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