xref: /aosp_15_r20/external/tink/go/internal/aead/aes_gcm_insecure_iv.go (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15////////////////////////////////////////////////////////////////////////////////
16
17package aead
18
19import (
20	"bytes"
21	"crypto/aes"
22	"crypto/cipher"
23	"errors"
24	"fmt"
25)
26
27// TODO(b/201070904): Rename to AESGCMInsecureNonce and simplify by getting rid
28// of the prependIV bool.
29
30const (
31	// aesGCMMaxPlaintextSize is the maximum plaintext size defined by RFC 5116.
32	aesGCMMaxPlaintextSize = (1 << 36) - 31
33
34	intSize             = 32 << (^uint(0) >> 63) // 32 or 64
35	maxInt              = 1<<(intSize-1) - 1
36	maxIntPlaintextSize = maxInt - AESGCMIVSize - AESGCMTagSize
37
38	minNoIVCiphertextSize      = AESGCMTagSize
39	minPrependIVCiphertextSize = AESGCMIVSize + AESGCMTagSize
40)
41
42// AESGCMInsecureIV is an insecure implementation of the AEAD interface that
43// permits the user to set the IV.
44type AESGCMInsecureIV struct {
45	Key       []byte
46	prependIV bool
47}
48
49// NewAESGCMInsecureIV returns an AESGCMInsecureIV instance, where key is the
50// AES key with length 16 bytes (AES-128) or 32 bytes (AES-256).
51//
52// If prependIV is true, both the ciphertext returned from Encrypt and passed
53// into Decrypt are prefixed with the IV.
54func NewAESGCMInsecureIV(key []byte, prependIV bool) (*AESGCMInsecureIV, error) {
55	keySize := uint32(len(key))
56	if err := ValidateAESKeySize(keySize); err != nil {
57		return nil, fmt.Errorf("invalid AES key size: %s", err)
58	}
59	return &AESGCMInsecureIV{
60		Key:       key,
61		prependIV: prependIV,
62	}, nil
63}
64
65// Encrypt encrypts plaintext with iv as the initialization vector and
66// associatedData as associated data.
67//
68// If prependIV is true, the returned ciphertext contains both the IV used for
69// encryption and the actual ciphertext.
70// If false, the returned ciphertext contains only the actual ciphertext.
71//
72// Note: The crypto library's AES-GCM implementation always returns the
73// ciphertext with an AESGCMTagSize (16-byte) tag.
74func (i *AESGCMInsecureIV) Encrypt(iv, plaintext, associatedData []byte) ([]byte, error) {
75	if got, want := len(iv), AESGCMIVSize; got != want {
76		return nil, fmt.Errorf("unexpected IV size: got %d, want %d", got, want)
77	}
78	// Seal() checks plaintext length, but this duplicated check avoids panic.
79	var maxPlaintextSize uint64 = maxIntPlaintextSize
80	if maxIntPlaintextSize > aesGCMMaxPlaintextSize {
81		maxPlaintextSize = aesGCMMaxPlaintextSize
82	}
83	if uint64(len(plaintext)) > maxPlaintextSize {
84		return nil, fmt.Errorf("plaintext too long: got %d", len(plaintext))
85	}
86
87	cipher, err := i.newCipher()
88	if err != nil {
89		return nil, err
90	}
91	if !i.prependIV {
92		return cipher.Seal(nil, iv, plaintext, associatedData), nil
93	}
94	// Make the capacity of dst large enough so that both the IV and the ciphertext fit inside.
95	dst := make([]byte, 0, AESGCMIVSize+len(plaintext)+cipher.Overhead())
96	dst = append(dst, iv...)
97	// Seal appends the ciphertext to dst. So the final output is: iv || ciphertext.
98	return cipher.Seal(dst, iv, plaintext, associatedData), nil
99}
100
101// Decrypt decrypts ciphertext with iv as the initialization vector and
102// associatedData as associated data.
103//
104// If prependIV is true, the iv argument and the first AESGCMIVSize bytes of
105// ciphertext must be equal. The ciphertext argument is as follows:
106//
107//	| iv | actual ciphertext | tag |
108//
109// If false, the ciphertext argument is as follows:
110//
111//	| actual ciphertext | tag |
112func (i *AESGCMInsecureIV) Decrypt(iv, ciphertext, associatedData []byte) ([]byte, error) {
113	if len(iv) != AESGCMIVSize {
114		return nil, fmt.Errorf("unexpected IV size: got %d, want %d", len(iv), AESGCMIVSize)
115	}
116
117	var actualCiphertext []byte
118	if i.prependIV {
119		if len(ciphertext) < minPrependIVCiphertextSize {
120			return nil, fmt.Errorf("ciphertext too short: got %d, want >= %d", len(ciphertext), minPrependIVCiphertextSize)
121		}
122		if !bytes.Equal(iv, ciphertext[:AESGCMIVSize]) {
123			return nil, fmt.Errorf("unequal IVs: iv argument %x, ct prefix %x", iv, ciphertext[:AESGCMIVSize])
124		}
125		actualCiphertext = ciphertext[AESGCMIVSize:]
126	} else {
127		if len(ciphertext) < minNoIVCiphertextSize {
128			return nil, fmt.Errorf("ciphertext too short: got %d, want >= %d", len(ciphertext), minNoIVCiphertextSize)
129		}
130		actualCiphertext = ciphertext
131	}
132
133	cipher, err := i.newCipher()
134	if err != nil {
135		return nil, err
136	}
137	plaintext, err := cipher.Open(nil, iv, actualCiphertext, associatedData)
138	if err != nil {
139		return nil, err
140	}
141	return plaintext, nil
142}
143
144// newCipher creates a new AES-GCM cipher using the given key and the crypto
145// library.
146func (i *AESGCMInsecureIV) newCipher() (cipher.AEAD, error) {
147	aesCipher, err := aes.NewCipher(i.Key)
148	if err != nil {
149		return nil, errors.New("failed to initialize cipher")
150	}
151	ret, err := cipher.NewGCM(aesCipher)
152	if err != nil {
153		return nil, errors.New("failed to initialize cipher")
154	}
155	return ret, nil
156}
157