• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1package runner
2
3import (
4	"crypto/cipher"
5	"crypto/subtle"
6	"encoding/binary"
7	"errors"
8
9	"./poly1305"
10)
11
12// See RFC 7539.
13
14func leftRotate(a uint32, n uint) uint32 {
15	return (a << n) | (a >> (32 - n))
16}
17
18func chaChaQuarterRound(state *[16]uint32, a, b, c, d int) {
19	state[a] += state[b]
20	state[d] = leftRotate(state[d]^state[a], 16)
21
22	state[c] += state[d]
23	state[b] = leftRotate(state[b]^state[c], 12)
24
25	state[a] += state[b]
26	state[d] = leftRotate(state[d]^state[a], 8)
27
28	state[c] += state[d]
29	state[b] = leftRotate(state[b]^state[c], 7)
30}
31
32func chaCha20Block(state *[16]uint32, out []byte) {
33	var workingState [16]uint32
34	copy(workingState[:], state[:])
35	for i := 0; i < 10; i++ {
36		chaChaQuarterRound(&workingState, 0, 4, 8, 12)
37		chaChaQuarterRound(&workingState, 1, 5, 9, 13)
38		chaChaQuarterRound(&workingState, 2, 6, 10, 14)
39		chaChaQuarterRound(&workingState, 3, 7, 11, 15)
40		chaChaQuarterRound(&workingState, 0, 5, 10, 15)
41		chaChaQuarterRound(&workingState, 1, 6, 11, 12)
42		chaChaQuarterRound(&workingState, 2, 7, 8, 13)
43		chaChaQuarterRound(&workingState, 3, 4, 9, 14)
44	}
45	for i := 0; i < 16; i++ {
46		binary.LittleEndian.PutUint32(out[i*4:i*4+4], workingState[i]+state[i])
47	}
48}
49
50// sliceForAppend takes a slice and a requested number of bytes. It returns a
51// slice with the contents of the given slice followed by that many bytes and a
52// second slice that aliases into it and contains only the extra bytes. If the
53// original slice has sufficient capacity then no allocation is performed.
54func sliceForAppend(in []byte, n int) (head, tail []byte) {
55	if total := len(in) + n; cap(in) >= total {
56		head = in[:total]
57	} else {
58		head = make([]byte, total)
59		copy(head, in)
60	}
61	tail = head[len(in):]
62	return
63}
64
65func chaCha20(out, in, key, nonce []byte, counter uint64) {
66	var state [16]uint32
67	state[0] = 0x61707865
68	state[1] = 0x3320646e
69	state[2] = 0x79622d32
70	state[3] = 0x6b206574
71	for i := 0; i < 8; i++ {
72		state[4+i] = binary.LittleEndian.Uint32(key[i*4 : i*4+4])
73	}
74
75	switch len(nonce) {
76	case 8:
77		state[14] = binary.LittleEndian.Uint32(nonce[0:4])
78		state[15] = binary.LittleEndian.Uint32(nonce[4:8])
79	case 12:
80		state[13] = binary.LittleEndian.Uint32(nonce[0:4])
81		state[14] = binary.LittleEndian.Uint32(nonce[4:8])
82		state[15] = binary.LittleEndian.Uint32(nonce[8:12])
83	default:
84		panic("bad nonce length")
85	}
86
87	for i := 0; i < len(in); i += 64 {
88		state[12] = uint32(counter)
89
90		var tmp [64]byte
91		chaCha20Block(&state, tmp[:])
92		count := 64
93		if len(in)-i < count {
94			count = len(in) - i
95		}
96		for j := 0; j < count; j++ {
97			out[i+j] = in[i+j] ^ tmp[j]
98		}
99
100		counter++
101	}
102}
103
104// chaCha20Poly1305 implements the AEAD from
105// RFC 7539 and draft-agl-tls-chacha20poly1305-04.
106type chaCha20Poly1305 struct {
107	key [32]byte
108	// oldMode, if true, indicates that the draft spec should be
109	// implemented rather than the final, RFC version.
110	oldMode bool
111}
112
113func newChaCha20Poly1305(key []byte) (cipher.AEAD, error) {
114	if len(key) != 32 {
115		return nil, errors.New("bad key length")
116	}
117	aead := new(chaCha20Poly1305)
118	copy(aead.key[:], key)
119	return aead, nil
120}
121
122func newChaCha20Poly1305Old(key []byte) (cipher.AEAD, error) {
123	if len(key) != 32 {
124		return nil, errors.New("bad key length")
125	}
126	aead := &chaCha20Poly1305{
127		oldMode: true,
128	}
129	copy(aead.key[:], key)
130	return aead, nil
131}
132
133func (c *chaCha20Poly1305) NonceSize() int {
134	if c.oldMode {
135		return 8
136	} else {
137		return 12
138	}
139}
140
141func (c *chaCha20Poly1305) Overhead() int { return 16 }
142
143func (c *chaCha20Poly1305) poly1305(tag *[16]byte, nonce, ciphertext, additionalData []byte) {
144	input := make([]byte, 0, len(additionalData)+15+len(ciphertext)+15+8+8)
145	input = append(input, additionalData...)
146	var zeros [15]byte
147	if pad := len(input) % 16; pad != 0 {
148		input = append(input, zeros[:16-pad]...)
149	}
150	input = append(input, ciphertext...)
151	if pad := len(input) % 16; pad != 0 {
152		input = append(input, zeros[:16-pad]...)
153	}
154	input, out := sliceForAppend(input, 8)
155	binary.LittleEndian.PutUint64(out, uint64(len(additionalData)))
156	input, out = sliceForAppend(input, 8)
157	binary.LittleEndian.PutUint64(out, uint64(len(ciphertext)))
158
159	var poly1305Key [32]byte
160	chaCha20(poly1305Key[:], poly1305Key[:], c.key[:], nonce, 0)
161
162	poly1305.Sum(tag, input, &poly1305Key)
163}
164
165func (c *chaCha20Poly1305) poly1305Old(tag *[16]byte, nonce, ciphertext, additionalData []byte) {
166	input := make([]byte, 0, len(additionalData)+8+len(ciphertext)+8)
167	input = append(input, additionalData...)
168	input, out := sliceForAppend(input, 8)
169	binary.LittleEndian.PutUint64(out, uint64(len(additionalData)))
170	input = append(input, ciphertext...)
171	input, out = sliceForAppend(input, 8)
172	binary.LittleEndian.PutUint64(out, uint64(len(ciphertext)))
173
174	var poly1305Key [32]byte
175	chaCha20(poly1305Key[:], poly1305Key[:], c.key[:], nonce, 0)
176
177	poly1305.Sum(tag, input, &poly1305Key)
178}
179
180func (c *chaCha20Poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
181	if len(nonce) != c.NonceSize() {
182		panic("Bad nonce length")
183	}
184
185	ret, out := sliceForAppend(dst, len(plaintext)+16)
186	chaCha20(out[:len(plaintext)], plaintext, c.key[:], nonce, 1)
187
188	var tag [16]byte
189	if c.oldMode {
190		c.poly1305Old(&tag, nonce, out[:len(plaintext)], additionalData)
191	} else {
192		c.poly1305(&tag, nonce, out[:len(plaintext)], additionalData)
193	}
194	copy(out[len(plaintext):], tag[:])
195
196	return ret
197}
198
199func (c *chaCha20Poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
200	if len(nonce) != c.NonceSize() {
201		panic("Bad nonce length")
202	}
203	if len(ciphertext) < 16 {
204		return nil, errors.New("chacha20: message authentication failed")
205	}
206	plaintextLen := len(ciphertext) - 16
207
208	var tag [16]byte
209	if c.oldMode {
210		c.poly1305Old(&tag, nonce, ciphertext[:plaintextLen], additionalData)
211	} else {
212		c.poly1305(&tag, nonce, ciphertext[:plaintextLen], additionalData)
213	}
214	if subtle.ConstantTimeCompare(tag[:], ciphertext[plaintextLen:]) != 1 {
215		return nil, errors.New("chacha20: message authentication failed")
216	}
217
218	ret, out := sliceForAppend(dst, plaintextLen)
219	chaCha20(out, ciphertext[:plaintextLen], c.key[:], nonce, 1)
220	return ret, nil
221}
222