• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2016, Google Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15package runner
16
17import (
18	"bytes"
19	"crypto/aes"
20	"crypto/cipher"
21	"crypto/hmac"
22	"crypto/sha256"
23	"encoding/asn1"
24	"errors"
25)
26
27// TestShimTicketKey is the testing key assumed for the shim.
28var TestShimTicketKey = make([]byte, 48)
29
30func DecryptShimTicket(in []byte) ([]byte, error) {
31	name := TestShimTicketKey[:16]
32	macKey := TestShimTicketKey[16:32]
33	encKey := TestShimTicketKey[32:48]
34
35	h := hmac.New(sha256.New, macKey)
36
37	block, err := aes.NewCipher(encKey)
38	if err != nil {
39		panic(err)
40	}
41
42	if len(in) < len(name)+block.BlockSize()+1+h.Size() {
43		return nil, errors.New("tls: shim ticket too short")
44	}
45
46	// Check the key name.
47	if !bytes.Equal(name, in[:len(name)]) {
48		return nil, errors.New("tls: shim ticket name mismatch")
49	}
50
51	// Check the MAC at the end of the ticket.
52	mac := in[len(in)-h.Size():]
53	in = in[:len(in)-h.Size()]
54	h.Write(in)
55	if !hmac.Equal(mac, h.Sum(nil)) {
56		return nil, errors.New("tls: shim ticket MAC mismatch")
57	}
58
59	// The MAC covers the key name, but the encryption does not.
60	in = in[len(name):]
61
62	// Decrypt in-place.
63	iv := in[:block.BlockSize()]
64	in = in[block.BlockSize():]
65	if l := len(in); l == 0 || l%block.BlockSize() != 0 {
66		return nil, errors.New("tls: ticket ciphertext not a multiple of the block size")
67	}
68	out := make([]byte, len(in))
69	cbc := cipher.NewCBCDecrypter(block, iv)
70	cbc.CryptBlocks(out, in)
71
72	// Remove the padding.
73	pad := int(out[len(out)-1])
74	if pad == 0 || pad > block.BlockSize() || pad > len(in) {
75		return nil, errors.New("tls: bad shim ticket CBC pad")
76	}
77
78	for i := 0; i < pad; i++ {
79		if out[len(out)-1-i] != byte(pad) {
80			return nil, errors.New("tls: bad shim ticket CBC pad")
81		}
82	}
83
84	return out[:len(out)-pad], nil
85}
86
87func EncryptShimTicket(in []byte) []byte {
88	name := TestShimTicketKey[:16]
89	macKey := TestShimTicketKey[16:32]
90	encKey := TestShimTicketKey[32:48]
91
92	h := hmac.New(sha256.New, macKey)
93
94	block, err := aes.NewCipher(encKey)
95	if err != nil {
96		panic(err)
97	}
98
99	// Use the zero IV for rewritten tickets.
100	iv := make([]byte, block.BlockSize())
101	cbc := cipher.NewCBCEncrypter(block, iv)
102	pad := block.BlockSize() - (len(in) % block.BlockSize())
103
104	out := make([]byte, 0, len(name)+len(iv)+len(in)+pad+h.Size())
105	out = append(out, name...)
106	out = append(out, iv...)
107	out = append(out, in...)
108	for i := 0; i < pad; i++ {
109		out = append(out, byte(pad))
110	}
111
112	ciphertext := out[len(name)+len(iv):]
113	cbc.CryptBlocks(ciphertext, ciphertext)
114
115	h.Write(out)
116	return h.Sum(out)
117}
118
119const asn1Constructed = 0x20
120
121func parseDERElement(in []byte) (tag byte, body, rest []byte, ok bool) {
122	rest = in
123	if len(rest) < 1 {
124		return
125	}
126
127	tag = rest[0]
128	rest = rest[1:]
129
130	if tag&0x1f == 0x1f {
131		// Long-form tags not supported.
132		return
133	}
134
135	if len(rest) < 1 {
136		return
137	}
138
139	length := int(rest[0])
140	rest = rest[1:]
141	if length > 0x7f {
142		lengthLength := length & 0x7f
143		length = 0
144		if lengthLength == 0 {
145			// No indefinite-length encoding.
146			return
147		}
148
149		// Decode long-form lengths.
150		for lengthLength > 0 {
151			if len(rest) < 1 || (length<<8)>>8 != length {
152				return
153			}
154			if length == 0 && rest[0] == 0 {
155				// Length not minimally-encoded.
156				return
157			}
158			length <<= 8
159			length |= int(rest[0])
160			rest = rest[1:]
161			lengthLength--
162		}
163
164		if length < 0x80 {
165			// Length not minimally-encoded.
166			return
167		}
168	}
169
170	if len(rest) < length {
171		return
172	}
173
174	body = rest[:length]
175	rest = rest[length:]
176	ok = true
177	return
178}
179
180func SetShimTicketVersion(in []byte, vers uint16) ([]byte, error) {
181	plaintext, err := DecryptShimTicket(in)
182	if err != nil {
183		return nil, err
184	}
185
186	tag, session, _, ok := parseDERElement(plaintext)
187	if !ok || tag != asn1.TagSequence|asn1Constructed {
188		return nil, errors.New("tls: could not decode shim session")
189	}
190
191	// Skip the session version.
192	tag, _, session, ok = parseDERElement(session)
193	if !ok || tag != asn1.TagInteger {
194		return nil, errors.New("tls: could not decode shim session")
195	}
196
197	// Next field is the protocol version.
198	tag, version, _, ok := parseDERElement(session)
199	if !ok || tag != asn1.TagInteger {
200		return nil, errors.New("tls: could not decode shim session")
201	}
202
203	// This code assumes both old and new versions are encoded in two
204	// bytes. This isn't quite right as INTEGERs are minimally-encoded, but
205	// we do not need to support other caess for now.
206	if len(version) != 2 || vers < 0x80 || vers >= 0x8000 {
207		return nil, errors.New("tls: unsupported version in shim session")
208	}
209
210	version[0] = byte(vers >> 8)
211	version[1] = byte(vers)
212
213	return EncryptShimTicket(plaintext), nil
214}
215
216func SetShimTicketCipherSuite(in []byte, id uint16) ([]byte, error) {
217	plaintext, err := DecryptShimTicket(in)
218	if err != nil {
219		return nil, err
220	}
221
222	tag, session, _, ok := parseDERElement(plaintext)
223	if !ok || tag != asn1.TagSequence|asn1Constructed {
224		return nil, errors.New("tls: could not decode shim session")
225	}
226
227	// Skip the session version.
228	tag, _, session, ok = parseDERElement(session)
229	if !ok || tag != asn1.TagInteger {
230		return nil, errors.New("tls: could not decode shim session")
231	}
232
233	// Skip the protocol version.
234	tag, _, session, ok = parseDERElement(session)
235	if !ok || tag != asn1.TagInteger {
236		return nil, errors.New("tls: could not decode shim session")
237	}
238
239	// Next field is the cipher suite.
240	tag, cipherSuite, _, ok := parseDERElement(session)
241	if !ok || tag != asn1.TagOctetString || len(cipherSuite) != 2 {
242		return nil, errors.New("tls: could not decode shim session")
243	}
244
245	cipherSuite[0] = byte(id >> 8)
246	cipherSuite[1] = byte(id)
247
248	return EncryptShimTicket(plaintext), nil
249}
250