k8s.io/apiserver@v0.31.1/pkg/storage/value/encrypt/aes/aes_extended_nonce.go (about) 1 /* 2 Copyright 2023 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 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/aes" 23 "crypto/sha256" 24 "errors" 25 "fmt" 26 "io" 27 "time" 28 29 "golang.org/x/crypto/hkdf" 30 31 "k8s.io/apiserver/pkg/storage/value" 32 "k8s.io/utils/clock" 33 ) 34 35 const ( 36 // cacheTTL is the TTL of KDF cache entries. We assume that the value.Context.AuthenticatedData 37 // for every call is the etcd storage path of the associated resource, and use that as the primary 38 // cache key (with a secondary check that confirms that the info matches). Thus if a client 39 // is constantly creating resources with new names (and thus new paths), they will keep adding new 40 // entries to the cache for up to this TTL before the GC logic starts deleting old entries. Each 41 // entry is ~300 bytes in size, so even a malicious client will be bounded in the overall memory 42 // it can consume. 43 cacheTTL = 10 * time.Minute 44 45 derivedKeySizeExtendedNonceGCM = commonSize 46 infoSizeExtendedNonceGCM 47 MinSeedSizeExtendedNonceGCM 48 ) 49 50 // NewHKDFExtendedNonceGCMTransformer is the same as NewGCMTransformer but trades storage, 51 // memory and CPU to work around the limitations of AES-GCM's 12 byte nonce size. The input seed 52 // is assumed to be a cryptographically strong slice of MinSeedSizeExtendedNonceGCM+ random bytes. 53 // Unlike NewGCMTransformer, this function is immune to the birthday attack because a new key is generated 54 // per encryption via a key derivation function: KDF(seed, random_bytes) -> key. The derived key is 55 // only used once as an AES-GCM key with a random 12 byte nonce. This avoids any concerns around 56 // cryptographic wear out (by either number of encryptions or the amount of data being encrypted). 57 // Speaking on the cryptographic safety, the limit on the number of operations that can be preformed 58 // with a single seed with derived keys and randomly generated nonces is not practically reachable. 59 // Thus, the scheme does not impose any specific requirements on the seed rotation schedule. 60 // Reusing the same seed is safe to do over time and across process restarts. Whenever a new 61 // seed is needed, the caller should generate it via GenerateKey(MinSeedSizeExtendedNonceGCM). 62 // In regard to KMSv2, organization standards or compliance policies around rotation may require 63 // that the seed be rotated at some interval. This can be implemented externally by rotating 64 // the key encryption key via a key ID change. 65 func NewHKDFExtendedNonceGCMTransformer(seed []byte) (value.Transformer, error) { 66 if seedLen := len(seed); seedLen < MinSeedSizeExtendedNonceGCM { 67 return nil, fmt.Errorf("invalid seed length %d used for key generation", seedLen) 68 } 69 return &extendedNonceGCM{ 70 seed: seed, 71 cache: newSimpleCache(clock.RealClock{}, cacheTTL), 72 }, nil 73 } 74 75 type extendedNonceGCM struct { 76 seed []byte 77 cache *simpleCache 78 } 79 80 func (e *extendedNonceGCM) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 81 if len(data) < infoSizeExtendedNonceGCM { 82 return nil, false, errors.New("the stored data was shorter than the required size") 83 } 84 85 info := data[:infoSizeExtendedNonceGCM] 86 87 transformer, err := e.derivedKeyTransformer(info, dataCtx, false) 88 if err != nil { 89 return nil, false, fmt.Errorf("failed to derive read key from KDF: %w", err) 90 } 91 92 return transformer.TransformFromStorage(ctx, data, dataCtx) 93 } 94 95 func (e *extendedNonceGCM) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 96 info := make([]byte, infoSizeExtendedNonceGCM) 97 if err := randomNonce(info); err != nil { 98 return nil, fmt.Errorf("failed to generate info for KDF: %w", err) 99 } 100 101 transformer, err := e.derivedKeyTransformer(info, dataCtx, true) 102 if err != nil { 103 return nil, fmt.Errorf("failed to derive write key from KDF: %w", err) 104 } 105 106 return transformer.TransformToStorage(ctx, data, dataCtx) 107 } 108 109 func (e *extendedNonceGCM) derivedKeyTransformer(info []byte, dataCtx value.Context, write bool) (value.Transformer, error) { 110 if !write { // no need to check cache on write since we always generate a new transformer 111 if transformer := e.cache.get(info, dataCtx); transformer != nil { 112 return transformer, nil 113 } 114 115 // on read, this is a subslice of a much larger slice and we do not want to hold onto that larger slice 116 info = bytes.Clone(info) 117 } 118 119 key, err := e.sha256KDFExpandOnly(info) 120 if err != nil { 121 return nil, fmt.Errorf("failed to KDF expand seed with info: %w", err) 122 } 123 124 transformer, err := newGCMTransformerWithInfo(key, info) 125 if err != nil { 126 return nil, fmt.Errorf("failed to build transformer with KDF derived key: %w", err) 127 } 128 129 e.cache.set(dataCtx, transformer) 130 131 return transformer, nil 132 } 133 134 func (e *extendedNonceGCM) sha256KDFExpandOnly(info []byte) ([]byte, error) { 135 kdf := hkdf.Expand(sha256.New, e.seed, info) 136 137 derivedKey := make([]byte, derivedKeySizeExtendedNonceGCM) 138 if _, err := io.ReadFull(kdf, derivedKey); err != nil { 139 return nil, fmt.Errorf("failed to read a derived key from KDF: %w", err) 140 } 141 142 return derivedKey, nil 143 } 144 145 func newGCMTransformerWithInfo(key, info []byte) (*transformerWithInfo, error) { 146 block, err := aes.NewCipher(key) 147 if err != nil { 148 return nil, err 149 } 150 151 transformer, err := NewGCMTransformer(block) 152 if err != nil { 153 return nil, err 154 } 155 156 return &transformerWithInfo{transformer: transformer, info: info}, nil 157 } 158 159 type transformerWithInfo struct { 160 transformer value.Transformer 161 // info are extra opaque bytes prepended to the writes from transformer and stripped from reads. 162 // currently info is used to generate a key via KDF(seed, info) -> key 163 // and transformer is the output of NewGCMTransformer(aes.NewCipher(key)) 164 info []byte 165 } 166 167 func (t *transformerWithInfo) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 168 if !bytes.HasPrefix(data, t.info) { 169 return nil, false, errors.New("the stored data is missing the required info prefix") 170 } 171 172 return t.transformer.TransformFromStorage(ctx, data[len(t.info):], dataCtx) 173 } 174 175 func (t *transformerWithInfo) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 176 out, err := t.transformer.TransformToStorage(ctx, data, dataCtx) 177 if err != nil { 178 return nil, err 179 } 180 181 outWithInfo := make([]byte, 0, len(out)+len(t.info)) 182 outWithInfo = append(outWithInfo, t.info...) 183 outWithInfo = append(outWithInfo, out...) 184 185 return outWithInfo, nil 186 }