storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/crypto/key.go (about)

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