// 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 }