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