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