-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgcm.go
377 lines (333 loc) · 13 KB
/
gcm.go
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cipher
import (
"crypto/internal/fips140/aes"
"crypto/internal/fips140/aes/gcm"
"crypto/internal/fips140/alias"
"crypto/internal/fips140only"
"crypto/subtle"
"errors"
"internal/byteorder"
)
const (
gcmBlockSize = 16
gcmStandardNonceSize = 12
gcmTagSize = 16
gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes.
)
// NewGCM returns the given 128-bit, block cipher wrapped in Galois Counter Mode
// with the standard nonce length.
//
// In general, the GHASH operation performed by this implementation of GCM is not constant-time.
// An exception is when the underlying [Block] was created by aes.NewCipher
// on systems with hardware support for AES. See the [crypto/aes] package documentation for details.
func NewGCM(cipher Block) (AEAD, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce")
}
return newGCM(cipher, gcmStandardNonceSize, gcmTagSize)
}
// NewGCMWithNonceSize returns the given 128-bit, block cipher wrapped in Galois
// Counter Mode, which accepts nonces of the given length. The length must not
// be zero.
//
// Only use this function if you require compatibility with an existing
// cryptosystem that uses non-standard nonce lengths. All other users should use
// [NewGCM], which is faster and more resistant to misuse.
func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce")
}
return newGCM(cipher, size, gcmTagSize)
}
// NewGCMWithTagSize returns the given 128-bit, block cipher wrapped in Galois
// Counter Mode, which generates tags with the given length.
//
// Tag sizes between 12 and 16 bytes are allowed.
//
// Only use this function if you require compatibility with an existing
// cryptosystem that uses non-standard tag lengths. All other users should use
// [NewGCM], which is more resistant to misuse.
func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce")
}
return newGCM(cipher, gcmStandardNonceSize, tagSize)
}
func newGCM(cipher Block, nonceSize, tagSize int) (AEAD, error) {
c, ok := cipher.(*aes.Block)
if !ok {
if fips140only.Enabled {
return nil, errors.New("crypto/cipher: use of GCM with non-AES ciphers is not allowed in FIPS 140-only mode")
}
return newGCMFallback(cipher, nonceSize, tagSize)
}
// We don't return gcm.New directly, because it would always return a non-nil
// AEAD interface value with type *gcm.GCM even if the *gcm.GCM is nil.
g, err := gcm.New(c, nonceSize, tagSize)
if err != nil {
return nil, err
}
return g, nil
}
// NewGCMWithRandomNonce returns the given cipher wrapped in Galois Counter
// Mode, with randomly-generated nonces. The cipher must have been created by
// [aes.NewCipher].
//
// It generates a random 96-bit nonce, which is prepended to the ciphertext by Seal,
// and is extracted from the ciphertext by Open. The NonceSize of the AEAD is zero,
// while the Overhead is 28 bytes (the combination of nonce size and tag size).
//
// A given key MUST NOT be used to encrypt more than 2^32 messages, to limit the
// risk of a random nonce collision to negligible levels.
func NewGCMWithRandomNonce(cipher Block) (AEAD, error) {
c, ok := cipher.(*aes.Block)
if !ok {
return nil, errors.New("cipher: NewGCMWithRandomNonce requires aes.Block")
}
g, err := gcm.New(c, gcmStandardNonceSize, gcmTagSize)
if err != nil {
return nil, err
}
return gcmWithRandomNonce{g}, nil
}
type gcmWithRandomNonce struct {
*gcm.GCM
}
func (g gcmWithRandomNonce) NonceSize() int {
return 0
}
func (g gcmWithRandomNonce) Overhead() int {
return gcmStandardNonceSize + gcmTagSize
}
func (g gcmWithRandomNonce) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != 0 {
panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce")
}
ret, out := sliceForAppend(dst, gcmStandardNonceSize+len(plaintext)+gcmTagSize)
if alias.InexactOverlap(out, plaintext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
nonce = out[:gcmStandardNonceSize]
ciphertext := out[gcmStandardNonceSize:]
// The AEAD interface allows using plaintext[:0] or ciphertext[:0] as dst.
//
// This is kind of a problem when trying to prepend or trim a nonce, because the
// actual AES-GCTR blocks end up overlapping but not exactly.
//
// In Open, we write the output *before* the input, so unless we do something
// weird like working through a chunk of block backwards, it works out.
//
// In Seal, we could work through the input backwards or intentionally load
// ahead before writing.
//
// However, the crypto/internal/fips140/aes/gcm APIs also check for exact overlap,
// so for now we just do a memmove if we detect overlap.
//
// ┌───────────────────────────┬ ─ ─
// │PPPPPPPPPPPPPPPPPPPPPPPPPPP│ │
// └▽─────────────────────────▲┴ ─ ─
// ╲ Seal ╲
// ╲ Open ╲
// ┌───▼─────────────────────────△──┐
// │NN|CCCCCCCCCCCCCCCCCCCCCCCCCCC|T│
// └────────────────────────────────┘
//
if alias.AnyOverlap(out, plaintext) {
copy(ciphertext, plaintext)
plaintext = ciphertext[:len(plaintext)]
}
gcm.SealWithRandomNonce(g.GCM, nonce, ciphertext, plaintext, additionalData)
return ret
}
func (g gcmWithRandomNonce) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != 0 {
panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce")
}
if len(ciphertext) < gcmStandardNonceSize+gcmTagSize {
return nil, errOpen
}
ret, out := sliceForAppend(dst, len(ciphertext)-gcmStandardNonceSize-gcmTagSize)
if alias.InexactOverlap(out, ciphertext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
// See the discussion in Seal. Note that if there is any overlap at this
// point, it's because out = ciphertext, so out must have enough capacity
// even if we sliced the tag off. Also note how [AEAD] specifies that "the
// contents of dst, up to its capacity, may be overwritten".
if alias.AnyOverlap(out, ciphertext) {
nonce = make([]byte, gcmStandardNonceSize)
copy(nonce, ciphertext)
copy(out[:len(ciphertext)], ciphertext[gcmStandardNonceSize:])
ciphertext = out[:len(ciphertext)-gcmStandardNonceSize]
} else {
nonce = ciphertext[:gcmStandardNonceSize]
ciphertext = ciphertext[gcmStandardNonceSize:]
}
_, err := g.GCM.Open(out[:0], nonce, ciphertext, additionalData)
if err != nil {
return nil, err
}
return ret, nil
}
// gcmAble is an interface implemented by ciphers that have a specific optimized
// implementation of GCM. crypto/aes doesn't use this anymore, and we'd like to
// eventually remove it.
type gcmAble interface {
NewGCM(nonceSize, tagSize int) (AEAD, error)
}
func newGCMFallback(cipher Block, nonceSize, tagSize int) (AEAD, error) {
if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize {
return nil, errors.New("cipher: incorrect tag size given to GCM")
}
if nonceSize <= 0 {
return nil, errors.New("cipher: the nonce can't have zero length")
}
if cipher, ok := cipher.(gcmAble); ok {
return cipher.NewGCM(nonceSize, tagSize)
}
if cipher.BlockSize() != gcmBlockSize {
return nil, errors.New("cipher: NewGCM requires 128-bit block cipher")
}
return &gcmFallback{cipher: cipher, nonceSize: nonceSize, tagSize: tagSize}, nil
}
// gcmFallback is only used for non-AES ciphers, which regrettably we
// theoretically support. It's a copy of the generic implementation from
// crypto/internal/fips140/aes/gcm/gcm_generic.go, refer to that file for more details.
type gcmFallback struct {
cipher Block
nonceSize int
tagSize int
}
func (g *gcmFallback) NonceSize() int {
return g.nonceSize
}
func (g *gcmFallback) Overhead() int {
return g.tagSize
}
func (g *gcmFallback) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != g.nonceSize {
panic("crypto/cipher: incorrect nonce length given to GCM")
}
if g.nonceSize == 0 {
panic("crypto/cipher: incorrect GCM nonce size")
}
if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize {
panic("crypto/cipher: message too large for GCM")
}
ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize)
if alias.InexactOverlap(out, plaintext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
var H, counter, tagMask [gcmBlockSize]byte
g.cipher.Encrypt(H[:], H[:])
deriveCounter(&H, &counter, nonce)
gcmCounterCryptGeneric(g.cipher, tagMask[:], tagMask[:], &counter)
gcmCounterCryptGeneric(g.cipher, out, plaintext, &counter)
var tag [gcmTagSize]byte
gcmAuth(tag[:], &H, &tagMask, out[:len(plaintext)], additionalData)
copy(out[len(plaintext):], tag[:])
return ret
}
var errOpen = errors.New("cipher: message authentication failed")
func (g *gcmFallback) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != g.nonceSize {
panic("crypto/cipher: incorrect nonce length given to GCM")
}
if g.tagSize < gcmMinimumTagSize {
panic("crypto/cipher: incorrect GCM tag size")
}
if len(ciphertext) < g.tagSize {
return nil, errOpen
}
if uint64(len(ciphertext)) > uint64((1<<32)-2)*gcmBlockSize+uint64(g.tagSize) {
return nil, errOpen
}
ret, out := sliceForAppend(dst, len(ciphertext)-g.tagSize)
if alias.InexactOverlap(out, ciphertext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
var H, counter, tagMask [gcmBlockSize]byte
g.cipher.Encrypt(H[:], H[:])
deriveCounter(&H, &counter, nonce)
gcmCounterCryptGeneric(g.cipher, tagMask[:], tagMask[:], &counter)
tag := ciphertext[len(ciphertext)-g.tagSize:]
ciphertext = ciphertext[:len(ciphertext)-g.tagSize]
var expectedTag [gcmTagSize]byte
gcmAuth(expectedTag[:], &H, &tagMask, ciphertext, additionalData)
if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 {
// We sometimes decrypt and authenticate concurrently, so we overwrite
// dst in the event of a tag mismatch. To be consistent across platforms
// and to avoid releasing unauthenticated plaintext, we clear the buffer
// in the event of an error.
clear(out)
return nil, errOpen
}
gcmCounterCryptGeneric(g.cipher, out, ciphertext, &counter)
return ret, nil
}
func deriveCounter(H, counter *[gcmBlockSize]byte, nonce []byte) {
if len(nonce) == gcmStandardNonceSize {
copy(counter[:], nonce)
counter[gcmBlockSize-1] = 1
} else {
lenBlock := make([]byte, 16)
byteorder.BEPutUint64(lenBlock[8:], uint64(len(nonce))*8)
J := gcm.GHASH(H, nonce, lenBlock)
copy(counter[:], J)
}
}
func gcmCounterCryptGeneric(b Block, out, src []byte, counter *[gcmBlockSize]byte) {
var mask [gcmBlockSize]byte
for len(src) >= gcmBlockSize {
b.Encrypt(mask[:], counter[:])
gcmInc32(counter)
subtle.XORBytes(out, src, mask[:])
out = out[gcmBlockSize:]
src = src[gcmBlockSize:]
}
if len(src) > 0 {
b.Encrypt(mask[:], counter[:])
gcmInc32(counter)
subtle.XORBytes(out, src, mask[:])
}
}
func gcmInc32(counterBlock *[gcmBlockSize]byte) {
ctr := counterBlock[len(counterBlock)-4:]
byteorder.BEPutUint32(ctr, byteorder.BEUint32(ctr)+1)
}
func gcmAuth(out []byte, H, tagMask *[gcmBlockSize]byte, ciphertext, additionalData []byte) {
lenBlock := make([]byte, 16)
byteorder.BEPutUint64(lenBlock[:8], uint64(len(additionalData))*8)
byteorder.BEPutUint64(lenBlock[8:], uint64(len(ciphertext))*8)
S := gcm.GHASH(H, additionalData, ciphertext, lenBlock)
subtle.XORBytes(out, S, tagMask[:])
}
// 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
}