k8s.io/apiserver@v0.31.1/pkg/storage/value/encrypt/aes/aes.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package aes transforms values for storage at rest using AES-GCM. 18 package aes 19 20 import ( 21 "bytes" 22 "context" 23 "crypto/aes" 24 "crypto/cipher" 25 "crypto/rand" 26 "encoding/binary" 27 "errors" 28 "fmt" 29 "io" 30 "sync/atomic" 31 "time" 32 33 "k8s.io/apiserver/pkg/storage/value" 34 "k8s.io/klog/v2" 35 ) 36 37 // commonSize is the length of various security sensitive byte slices such as encryption keys. 38 // Do not change this value. It would be a backward incompatible change. 39 const commonSize = 32 40 41 const keySizeCounterNonceGCM = commonSize 42 43 // NewGCMTransformerWithUniqueKeyUnsafe is the same as NewGCMTransformer but is unsafe for general 44 // use because it makes assumptions about the key underlying the block cipher. Specifically, 45 // it uses a 96-bit nonce where the first 32 bits are random data and the remaining 64 bits are 46 // a monotonically incrementing atomic counter. This means that the key must be randomly generated 47 // on process startup and must never be used for encryption outside the lifetime of the process. 48 // Unlike NewGCMTransformer, this function is immune to the birthday attack and thus the key can 49 // be used for 2^64-1 writes without rotation. Furthermore, cryptographic wear out of AES-GCM with 50 // a sequential nonce occurs after 2^64 encryptions, which is not a concern for our use cases. 51 // Even if that occurs, the nonce counter would overflow and crash the process. We have no concerns 52 // around plaintext length because all stored items are small (less than 2 MB). To prevent the 53 // chance of the block cipher being accidentally re-used, it is not taken in as input. Instead, 54 // a new random key is generated and returned on every invocation of this function. This key is 55 // used as the input to the block cipher. If the key is stored and retrieved at a later point, 56 // it can be passed to NewGCMTransformer(aes.NewCipher(key)) to construct a transformer capable 57 // of decrypting values encrypted by this transformer (that transformer must not be used for encryption). 58 func NewGCMTransformerWithUniqueKeyUnsafe() (value.Transformer, []byte, error) { 59 key, err := GenerateKey(keySizeCounterNonceGCM) 60 if err != nil { 61 return nil, nil, err 62 } 63 block, err := aes.NewCipher(key) 64 if err != nil { 65 return nil, nil, err 66 } 67 68 nonceGen := &nonceGenerator{ 69 // we start the nonce counter at one billion so that we are 70 // guaranteed to detect rollover across different go routines 71 zero: 1_000_000_000, 72 fatal: die, 73 } 74 nonceGen.nonce.Add(nonceGen.zero) 75 76 transformer, err := newGCMTransformerWithUniqueKeyUnsafe(block, nonceGen) 77 if err != nil { 78 return nil, nil, err 79 } 80 return transformer, key, nil 81 } 82 83 func newGCMTransformerWithUniqueKeyUnsafe(block cipher.Block, nonceGen *nonceGenerator) (value.Transformer, error) { 84 aead, err := newGCM(block) 85 if err != nil { 86 return nil, err 87 } 88 89 nonceFunc := func(b []byte) error { 90 // we only need 8 bytes to store our 64 bit incrementing nonce 91 // instead of leaving the unused bytes as zeros, set those to random bits 92 // this mostly protects us from weird edge cases like a VM restore that rewinds our atomic counter 93 randNonceSize := len(b) - 8 94 95 if err := randomNonce(b[:randNonceSize]); err != nil { 96 return err 97 } 98 99 nonceGen.next(b[randNonceSize:]) 100 101 return nil 102 } 103 104 return &gcm{aead: aead, nonceFunc: nonceFunc}, nil 105 } 106 107 func randomNonce(b []byte) error { 108 _, err := rand.Read(b) 109 return err 110 } 111 112 type nonceGenerator struct { 113 // even at one million encryptions per second, this counter is enough for half a million years 114 // using this struct avoids alignment bugs: https://pkg.go.dev/sync/atomic#pkg-note-BUG 115 nonce atomic.Uint64 116 zero uint64 117 fatal func(msg string) 118 } 119 120 func (n *nonceGenerator) next(b []byte) { 121 incrementingNonce := n.nonce.Add(1) 122 if incrementingNonce <= n.zero { 123 // this should never happen, and is unrecoverable if it does 124 n.fatal("aes-gcm detected nonce overflow - cryptographic wear out has occurred") 125 } 126 binary.LittleEndian.PutUint64(b, incrementingNonce) 127 } 128 129 func die(msg string) { 130 // nolint:logcheck // we want the stack traces, log flushing, and process exiting logic from FatalDepth 131 klog.FatalDepth(1, msg) 132 } 133 134 // GenerateKey generates a random key using system randomness. 135 func GenerateKey(length int) (key []byte, err error) { 136 defer func(start time.Time) { 137 value.RecordDataKeyGeneration(start, err) 138 }(time.Now()) 139 key = make([]byte, length) 140 if _, err = rand.Read(key); err != nil { 141 return nil, err 142 } 143 144 return key, nil 145 } 146 147 // NewGCMTransformer takes the given block cipher and performs encryption and decryption on the given data. 148 // It implements AEAD encryption of the provided values given a cipher.Block algorithm. 149 // The authenticated data provided as part of the value.Context method must match when the same 150 // value is set to and loaded from storage. In order to ensure that values cannot be copied by 151 // an attacker from a location under their control, use characteristics of the storage location 152 // (such as the etcd key) as part of the authenticated data. 153 // 154 // Because this mode requires a generated IV and IV reuse is a known weakness of AES-GCM, keys 155 // must be rotated before a birthday attack becomes feasible. NIST SP 800-38D 156 // (http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf) recommends using the same 157 // key with random 96-bit nonces (the default nonce length) no more than 2^32 times, and 158 // therefore transformers using this implementation *must* ensure they allow for frequent key 159 // rotation. Future work should include investigation of AES-GCM-SIV as an alternative to 160 // random nonces. 161 func NewGCMTransformer(block cipher.Block) (value.Transformer, error) { 162 aead, err := newGCM(block) 163 if err != nil { 164 return nil, err 165 } 166 167 return &gcm{aead: aead, nonceFunc: randomNonce}, nil 168 } 169 170 func newGCM(block cipher.Block) (cipher.AEAD, error) { 171 aead, err := cipher.NewGCM(block) 172 if err != nil { 173 return nil, err 174 } 175 if nonceSize := aead.NonceSize(); nonceSize != 12 { // all data in etcd will be broken if this ever changes 176 return nil, fmt.Errorf("crypto/cipher.NewGCM returned unexpected nonce size: %d", nonceSize) 177 } 178 return aead, nil 179 } 180 181 type gcm struct { 182 aead cipher.AEAD 183 nonceFunc func([]byte) error 184 } 185 186 func (t *gcm) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 187 nonceSize := t.aead.NonceSize() 188 if len(data) < nonceSize { 189 return nil, false, errors.New("the stored data was shorter than the required size") 190 } 191 result, err := t.aead.Open(nil, data[:nonceSize], data[nonceSize:], dataCtx.AuthenticatedData()) 192 return result, false, err 193 } 194 195 func (t *gcm) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 196 nonceSize := t.aead.NonceSize() 197 result := make([]byte, nonceSize+t.aead.Overhead()+len(data)) 198 199 if err := t.nonceFunc(result[:nonceSize]); err != nil { 200 return nil, fmt.Errorf("failed to write nonce for AES-GCM: %w", err) 201 } 202 203 cipherText := t.aead.Seal(result[nonceSize:nonceSize], result[:nonceSize], data, dataCtx.AuthenticatedData()) 204 return result[:nonceSize+len(cipherText)], nil 205 } 206 207 // cbc implements encryption at rest of the provided values given a cipher.Block algorithm. 208 type cbc struct { 209 block cipher.Block 210 } 211 212 // NewCBCTransformer takes the given block cipher and performs encryption and decryption on the given 213 // data. 214 func NewCBCTransformer(block cipher.Block) value.Transformer { 215 return &cbc{block: block} 216 } 217 218 var ( 219 errInvalidBlockSize = errors.New("the stored data is not a multiple of the block size") 220 errInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)") 221 errInvalidPKCS7Padding = errors.New("invalid padding on input") 222 ) 223 224 func (t *cbc) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 225 blockSize := aes.BlockSize 226 if len(data) < blockSize { 227 return nil, false, errors.New("the stored data was shorter than the required size") 228 } 229 iv := data[:blockSize] 230 data = data[blockSize:] 231 232 if len(data)%blockSize != 0 { 233 return nil, false, errInvalidBlockSize 234 } 235 236 result := make([]byte, len(data)) 237 copy(result, data) 238 mode := cipher.NewCBCDecrypter(t.block, iv) 239 mode.CryptBlocks(result, result) 240 241 // remove and verify PKCS#7 padding for CBC 242 c := result[len(result)-1] 243 paddingSize := int(c) 244 size := len(result) - paddingSize 245 if paddingSize == 0 || paddingSize > len(result) { 246 return nil, false, errInvalidPKCS7Data 247 } 248 for i := 0; i < paddingSize; i++ { 249 if result[size+i] != c { 250 return nil, false, errInvalidPKCS7Padding 251 } 252 } 253 254 return result[:size], false, nil 255 } 256 257 func (t *cbc) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 258 blockSize := aes.BlockSize 259 paddingSize := blockSize - (len(data) % blockSize) 260 result := make([]byte, blockSize+len(data)+paddingSize) 261 iv := result[:blockSize] 262 if _, err := io.ReadFull(rand.Reader, iv); err != nil { 263 return nil, errors.New("unable to read sufficient random bytes") 264 } 265 copy(result[blockSize:], data) 266 267 // add PKCS#7 padding for CBC 268 copy(result[blockSize+len(data):], bytes.Repeat([]byte{byte(paddingSize)}, paddingSize)) 269 270 mode := cipher.NewCBCEncrypter(t.block, iv) 271 mode.CryptBlocks(result[blockSize:], result[blockSize:]) 272 return result, nil 273 }