github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/crypto/sse-kms.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 "context" 22 "encoding/base64" 23 "errors" 24 "net/http" 25 "path" 26 "strings" 27 28 jsoniter "github.com/json-iterator/go" 29 xhttp "github.com/minio/minio/internal/http" 30 "github.com/minio/minio/internal/kms" 31 "github.com/minio/minio/internal/logger" 32 ) 33 34 type ssekms struct{} 35 36 var ( 37 // S3KMS represents AWS SSE-KMS. It provides functionality to 38 // handle SSE-KMS requests. 39 S3KMS = ssekms{} 40 41 _ Type = S3KMS 42 ) 43 44 // String returns the SSE domain as string. For SSE-KMS the 45 // domain is "SSE-KMS". 46 func (ssekms) String() string { return "SSE-KMS" } 47 48 // IsRequested returns true if the HTTP headers contains 49 // at least one SSE-KMS header. 50 func (ssekms) IsRequested(h http.Header) bool { 51 if _, ok := h[xhttp.AmzServerSideEncryptionKmsID]; ok { 52 return true 53 } 54 if _, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok { 55 return true 56 } 57 if _, ok := h[xhttp.AmzServerSideEncryption]; ok { 58 // Return only true if the SSE header is specified and does not contain the SSE-S3 value 59 return strings.ToUpper(h.Get(xhttp.AmzServerSideEncryption)) != xhttp.AmzEncryptionAES 60 } 61 return false 62 } 63 64 // ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID 65 // and the KMS context on success. 66 func (ssekms) ParseHTTP(h http.Header) (string, kms.Context, error) { 67 if h == nil { 68 return "", nil, ErrInvalidEncryptionMethod 69 } 70 71 algorithm := h.Get(xhttp.AmzServerSideEncryption) 72 if algorithm != xhttp.AmzEncryptionKMS { 73 return "", nil, ErrInvalidEncryptionMethod 74 } 75 76 var ctx kms.Context 77 if context, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok { 78 b, err := base64.StdEncoding.DecodeString(context[0]) 79 if err != nil { 80 return "", nil, err 81 } 82 83 json := jsoniter.ConfigCompatibleWithStandardLibrary 84 if err := json.Unmarshal(b, &ctx); err != nil { 85 return "", nil, err 86 } 87 } 88 89 keyID := h.Get(xhttp.AmzServerSideEncryptionKmsID) 90 spaces := strings.HasPrefix(keyID, " ") || strings.HasSuffix(keyID, " ") 91 if spaces { 92 return "", nil, ErrInvalidEncryptionKeyID 93 } 94 return strings.TrimPrefix(keyID, ARNPrefix), ctx, nil 95 } 96 97 // IsEncrypted returns true if the object metadata indicates 98 // that the object was uploaded using SSE-KMS. 99 func (ssekms) IsEncrypted(metadata map[string]string) bool { 100 if _, ok := metadata[MetaSealedKeyKMS]; ok { 101 return true 102 } 103 return false 104 } 105 106 // UnsealObjectKey extracts and decrypts the sealed object key 107 // from the metadata using KMS and returns the decrypted object 108 // key. 109 func (s3 ssekms) UnsealObjectKey(k kms.KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { 110 if k == nil { 111 return key, Errorf("KMS not configured") 112 } 113 114 keyID, kmsKey, sealedKey, ctx, err := s3.ParseMetadata(metadata) 115 if err != nil { 116 return key, err 117 } 118 if ctx == nil { 119 ctx = kms.Context{bucket: path.Join(bucket, object)} 120 } else if _, ok := ctx[bucket]; !ok { 121 ctx[bucket] = path.Join(bucket, object) 122 } 123 unsealKey, err := k.DecryptKey(keyID, kmsKey, ctx) 124 if err != nil { 125 return key, err 126 } 127 err = key.Unseal(unsealKey, sealedKey, s3.String(), bucket, object) 128 return key, err 129 } 130 131 // CreateMetadata encodes the sealed object key into the metadata and returns 132 // the modified metadata. If the keyID and the kmsKey is not empty it encodes 133 // both into the metadata as well. It allocates a new metadata map if metadata 134 // is nil. 135 func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey, ctx kms.Context) map[string]string { 136 if sealedKey.Algorithm != SealAlgorithm { 137 logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm)) 138 } 139 140 // There are two possibilities: 141 // - We use a KMS -> There must be non-empty key ID and a KMS data key. 142 // - We use a K/V -> There must be no key ID and no KMS data key. 143 // Otherwise, the caller has passed an invalid argument combination. 144 if keyID == "" && len(kmsKey) != 0 { 145 logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present")) 146 } 147 if keyID != "" && len(kmsKey) == 0 { 148 logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present")) 149 } 150 151 if metadata == nil { 152 metadata = make(map[string]string, 5) 153 } 154 155 metadata[MetaAlgorithm] = sealedKey.Algorithm 156 metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) 157 metadata[MetaSealedKeyKMS] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) 158 if len(ctx) > 0 { 159 b, _ := ctx.MarshalText() 160 metadata[MetaContext] = base64.StdEncoding.EncodeToString(b) 161 } 162 if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key. 163 metadata[MetaKeyID] = keyID 164 metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey) 165 } 166 return metadata 167 } 168 169 // ParseMetadata extracts all SSE-KMS related values from the object metadata 170 // and checks whether they are well-formed. It returns the sealed object key 171 // on success. If the metadata contains both, a KMS master key ID and a sealed 172 // KMS data key it returns both. If the metadata does not contain neither a 173 // KMS master key ID nor a sealed KMS data key it returns an empty keyID and 174 // KMS data key. Otherwise, it returns an error. 175 func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, ctx kms.Context, err error) { 176 // Extract all required values from object metadata 177 b64IV, ok := metadata[MetaIV] 178 if !ok { 179 return keyID, kmsKey, sealedKey, ctx, errMissingInternalIV 180 } 181 algorithm, ok := metadata[MetaAlgorithm] 182 if !ok { 183 return keyID, kmsKey, sealedKey, ctx, errMissingInternalSealAlgorithm 184 } 185 b64SealedKey, ok := metadata[MetaSealedKeyKMS] 186 if !ok { 187 return keyID, kmsKey, sealedKey, ctx, Errorf("The object metadata is missing the internal sealed key for SSE-S3") 188 } 189 190 // There are two possibilities: 191 // - We use a KMS -> There must be a key ID and a KMS data key. 192 // - We use a K/V -> There must be no key ID and no KMS data key. 193 // Otherwise, the metadata is corrupted. 194 keyID, idPresent := metadata[MetaKeyID] 195 b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey] 196 if !idPresent && kmsKeyPresent { 197 return keyID, kmsKey, sealedKey, ctx, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3") 198 } 199 if idPresent && !kmsKeyPresent { 200 return keyID, kmsKey, sealedKey, ctx, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3") 201 } 202 203 // Check whether all extracted values are well-formed 204 iv, err := base64.StdEncoding.DecodeString(b64IV) 205 if err != nil || len(iv) != 32 { 206 return keyID, kmsKey, sealedKey, ctx, errInvalidInternalIV 207 } 208 if algorithm != SealAlgorithm { 209 return keyID, kmsKey, sealedKey, ctx, errInvalidInternalSealAlgorithm 210 } 211 encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey) 212 if err != nil || len(encryptedKey) != 64 { 213 return keyID, kmsKey, sealedKey, ctx, Errorf("The internal sealed key for SSE-KMS is invalid") 214 } 215 if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key. 216 kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey) 217 if err != nil { 218 return keyID, kmsKey, sealedKey, ctx, Errorf("The internal sealed KMS data key for SSE-KMS is invalid") 219 } 220 } 221 b64Ctx, ok := metadata[MetaContext] 222 if ok { 223 b, err := base64.StdEncoding.DecodeString(b64Ctx) 224 if err != nil { 225 return keyID, kmsKey, sealedKey, ctx, Errorf("The internal KMS context is not base64-encoded") 226 } 227 json := jsoniter.ConfigCompatibleWithStandardLibrary 228 if err = json.Unmarshal(b, &ctx); err != nil { 229 return keyID, kmsKey, sealedKey, ctx, Errorf("The internal sealed KMS context is invalid %w", err) 230 } 231 } 232 233 sealedKey.Algorithm = algorithm 234 copy(sealedKey.IV[:], iv) 235 copy(sealedKey.Key[:], encryptedKey) 236 return keyID, kmsKey, sealedKey, ctx, nil 237 }