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

     1  /*
     2   * Minio Cloud Storage, (C) 2019-2020 Minio, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package crypto
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"errors"
    23  	"net/http"
    24  	"path"
    25  	"strings"
    26  
    27  	xhttp "storj.io/minio/cmd/http"
    28  	"storj.io/minio/cmd/logger"
    29  )
    30  
    31  type sses3 struct{}
    32  
    33  var (
    34  	// S3 represents AWS SSE-S3. It provides functionality to handle
    35  	// SSE-S3 requests.
    36  	S3 = sses3{}
    37  
    38  	_ Type = S3
    39  )
    40  
    41  // String returns the SSE domain as string. For SSE-S3 the
    42  // domain is "SSE-S3".
    43  func (sses3) String() string { return "SSE-S3" }
    44  
    45  func (sses3) IsRequested(h http.Header) bool {
    46  	_, ok := h[xhttp.AmzServerSideEncryption]
    47  	return ok && strings.ToLower(h.Get(xhttp.AmzServerSideEncryption)) != xhttp.AmzEncryptionKMS // Return only true if the SSE header is specified and does not contain the SSE-KMS value
    48  }
    49  
    50  // ParseHTTP parses the SSE-S3 related HTTP headers and checks
    51  // whether they contain valid values.
    52  func (sses3) ParseHTTP(h http.Header) error {
    53  	if h.Get(xhttp.AmzServerSideEncryption) != xhttp.AmzEncryptionAES {
    54  		return ErrInvalidEncryptionMethod
    55  	}
    56  	return nil
    57  }
    58  
    59  // IsEncrypted returns true if the object metadata indicates
    60  // that the object was uploaded using SSE-S3.
    61  func (sses3) IsEncrypted(metadata map[string]string) bool {
    62  	if _, ok := metadata[MetaSealedKeyS3]; ok {
    63  		return true
    64  	}
    65  	return false
    66  }
    67  
    68  // UnsealObjectKey extracts and decrypts the sealed object key
    69  // from the metadata using KMS and returns the decrypted object
    70  // key.
    71  func (s3 sses3) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
    72  	keyID, kmsKey, sealedKey, err := s3.ParseMetadata(metadata)
    73  	if err != nil {
    74  		return key, err
    75  	}
    76  	unsealKey, err := kms.DecryptKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)})
    77  	if err != nil {
    78  		return key, err
    79  	}
    80  	err = key.Unseal(unsealKey[:], sealedKey, s3.String(), bucket, object)
    81  	return key, err
    82  }
    83  
    84  // CreateMetadata encodes the sealed object key into the metadata and returns
    85  // the modified metadata. If the keyID and the kmsKey is not empty it encodes
    86  // both into the metadata as well. It allocates a new metadata map if metadata
    87  // is nil.
    88  func (sses3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string {
    89  	if sealedKey.Algorithm != SealAlgorithm {
    90  		logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
    91  	}
    92  
    93  	// There are two possibilites:
    94  	// - We use a KMS -> There must be non-empty key ID and a KMS data key.
    95  	// - We use a K/V -> There must be no key ID and no KMS data key.
    96  	// Otherwise, the caller has passed an invalid argument combination.
    97  	if keyID == "" && len(kmsKey) != 0 {
    98  		logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
    99  	}
   100  	if keyID != "" && len(kmsKey) == 0 {
   101  		logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
   102  	}
   103  
   104  	if metadata == nil {
   105  		metadata = make(map[string]string, 5)
   106  	}
   107  
   108  	metadata[MetaAlgorithm] = sealedKey.Algorithm
   109  	metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
   110  	metadata[MetaSealedKeyS3] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
   111  	if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
   112  		metadata[MetaKeyID] = keyID
   113  		metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey)
   114  	}
   115  	return metadata
   116  }
   117  
   118  // ParseMetadata extracts all SSE-S3 related values from the object metadata
   119  // and checks whether they are well-formed. It returns the sealed object key
   120  // on success. If the metadata contains both, a KMS master key ID and a sealed
   121  // KMS data key it returns both. If the metadata does not contain neither a
   122  // KMS master key ID nor a sealed KMS data key it returns an empty keyID and
   123  // KMS data key. Otherwise, it returns an error.
   124  func (sses3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) {
   125  	// Extract all required values from object metadata
   126  	b64IV, ok := metadata[MetaIV]
   127  	if !ok {
   128  		return keyID, kmsKey, sealedKey, errMissingInternalIV
   129  	}
   130  	algorithm, ok := metadata[MetaAlgorithm]
   131  	if !ok {
   132  		return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm
   133  	}
   134  	b64SealedKey, ok := metadata[MetaSealedKeyS3]
   135  	if !ok {
   136  		return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3")
   137  	}
   138  
   139  	// There are two possibilites:
   140  	// - We use a KMS -> There must be a key ID and a KMS data key.
   141  	// - We use a K/V -> There must be no key ID and no KMS data key.
   142  	// Otherwise, the metadata is corrupted.
   143  	keyID, idPresent := metadata[MetaKeyID]
   144  	b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey]
   145  	if !idPresent && kmsKeyPresent {
   146  		return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3")
   147  	}
   148  	if idPresent && !kmsKeyPresent {
   149  		return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3")
   150  	}
   151  
   152  	// Check whether all extracted values are well-formed
   153  	iv, err := base64.StdEncoding.DecodeString(b64IV)
   154  	if err != nil || len(iv) != 32 {
   155  		return keyID, kmsKey, sealedKey, errInvalidInternalIV
   156  	}
   157  	if algorithm != SealAlgorithm {
   158  		return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm
   159  	}
   160  	encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
   161  	if err != nil || len(encryptedKey) != 64 {
   162  		return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid")
   163  	}
   164  	if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
   165  		kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
   166  		if err != nil {
   167  			return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid")
   168  		}
   169  	}
   170  
   171  	sealedKey.Algorithm = algorithm
   172  	copy(sealedKey.IV[:], iv)
   173  	copy(sealedKey.Key[:], encryptedKey)
   174  	return keyID, kmsKey, sealedKey, nil
   175  }