// 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 }