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  }