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