xref: /aosp_15_r20/external/tink/go/kwp/subtle/kwp.go (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1// Copyright 2020 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
17// Package subtle implements the key wrapping primitive KWP defined in
18// NIST SP 800 38f.
19//
20// The same encryption mode is also defined in RFC 5649. The NIST document is
21// used here as a primary reference, since it contains a security analysis and
22// further recommendations. In particular, Section 8 of NIST SP 800 38f
23// suggests that the allowed key sizes may be restricted. The implementation in
24// this package requires that the key sizes are in the range MinWrapSize and
25// MaxWrapSize.
26//
27// The minimum of 16 bytes has been chosen, because 128 bit keys are the
28// smallest key sizes used in tink. Additionally, wrapping short keys with KWP
29// does not use the function W and hence prevents using security arguments
30// based on the assumption that W is a strong pseudorandom. One consequence of
31// using a strong pseudorandom permutation as an underlying function is that
32// leaking partial information about decrypted bytes is not useful for an
33// attack.
34//
35// The upper bound for the key size is somewhat arbitrary. Setting an upper
36// bound is motivated by the analysis in section A.4 of NIST SP 800 38f:
37// forgery of long messages is simpler than forgery of short messages.
38package subtle
39
40import (
41	"crypto/aes"
42	"crypto/cipher"
43	"encoding/binary"
44	"fmt"
45	"math"
46
47	// Placeholder for internal crypto/cipher allowlist, please ignore.
48)
49
50const (
51	// MinWrapSize is the smallest key byte length that may be wrapped.
52	MinWrapSize = 16
53	// MaxWrapSize is the largest key byte length that may be wrapped.
54	MaxWrapSize = 8192
55
56	roundCount = 6
57	ivPrefix   = uint32(0xA65959A6)
58)
59
60// KWP is an implementation of an AES-KWP key wrapping cipher.
61type KWP struct {
62	block cipher.Block
63}
64
65// NewKWP returns a KWP instance.
66//
67// The key argument should be the AES wrapping key, either 16 or 32 bytes
68// to select AES-128 or AES-256.
69func NewKWP(wrappingKey []byte) (*KWP, error) {
70	switch len(wrappingKey) {
71	default:
72		return nil, fmt.Errorf("kwp: invalid AES key size; want 16 or 32, got %d", len(wrappingKey))
73	case 16, 32:
74		block, err := aes.NewCipher(wrappingKey)
75		if err != nil {
76			return nil, fmt.Errorf("kwp: error building AES cipher: %v", err)
77		}
78		return &KWP{block: block}, nil
79	}
80}
81
82// wrappingSize computes the byte length of the ciphertext output for the
83// provided plaintext input.
84func wrappingSize(inputSize int) int {
85	paddingSize := 7 - (inputSize+7)%8
86	return inputSize + paddingSize + 8
87}
88
89// computeW computes the pseudorandom permutation W over the IV concatenated
90// with zero-padded key material.
91func (kwp *KWP) computeW(iv, key []byte) ([]byte, error) {
92	// Checks the parameter sizes for which W is defined.
93	// Note that the caller ensures stricter limits.
94	if len(key) <= 8 || len(key) > math.MaxInt32-16 || len(iv) != 8 {
95		return nil, fmt.Errorf("kwp: computeW called with invalid parameters")
96	}
97
98	data := make([]byte, wrappingSize(len(key)))
99	copy(data, iv)
100	copy(data[8:], key)
101	blockCount := len(data)/8 - 1
102
103	buf := make([]byte, 16)
104	copy(buf, data[:8])
105
106	for i := 0; i < roundCount; i++ {
107		for j := 0; j < blockCount; j++ {
108
109			copy(buf[8:], data[8*(j+1):])
110			kwp.block.Encrypt(buf, buf)
111
112			// xor the round constant in big endian order
113			// to the left half of the buffer
114			roundConst := uint(i*blockCount + j + 1)
115			for b := 0; b < 4; b++ {
116				buf[7-b] ^= byte(roundConst & 0xFF)
117				roundConst >>= 8
118			}
119
120			copy(data[8*(j+1):], buf[8:])
121		}
122	}
123	copy(data[:8], buf)
124	return data, nil
125}
126
127// invertW computes the inverse of the pseudorandom permutation W. Note that
128// invertW does not perform an integrity check.
129func (kwp *KWP) invertW(wrapped []byte) ([]byte, error) {
130	// Checks the input size for which invertW is defined.
131	// Note that the caller ensures stricter limits.
132	if len(wrapped) < 24 || len(wrapped)%8 != 0 {
133		return nil, fmt.Errorf("kwp: incorrect data size")
134	}
135
136	data := make([]byte, len(wrapped))
137	copy(data, wrapped)
138
139	blockCount := len(data)/8 - 1
140
141	buf := make([]byte, 16)
142	copy(buf, data[:8])
143
144	for i := roundCount - 1; i >= 0; i-- {
145		for j := blockCount - 1; j >= 0; j-- {
146			copy(buf[8:], data[8*(j+1):])
147
148			// xor the round constant in big endian order
149			// to the left half of the buffer
150			roundConst := uint(i*blockCount + j + 1)
151			for b := 0; b < 4; b++ {
152				buf[7-b] ^= byte(roundConst & 0xFF)
153				roundConst >>= 8
154			}
155
156			kwp.block.Decrypt(buf, buf)
157			copy(data[8*(j+1):], buf[8:])
158		}
159	}
160
161	copy(data, buf[:8])
162	return data, nil
163}
164
165// Wrap wraps the provided key material.
166func (kwp *KWP) Wrap(data []byte) ([]byte, error) {
167	if len(data) < MinWrapSize {
168		return nil, fmt.Errorf("kwp: key size to wrap too small")
169	}
170	if len(data) > MaxWrapSize {
171		return nil, fmt.Errorf("kwp: key size to wrap too large")
172	}
173
174	iv := make([]byte, 8)
175	binary.BigEndian.PutUint32(iv, ivPrefix)
176	binary.BigEndian.PutUint32(iv[4:], uint32(len(data)))
177
178	return kwp.computeW(iv, data)
179}
180
181var errIntegrity = fmt.Errorf("kwp: unwrap failed integrity check")
182
183// Unwrap unwraps a wrapped key.
184func (kwp *KWP) Unwrap(data []byte) ([]byte, error) {
185	if len(data) < wrappingSize(MinWrapSize) {
186		return nil, fmt.Errorf("kwp: wrapped key size too small")
187	}
188	if len(data) > wrappingSize(MaxWrapSize) {
189		return nil, fmt.Errorf("kwp: wrapped key size too large")
190	}
191	if len(data)%8 != 0 {
192		return nil, fmt.Errorf("kwp: wrapped key size must be a multiple of 8 bytes")
193	}
194
195	unwrapped, err := kwp.invertW(data)
196	if err != nil {
197		return nil, err
198	}
199
200	// Check the IV and padding.
201	// W has been designed to be strong pseudorandom permutation, so
202	// leaking information about improperly padded keys would not be a
203	// vulnerability. This means we don't have to go to extra lengths to
204	// ensure that the integrity checks run in constant time.
205
206	if binary.BigEndian.Uint32(unwrapped) != ivPrefix {
207		return nil, errIntegrity
208	}
209
210	encodedSize := int(binary.BigEndian.Uint32(unwrapped[4:]))
211	if encodedSize < 0 || wrappingSize(encodedSize) != len(unwrapped) {
212		return nil, errIntegrity
213	}
214
215	for i := 8 + encodedSize; i < len(unwrapped); i++ {
216		if unwrapped[i] != 0 {
217			return nil, errIntegrity
218		}
219	}
220
221	return unwrapped[8 : 8+encodedSize], nil
222}
223