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