summaryrefslogtreecommitdiffstats
path: root/src/ssl/test/runner/chacha20_poly1305.go
blob: 42911d4f9ce6753a51f377ba667411ec9b374c57 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package main

import (
	"crypto/cipher"
	"crypto/subtle"
	"encoding/binary"
	"errors"
)

// See draft-agl-tls-chacha20poly1305-04 and
// draft-irtf-cfrg-chacha20-poly1305-10. Where the two differ, the
// draft-agl-tls-chacha20poly1305-04 variant is implemented.

func leftRotate(a uint32, n uint) uint32 {
	return (a << n) | (a >> (32 - n))
}

func chaChaQuarterRound(state *[16]uint32, a, b, c, d int) {
	state[a] += state[b]
	state[d] = leftRotate(state[d]^state[a], 16)

	state[c] += state[d]
	state[b] = leftRotate(state[b]^state[c], 12)

	state[a] += state[b]
	state[d] = leftRotate(state[d]^state[a], 8)

	state[c] += state[d]
	state[b] = leftRotate(state[b]^state[c], 7)
}

func chaCha20Block(state *[16]uint32, out []byte) {
	var workingState [16]uint32
	copy(workingState[:], state[:])
	for i := 0; i < 10; i++ {
		chaChaQuarterRound(&workingState, 0, 4, 8, 12)
		chaChaQuarterRound(&workingState, 1, 5, 9, 13)
		chaChaQuarterRound(&workingState, 2, 6, 10, 14)
		chaChaQuarterRound(&workingState, 3, 7, 11, 15)
		chaChaQuarterRound(&workingState, 0, 5, 10, 15)
		chaChaQuarterRound(&workingState, 1, 6, 11, 12)
		chaChaQuarterRound(&workingState, 2, 7, 8, 13)
		chaChaQuarterRound(&workingState, 3, 4, 9, 14)
	}
	for i := 0; i < 16; i++ {
		binary.LittleEndian.PutUint32(out[i*4:i*4+4], workingState[i]+state[i])
	}
}

// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
func sliceForAppend(in []byte, n int) (head, tail []byte) {
	if total := len(in) + n; cap(in) >= total {
		head = in[:total]
	} else {
		head = make([]byte, total)
		copy(head, in)
	}
	tail = head[len(in):]
	return
}

type chaCha20Poly1305 struct {
	key [32]byte
}

func newChaCha20Poly1305(key []byte) (cipher.AEAD, error) {
	if len(key) != 32 {
		return nil, errors.New("bad key length")
	}
	aead := new(chaCha20Poly1305)
	copy(aead.key[:], key)
	return aead, nil
}

func (c *chaCha20Poly1305) NonceSize() int { return 8 }
func (c *chaCha20Poly1305) Overhead() int  { return 16 }

func (c *chaCha20Poly1305) chaCha20(out, in, nonce []byte, counter uint64) {
	var state [16]uint32
	state[0] = 0x61707865
	state[1] = 0x3320646e
	state[2] = 0x79622d32
	state[3] = 0x6b206574
	for i := 0; i < 8; i++ {
		state[4+i] = binary.LittleEndian.Uint32(c.key[i*4 : i*4+4])
	}
	state[14] = binary.LittleEndian.Uint32(nonce[0:4])
	state[15] = binary.LittleEndian.Uint32(nonce[4:8])

	for i := 0; i < len(in); i += 64 {
		state[12] = uint32(counter & 0xffffffff)
		state[13] = uint32(counter >> 32)

		var tmp [64]byte
		chaCha20Block(&state, tmp[:])
		count := 64
		if len(in)-i < count {
			count = len(in) - i
		}
		for j := 0; j < count; j++ {
			out[i+j] = in[i+j] ^ tmp[j]
		}

		counter++
	}
}

func (c *chaCha20Poly1305) poly1305(tag *[16]byte, nonce, ciphertext, additionalData []byte) {
	input := make([]byte, 0, len(additionalData)+8+len(ciphertext)+8)
	input = append(input, additionalData...)
	input, out := sliceForAppend(input, 8)
	binary.LittleEndian.PutUint64(out, uint64(len(additionalData)))
	input = append(input, ciphertext...)
	input, out = sliceForAppend(input, 8)
	binary.LittleEndian.PutUint64(out, uint64(len(ciphertext)))

	var poly1305Key [32]byte
	c.chaCha20(poly1305Key[:], poly1305Key[:], nonce, 0)

	poly1305Sum(tag, input, &poly1305Key)
}

func (c *chaCha20Poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
	if len(nonce) != 8 {
		panic("Bad nonce length")
	}

	ret, out := sliceForAppend(dst, len(plaintext)+16)
	c.chaCha20(out[:len(plaintext)], plaintext, nonce, 1)

	var tag [16]byte
	c.poly1305(&tag, nonce, out[:len(plaintext)], additionalData)
	copy(out[len(plaintext):], tag[:])

	return ret
}

func (c *chaCha20Poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
	if len(nonce) != 8 {
		panic("Bad nonce length")
	}
	if len(ciphertext) < 16 {
		return nil, errors.New("chacha20: message authentication failed")
	}
	plaintextLen := len(ciphertext) - 16

	var tag [16]byte
	c.poly1305(&tag, nonce, ciphertext[:plaintextLen], additionalData)
	if subtle.ConstantTimeCompare(tag[:], ciphertext[plaintextLen:]) != 1 {
		return nil, errors.New("chacha20: message authentication failed")
	}

	ret, out := sliceForAppend(dst, plaintextLen)
	c.chaCha20(out, ciphertext[:plaintextLen], nonce, 1)
	return ret, nil
}