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