github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/crypto/key.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package crypto
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"crypto/hmac"
    24  	"crypto/rand"
    25  	"encoding/binary"
    26  	"errors"
    27  	"io"
    28  	"path"
    29  
    30  	"github.com/minio/minio/internal/fips"
    31  	"github.com/minio/minio/internal/hash/sha256"
    32  	"github.com/minio/minio/internal/logger"
    33  	"github.com/minio/sio"
    34  )
    35  
    36  // ObjectKey is a 256 bit secret key used to encrypt the object.
    37  // It must never be stored in plaintext.
    38  type ObjectKey [32]byte
    39  
    40  // GenerateKey generates a unique ObjectKey from a 256 bit external key
    41  // and a source of randomness. If random is nil the default PRNG of the
    42  // system (crypto/rand) is used.
    43  func GenerateKey(extKey []byte, random io.Reader) (key ObjectKey) {
    44  	if random == nil {
    45  		random = rand.Reader
    46  	}
    47  	if len(extKey) != 32 { // safety check
    48  		logger.CriticalIf(context.Background(), errors.New("crypto: invalid key length"))
    49  	}
    50  	var nonce [32]byte
    51  	if _, err := io.ReadFull(random, nonce[:]); err != nil {
    52  		logger.CriticalIf(context.Background(), errOutOfEntropy)
    53  	}
    54  
    55  	const Context = "object-encryption-key generation"
    56  	mac := hmac.New(sha256.New, extKey)
    57  	mac.Write([]byte(Context))
    58  	mac.Write(nonce[:])
    59  	mac.Sum(key[:0])
    60  	return key
    61  }
    62  
    63  // GenerateIV generates a new random 256 bit IV from the provided source
    64  // of randomness. If random is nil the default PRNG of the system
    65  // (crypto/rand) is used.
    66  func GenerateIV(random io.Reader) (iv [32]byte) {
    67  	if random == nil {
    68  		random = rand.Reader
    69  	}
    70  	if _, err := io.ReadFull(random, iv[:]); err != nil {
    71  		logger.CriticalIf(context.Background(), errOutOfEntropy)
    72  	}
    73  	return iv
    74  }
    75  
    76  // SealedKey represents a sealed object key. It can be stored
    77  // at an untrusted location.
    78  type SealedKey struct {
    79  	Key       [64]byte // The encrypted and authenticated object-key.
    80  	IV        [32]byte // The random IV used to encrypt the object-key.
    81  	Algorithm string   // The sealing algorithm used to encrypt the object key.
    82  }
    83  
    84  // Seal encrypts the ObjectKey using the 256 bit external key and IV. The sealed
    85  // key is also cryptographically bound to the object's path (bucket/object) and the
    86  // domain (SSE-C or SSE-S3).
    87  func (key ObjectKey) Seal(extKey []byte, iv [32]byte, domain, bucket, object string) SealedKey {
    88  	if len(extKey) != 32 {
    89  		logger.CriticalIf(context.Background(), errors.New("crypto: invalid key length"))
    90  	}
    91  	var (
    92  		sealingKey   [32]byte
    93  		encryptedKey bytes.Buffer
    94  	)
    95  	mac := hmac.New(sha256.New, extKey)
    96  	mac.Write(iv[:])
    97  	mac.Write([]byte(domain))
    98  	mac.Write([]byte(SealAlgorithm))
    99  	mac.Write([]byte(path.Join(bucket, object))) // use path.Join for canonical 'bucket/object'
   100  	mac.Sum(sealingKey[:0])
   101  	if n, err := sio.Encrypt(&encryptedKey, bytes.NewReader(key[:]), sio.Config{Key: sealingKey[:], CipherSuites: fips.DARECiphers()}); n != 64 || err != nil {
   102  		logger.CriticalIf(context.Background(), errors.New("Unable to generate sealed key"))
   103  	}
   104  	sealedKey := SealedKey{
   105  		IV:        iv,
   106  		Algorithm: SealAlgorithm,
   107  	}
   108  	copy(sealedKey.Key[:], encryptedKey.Bytes())
   109  	return sealedKey
   110  }
   111  
   112  // Unseal decrypts a sealed key using the 256 bit external key. Since the sealed key
   113  // may be cryptographically bound to the object's path the same bucket/object as during sealing
   114  // must be provided. On success the ObjectKey contains the decrypted sealed key.
   115  func (key *ObjectKey) Unseal(extKey []byte, sealedKey SealedKey, domain, bucket, object string) error {
   116  	var unsealConfig sio.Config
   117  	switch sealedKey.Algorithm {
   118  	default:
   119  		return Errorf("The sealing algorithm '%s' is not supported", sealedKey.Algorithm)
   120  	case SealAlgorithm:
   121  		mac := hmac.New(sha256.New, extKey)
   122  		mac.Write(sealedKey.IV[:])
   123  		mac.Write([]byte(domain))
   124  		mac.Write([]byte(SealAlgorithm))
   125  		mac.Write([]byte(path.Join(bucket, object))) // use path.Join for canonical 'bucket/object'
   126  		unsealConfig = sio.Config{MinVersion: sio.Version20, Key: mac.Sum(nil), CipherSuites: fips.DARECiphers()}
   127  	case InsecureSealAlgorithm:
   128  		sha := sha256.New()
   129  		sha.Write(extKey)
   130  		sha.Write(sealedKey.IV[:])
   131  		unsealConfig = sio.Config{MinVersion: sio.Version10, Key: sha.Sum(nil), CipherSuites: fips.DARECiphers()}
   132  	}
   133  
   134  	if out, err := sio.DecryptBuffer(key[:0], sealedKey.Key[:], unsealConfig); len(out) != 32 || err != nil {
   135  		return ErrSecretKeyMismatch
   136  	}
   137  	return nil
   138  }
   139  
   140  // DerivePartKey derives an unique 256 bit key from an ObjectKey and the part index.
   141  func (key ObjectKey) DerivePartKey(id uint32) (partKey [32]byte) {
   142  	var bin [4]byte
   143  	binary.LittleEndian.PutUint32(bin[:], id)
   144  
   145  	mac := hmac.New(sha256.New, key[:])
   146  	mac.Write(bin[:])
   147  	mac.Sum(partKey[:0])
   148  	return partKey
   149  }
   150  
   151  // SealETag seals the etag using the object key.
   152  // It does not encrypt empty ETags because such ETags indicate
   153  // that the S3 client hasn't sent an ETag = MD5(object) and
   154  // the backend can pick an ETag value.
   155  func (key ObjectKey) SealETag(etag []byte) []byte {
   156  	if len(etag) == 0 { // don't encrypt empty ETag - only if client sent ETag = MD5(object)
   157  		return etag
   158  	}
   159  	var buffer bytes.Buffer
   160  	mac := hmac.New(sha256.New, key[:])
   161  	mac.Write([]byte("SSE-etag"))
   162  	if _, err := sio.Encrypt(&buffer, bytes.NewReader(etag), sio.Config{Key: mac.Sum(nil), CipherSuites: fips.DARECiphers()}); err != nil {
   163  		logger.CriticalIf(context.Background(), errors.New("Unable to encrypt ETag using object key"))
   164  	}
   165  	return buffer.Bytes()
   166  }
   167  
   168  // UnsealETag unseals the etag using the provided object key.
   169  // It does not try to decrypt the ETag if len(etag) == 16
   170  // because such ETags indicate that the S3 client hasn't sent
   171  // an ETag = MD5(object) and the backend has picked an ETag value.
   172  func (key ObjectKey) UnsealETag(etag []byte) ([]byte, error) {
   173  	if !IsETagSealed(etag) {
   174  		return etag, nil
   175  	}
   176  	mac := hmac.New(sha256.New, key[:])
   177  	mac.Write([]byte("SSE-etag"))
   178  	return sio.DecryptBuffer(make([]byte, 0, len(etag)), etag, sio.Config{Key: mac.Sum(nil), CipherSuites: fips.DARECiphers()})
   179  }