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 }