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 }