This repository has been archived by the owner on Apr 17, 2024. It is now read-only.
/
kwp.go
222 lines (188 loc) · 6.63 KB
/
kwp.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
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
// Package subtle implements the key wrapping primitive KWP defined in
// NIST SP 800 38f.
//
// The same encryption mode is also defined in RFC 5649. The NIST document is
// used here as a primary reference, since it contains a security analysis and
// further recommendations. In particular, Section 8 of NIST SP 800 38f
// suggests that the allowed key sizes may be restricted. The implementation in
// this package requires that the key sizes are in the range MinWrapSize and
// MaxWrapSize.
//
// The minimum of 16 bytes has been chosen, because 128 bit keys are the
// smallest key sizes used in tink. Additionally, wrapping short keys with KWP
// does not use the function W and hence prevents using security arguments
// based on the assumption that W is a strong pseudorandom. One consequence of
// using a strong pseudorandom permutation as an underlying function is that
// leaking partial information about decrypted bytes is not useful for an
// attack.
//
// The upper bound for the key size is somewhat arbitrary. Setting an upper
// bound is motivated by the analysis in section A.4 of NIST SP 800 38f:
// forgery of long messages is simpler than forgery of short messages.
package subtle
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"fmt"
"math"
// Placeholder for internal crypto/cipher allowlist, please ignore.
)
const (
// MinWrapSize is the smallest key byte length that may be wrapped.
MinWrapSize = 16
// MaxWrapSize is the largest key byte length that may be wrapped.
MaxWrapSize = 8192
roundCount = 6
ivPrefix = uint32(0xA65959A6)
)
// KWP is an implementation of an AES-KWP key wrapping cipher.
type KWP struct {
block cipher.Block
}
// NewKWP returns a KWP instance.
//
// The key argument should be the AES wrapping key, either 16 or 32 bytes
// to select AES-128 or AES-256.
func NewKWP(wrappingKey []byte) (*KWP, error) {
switch len(wrappingKey) {
default:
return nil, fmt.Errorf("kwp: invalid AES key size; want 16 or 32, got %d", len(wrappingKey))
case 16, 32:
block, err := aes.NewCipher(wrappingKey)
if err != nil {
return nil, fmt.Errorf("kwp: error building AES cipher: %v", err)
}
return &KWP{block: block}, nil
}
}
// wrappingSize computes the byte length of the ciphertext output for the
// provided plaintext input.
func wrappingSize(inputSize int) int {
paddingSize := 7 - (inputSize+7)%8
return inputSize + paddingSize + 8
}
// computeW computes the pseudorandom permutation W over the IV concatenated
// with zero-padded key material.
func (kwp *KWP) computeW(iv, key []byte) ([]byte, error) {
// Checks the parameter sizes for which W is defined.
// Note that the caller ensures stricter limits.
if len(key) <= 8 || len(key) > math.MaxInt32-16 || len(iv) != 8 {
return nil, fmt.Errorf("kwp: computeW called with invalid parameters")
}
data := make([]byte, wrappingSize(len(key)))
copy(data, iv)
copy(data[8:], key)
blockCount := len(data)/8 - 1
buf := make([]byte, 16)
copy(buf, data[:8])
for i := 0; i < roundCount; i++ {
for j := 0; j < blockCount; j++ {
copy(buf[8:], data[8*(j+1):])
kwp.block.Encrypt(buf, buf)
// xor the round constant in big endian order
// to the left half of the buffer
roundConst := uint(i*blockCount + j + 1)
for b := 0; b < 4; b++ {
buf[7-b] ^= byte(roundConst & 0xFF)
roundConst >>= 8
}
copy(data[8*(j+1):], buf[8:])
}
}
copy(data[:8], buf)
return data, nil
}
// invertW computes the inverse of the pseudorandom permutation W. Note that
// invertW does not perform an integrity check.
func (kwp *KWP) invertW(wrapped []byte) ([]byte, error) {
// Checks the input size for which invertW is defined.
// Note that the caller ensures stricter limits.
if len(wrapped) < 24 || len(wrapped)%8 != 0 {
return nil, fmt.Errorf("kwp: incorrect data size")
}
data := make([]byte, len(wrapped))
copy(data, wrapped)
blockCount := len(data)/8 - 1
buf := make([]byte, 16)
copy(buf, data[:8])
for i := roundCount - 1; i >= 0; i-- {
for j := blockCount - 1; j >= 0; j-- {
copy(buf[8:], data[8*(j+1):])
// xor the round constant in big endian order
// to the left half of the buffer
roundConst := uint(i*blockCount + j + 1)
for b := 0; b < 4; b++ {
buf[7-b] ^= byte(roundConst & 0xFF)
roundConst >>= 8
}
kwp.block.Decrypt(buf, buf)
copy(data[8*(j+1):], buf[8:])
}
}
copy(data, buf[:8])
return data, nil
}
// Wrap wraps the provided key material.
func (kwp *KWP) Wrap(data []byte) ([]byte, error) {
if len(data) < MinWrapSize {
return nil, fmt.Errorf("kwp: key size to wrap too small")
}
if len(data) > MaxWrapSize {
return nil, fmt.Errorf("kwp: key size to wrap too large")
}
iv := make([]byte, 8)
binary.BigEndian.PutUint32(iv, ivPrefix)
binary.BigEndian.PutUint32(iv[4:], uint32(len(data)))
return kwp.computeW(iv, data)
}
var errIntegrity = fmt.Errorf("kwp: unwrap failed integrity check")
// Unwrap unwraps a wrapped key.
func (kwp *KWP) Unwrap(data []byte) ([]byte, error) {
if len(data) < wrappingSize(MinWrapSize) {
return nil, fmt.Errorf("kwp: wrapped key size too small")
}
if len(data) > wrappingSize(MaxWrapSize) {
return nil, fmt.Errorf("kwp: wrapped key size too large")
}
if len(data)%8 != 0 {
return nil, fmt.Errorf("kwp: wrapped key size must be a multiple of 8 bytes")
}
unwrapped, err := kwp.invertW(data)
if err != nil {
return nil, err
}
// Check the IV and padding.
// W has been designed to be strong pseudorandom permutation, so
// leaking information about improperly padded keys would not be a
// vulnerability. This means we don't have to go to extra lengths to
// ensure that the integrity checks run in constant time.
if binary.BigEndian.Uint32(unwrapped) != ivPrefix {
return nil, errIntegrity
}
encodedSize := int(binary.BigEndian.Uint32(unwrapped[4:]))
if encodedSize < 0 || wrappingSize(encodedSize) != len(unwrapped) {
return nil, errIntegrity
}
for i := 8 + encodedSize; i < len(unwrapped); i++ {
if unwrapped[i] != 0 {
return nil, errIntegrity
}
}
return unwrapped[8 : 8+encodedSize], nil
}