| // Copyright (c) 2016, Google Inc. |
| // |
| // Permission to use, copy, modify, and/or distribute this software for any |
| // purpose with or without fee is hereby granted, provided that the above |
| // copyright notice and this permission notice appear in all copies. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| |
| package runner |
| |
| import ( |
| "bytes" |
| "crypto/aes" |
| "crypto/cipher" |
| "crypto/hmac" |
| "crypto/sha256" |
| "encoding/asn1" |
| "errors" |
| ) |
| |
| // TestShimTicketKey is the testing key assumed for the shim. |
| var TestShimTicketKey = make([]byte, 48) |
| |
| func DecryptShimTicket(in []byte) ([]byte, error) { |
| name := TestShimTicketKey[:16] |
| macKey := TestShimTicketKey[16:32] |
| encKey := TestShimTicketKey[32:48] |
| |
| h := hmac.New(sha256.New, macKey) |
| |
| block, err := aes.NewCipher(encKey) |
| if err != nil { |
| panic(err) |
| } |
| |
| if len(in) < len(name)+block.BlockSize()+1+h.Size() { |
| return nil, errors.New("tls: shim ticket too short") |
| } |
| |
| // Check the key name. |
| if !bytes.Equal(name, in[:len(name)]) { |
| return nil, errors.New("tls: shim ticket name mismatch") |
| } |
| |
| // Check the MAC at the end of the ticket. |
| mac := in[len(in)-h.Size():] |
| in = in[:len(in)-h.Size()] |
| h.Write(in) |
| if !hmac.Equal(mac, h.Sum(nil)) { |
| return nil, errors.New("tls: shim ticket MAC mismatch") |
| } |
| |
| // The MAC covers the key name, but the encryption does not. |
| in = in[len(name):] |
| |
| // Decrypt in-place. |
| iv := in[:block.BlockSize()] |
| in = in[block.BlockSize():] |
| if l := len(in); l == 0 || l%block.BlockSize() != 0 { |
| return nil, errors.New("tls: ticket ciphertext not a multiple of the block size") |
| } |
| out := make([]byte, len(in)) |
| cbc := cipher.NewCBCDecrypter(block, iv) |
| cbc.CryptBlocks(out, in) |
| |
| // Remove the padding. |
| pad := int(out[len(out)-1]) |
| if pad == 0 || pad > block.BlockSize() || pad > len(in) { |
| return nil, errors.New("tls: bad shim ticket CBC pad") |
| } |
| |
| for i := 0; i < pad; i++ { |
| if out[len(out)-1-i] != byte(pad) { |
| return nil, errors.New("tls: bad shim ticket CBC pad") |
| } |
| } |
| |
| return out[:len(out)-pad], nil |
| } |
| |
| func EncryptShimTicket(in []byte) []byte { |
| name := TestShimTicketKey[:16] |
| macKey := TestShimTicketKey[16:32] |
| encKey := TestShimTicketKey[32:48] |
| |
| h := hmac.New(sha256.New, macKey) |
| |
| block, err := aes.NewCipher(encKey) |
| if err != nil { |
| panic(err) |
| } |
| |
| // Use the zero IV for rewritten tickets. |
| iv := make([]byte, block.BlockSize()) |
| cbc := cipher.NewCBCEncrypter(block, iv) |
| pad := block.BlockSize() - (len(in) % block.BlockSize()) |
| |
| out := make([]byte, 0, len(name)+len(iv)+len(in)+pad+h.Size()) |
| out = append(out, name...) |
| out = append(out, iv...) |
| out = append(out, in...) |
| for i := 0; i < pad; i++ { |
| out = append(out, byte(pad)) |
| } |
| |
| ciphertext := out[len(name)+len(iv):] |
| cbc.CryptBlocks(ciphertext, ciphertext) |
| |
| h.Write(out) |
| return h.Sum(out) |
| } |
| |
| const asn1Constructed = 0x20 |
| |
| func parseDERElement(in []byte) (tag byte, body, rest []byte, ok bool) { |
| rest = in |
| if len(rest) < 1 { |
| return |
| } |
| |
| tag = rest[0] |
| rest = rest[1:] |
| |
| if tag&0x1f == 0x1f { |
| // Long-form tags not supported. |
| return |
| } |
| |
| if len(rest) < 1 { |
| return |
| } |
| |
| length := int(rest[0]) |
| rest = rest[1:] |
| if length > 0x7f { |
| lengthLength := length & 0x7f |
| length = 0 |
| if lengthLength == 0 { |
| // No indefinite-length encoding. |
| return |
| } |
| |
| // Decode long-form lengths. |
| for lengthLength > 0 { |
| if len(rest) < 1 || (length<<8)>>8 != length { |
| return |
| } |
| if length == 0 && rest[0] == 0 { |
| // Length not minimally-encoded. |
| return |
| } |
| length <<= 8 |
| length |= int(rest[0]) |
| rest = rest[1:] |
| lengthLength-- |
| } |
| |
| if length < 0x80 { |
| // Length not minimally-encoded. |
| return |
| } |
| } |
| |
| if len(rest) < length { |
| return |
| } |
| |
| body = rest[:length] |
| rest = rest[length:] |
| ok = true |
| return |
| } |
| |
| func SetShimTicketVersion(in []byte, vers uint16) ([]byte, error) { |
| plaintext, err := DecryptShimTicket(in) |
| if err != nil { |
| return nil, err |
| } |
| |
| tag, session, _, ok := parseDERElement(plaintext) |
| if !ok || tag != asn1.TagSequence|asn1Constructed { |
| return nil, errors.New("tls: could not decode shim session") |
| } |
| |
| // Skip the session version. |
| tag, _, session, ok = parseDERElement(session) |
| if !ok || tag != asn1.TagInteger { |
| return nil, errors.New("tls: could not decode shim session") |
| } |
| |
| // Next field is the protocol version. |
| tag, version, _, ok := parseDERElement(session) |
| if !ok || tag != asn1.TagInteger { |
| return nil, errors.New("tls: could not decode shim session") |
| } |
| |
| // This code assumes both old and new versions are encoded in two |
| // bytes. This isn't quite right as INTEGERs are minimally-encoded, but |
| // we do not need to support other caess for now. |
| if len(version) != 2 || vers < 0x80 || vers >= 0x8000 { |
| return nil, errors.New("tls: unsupported version in shim session") |
| } |
| |
| version[0] = byte(vers >> 8) |
| version[1] = byte(vers) |
| |
| return EncryptShimTicket(plaintext), nil |
| } |
| |
| func SetShimTicketCipherSuite(in []byte, id uint16) ([]byte, error) { |
| plaintext, err := DecryptShimTicket(in) |
| if err != nil { |
| return nil, err |
| } |
| |
| tag, session, _, ok := parseDERElement(plaintext) |
| if !ok || tag != asn1.TagSequence|asn1Constructed { |
| return nil, errors.New("tls: could not decode shim session") |
| } |
| |
| // Skip the session version. |
| tag, _, session, ok = parseDERElement(session) |
| if !ok || tag != asn1.TagInteger { |
| return nil, errors.New("tls: could not decode shim session") |
| } |
| |
| // Skip the protocol version. |
| tag, _, session, ok = parseDERElement(session) |
| if !ok || tag != asn1.TagInteger { |
| return nil, errors.New("tls: could not decode shim session") |
| } |
| |
| // Next field is the cipher suite. |
| tag, cipherSuite, _, ok := parseDERElement(session) |
| if !ok || tag != asn1.TagOctetString || len(cipherSuite) != 2 { |
| return nil, errors.New("tls: could not decode shim session") |
| } |
| |
| cipherSuite[0] = byte(id >> 8) |
| cipherSuite[1] = byte(id) |
| |
| return EncryptShimTicket(plaintext), nil |
| } |