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  }