From e34369c860f232eeebba7a90edd887fe580c0784 Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Thu, 19 Sep 2019 00:38:09 +0200 Subject: [PATCH] prepare SSE-S3 metadata parsing for K/V data key store (#8259) This commit allows the MinIO server to parse the metadata if: - either the `X-Minio-Internal-Server-Side-Encryption-S3-Key-Id` and the `X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key` entries are present. - or *both* headers are not present. This is in service to support a K/V data key store. --- cmd/crypto/metadata.go | 54 +++++++++++++++++++++++++++---------- cmd/crypto/metadata_test.go | 6 +++-- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/cmd/crypto/metadata.go b/cmd/crypto/metadata.go index 1a3f5f05a..9da9b6774 100644 --- a/cmd/crypto/metadata.go +++ b/cmd/crypto/metadata.go @@ -17,6 +17,7 @@ package crypto import ( "context" "encoding/base64" + "errors" "fmt" "github.com/minio/minio/cmd/logger" @@ -119,28 +120,46 @@ func CreateMultipartMetadata(metadata map[string]string) map[string]string { return metadata } -// CreateMetadata encodes the keyID, the sealed kms data key and the sealed key -// into the metadata and returns the modified metadata. It allocates a new -// metadata map if metadata is nil. +// CreateMetadata encodes the sealed object key into the metadata and returns +// the modified metadata. If the keyID and the kmsKey is not empty it encodes +// both into the metadata as well. It allocates a new metadata map if metadata +// is nil. func (s3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string { if sealedKey.Algorithm != SealAlgorithm { logger.CriticalIf(context.Background(), fmt.Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm)) } + // There are two possibilites: + // - We use a KMS -> There must be non-empty key ID and a KMS data key. + // - We use a K/V -> There must be no key ID and no KMS data key. + // Otherwise, the caller has passed an invalid argument combination. + if keyID == "" && len(kmsKey) != 0 { + logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present")) + } + if keyID != "" && len(kmsKey) == 0 { + logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present")) + } + if metadata == nil { metadata = map[string]string{} } - metadata[S3KMSKeyID] = keyID + metadata[SSESealAlgorithm] = sealedKey.Algorithm metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) metadata[S3SealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) - metadata[S3KMSSealedKey] = base64.StdEncoding.EncodeToString(kmsKey) + if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key. + metadata[S3KMSKeyID] = keyID + metadata[S3KMSSealedKey] = base64.StdEncoding.EncodeToString(kmsKey) + } return metadata } // ParseMetadata extracts all SSE-S3 related values from the object metadata -// and checks whether they are well-formed. It returns the KMS key-ID, the -// sealed KMS key and the sealed object key on success. +// and checks whether they are well-formed. It returns the sealed object key +// on success. If the metadata contains both, a KMS master key ID and a sealed +// KMS data key it returns both. If the metadata does not contain neither a +// KMS master key ID nor a sealed KMS data key it returns an empty keyID and +// KMS data key. Otherwise, it returns an error. func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) { // Extract all required values from object metadata b64IV, ok := metadata[SSEIV] @@ -155,12 +174,17 @@ func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte if !ok { return keyID, kmsKey, sealedKey, Error{"The object metadata is missing the internal sealed key for SSE-S3"} } - keyID, ok = metadata[S3KMSKeyID] - if !ok { + + // There are two possibilites: + // - We use a KMS -> There must be a key ID and a KMS data key. + // - We use a K/V -> There must be no key ID and no KMS data key. + // Otherwise, the metadata is corrupted. + keyID, idPresent := metadata[S3KMSKeyID] + b64KMSSealedKey, kmsKeyPresent := metadata[S3KMSSealedKey] + if !idPresent && kmsKeyPresent { return keyID, kmsKey, sealedKey, Error{"The object metadata is missing the internal KMS key-ID for SSE-S3"} } - b64KMSSealedKey, ok := metadata[S3KMSSealedKey] - if !ok { + if idPresent && !kmsKeyPresent { return keyID, kmsKey, sealedKey, Error{"The object metadata is missing the internal sealed KMS data key for SSE-S3"} } @@ -176,9 +200,11 @@ func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte if err != nil || len(encryptedKey) != 64 { return keyID, kmsKey, sealedKey, Error{"The internal sealed key for SSE-S3 is invalid"} } - kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey) - if err != nil { - return keyID, kmsKey, sealedKey, Error{"The internal sealed KMS data key for SSE-S3 is invalid"} + if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key. + kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey) + if err != nil { + return keyID, kmsKey, sealedKey, Error{"The internal sealed KMS data key for SSE-S3 is invalid"} + } } sealedKey.Algorithm = algorithm diff --git a/cmd/crypto/metadata_test.go b/cmd/crypto/metadata_test.go index 2e28d6086..7141f17c6 100644 --- a/cmd/crypto/metadata_test.go +++ b/cmd/crypto/metadata_test.go @@ -130,7 +130,7 @@ var s3ParseMetadataTests = []struct { }, // 2 { ExpectedErr: Error{"The object metadata is missing the internal KMS key-ID for SSE-S3"}, - Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, + Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSSealedKey: "IAAF0b=="}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, }, // 3 { ExpectedErr: Error{"The object metadata is missing the internal sealed KMS data key for SSE-S3"}, @@ -287,7 +287,9 @@ var s3CreateMetadataTests = []struct { SealedDataKey []byte SealedKey SealedKey }{ - {KeyID: "", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}}, + + {KeyID: "", SealedDataKey: nil, SealedKey: SealedKey{Algorithm: SealAlgorithm}}, + {KeyID: "my-minio-key", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}}, {KeyID: "cafebabe", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}}, {KeyID: "deadbeef", SealedDataKey: make([]byte, 32), SealedKey: SealedKey{IV: [32]byte{0xf7}, Key: [64]byte{0xea}, Algorithm: SealAlgorithm}}, }