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  }