From 28e25eac789643d665a32bd8b7458d38e3f1c93e Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Sat, 13 Oct 2018 03:06:38 +0200 Subject: [PATCH] crypto: add helper functions for unsealing object keys (#6609) This commit adds 3 helper functions for SSE-C and SSE-S3 to simplify object key unsealing in server code. See #6600 --- cmd/crypto/sse.go | 51 +++++++++++ cmd/crypto/sse_test.go | 197 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 247 insertions(+), 1 deletion(-) diff --git a/cmd/crypto/sse.go b/cmd/crypto/sse.go index 44415e2cc..a94ab50cc 100644 --- a/cmd/crypto/sse.go +++ b/cmd/crypto/sse.go @@ -18,6 +18,8 @@ import ( "context" "errors" "io" + "net/http" + "path" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/ioutil" @@ -69,10 +71,59 @@ const ( // domain is "SSE-S3". func (s3) String() string { return "SSE-S3" } +// UnsealObjectKey extracts and decrypts the sealed object key +// from the metadata using KMS and returns the decrypted object +// key. +func (sse s3) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { + keyID, kmsKey, sealedKey, err := sse.ParseMetadata(metadata) + if err != nil { + return + } + unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)}) + if err != nil { + return + } + err = key.Unseal(unsealKey, sealedKey, sse.String(), bucket, object) + return +} + // String returns the SSE domain as string. For SSE-C the // domain is "SSE-C". func (ssec) String() string { return "SSE-C" } +// UnsealObjectKey extracts and decrypts the sealed object key +// from the metadata using the SSE-C client key of the HTTP headers +// and returns the decrypted object key. +func (sse ssec) UnsealObjectKey(h http.Header, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { + clientKey, err := sse.ParseHTTP(h) + if err != nil { + return + } + return unsealObjectKey(clientKey, metadata, bucket, object) +} + +// UnsealObjectKey extracts and decrypts the sealed object key +// from the metadata using the SSE-Copy client key of the HTTP headers +// and returns the decrypted object key. +func (sse ssecCopy) UnsealObjectKey(h http.Header, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { + clientKey, err := sse.ParseHTTP(h) + if err != nil { + return + } + return unsealObjectKey(clientKey, metadata, bucket, object) +} + +// unsealObjectKey decrypts and returns the sealed object key +// from the metadata using the SSE-C client key. +func unsealObjectKey(clientKey [32]byte, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { + sealedKey, err := SSEC.ParseMetadata(metadata) + if err != nil { + return + } + err = key.Unseal(clientKey, sealedKey, SSEC.String(), bucket, object) + return +} + // EncryptSinglePart encrypts an io.Reader which must be the // the body of a single-part PUT request. func EncryptSinglePart(r io.Reader, key ObjectKey) io.Reader { diff --git a/cmd/crypto/sse_test.go b/cmd/crypto/sse_test.go index 2d1ccb64a..72e45b729 100644 --- a/cmd/crypto/sse_test.go +++ b/cmd/crypto/sse_test.go @@ -14,7 +14,10 @@ package crypto -import "testing" +import ( + "net/http" + "testing" +) func TestS3String(t *testing.T) { const Domain = "SSE-S3" @@ -29,3 +32,195 @@ func TestSSECString(t *testing.T) { t.Errorf("SSEC's string method returns wrong domain: got '%s' - want '%s'", domain, Domain) } } + +var ssecUnsealObjectKeyTests = []struct { + Headers http.Header + Bucket, Object string + Metadata map[string]string + + ExpectedErr error +}{ + { // 0 - Valid HTTP headers and valid metadata entries for bucket/object + Headers: http.Header{ + "X-Amz-Server-Side-Encryption-Customer-Algorithm": []string{"AES256"}, + "X-Amz-Server-Side-Encryption-Customer-Key": []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + "X-Amz-Server-Side-Encryption-Customer-Key-Md5": []string{"7PpPLAK26ONlVUGOWlusfg=="}, + }, + Bucket: "bucket", + Object: "object", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "IAAfAMBdYor5tf/UlVaQvwYlw5yKbPBeQqfygqsfHqhu1wHD9KDAP4bw38AhL12prFTS23JbbR9Re5Qv26ZnlQ==", + "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "DAREv2-HMAC-SHA256", + "X-Minio-Internal-Server-Side-Encryption-Iv": "coVfGS3I/CTrqexX5vUN+PQPoP9aUFiPYYrSzqTWfBA=", + }, + ExpectedErr: nil, + }, + { // 1 - Valid HTTP headers but invalid metadata entries for bucket/object2 + Headers: http.Header{ + "X-Amz-Server-Side-Encryption-Customer-Algorithm": []string{"AES256"}, + "X-Amz-Server-Side-Encryption-Customer-Key": []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + "X-Amz-Server-Side-Encryption-Customer-Key-Md5": []string{"7PpPLAK26ONlVUGOWlusfg=="}, + }, + Bucket: "bucket", + Object: "object2", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "IAAfAMBdYor5tf/UlVaQvwYlw5yKbPBeQqfygqsfHqhu1wHD9KDAP4bw38AhL12prFTS23JbbR9Re5Qv26ZnlQ==", + "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "DAREv2-HMAC-SHA256", + "X-Minio-Internal-Server-Side-Encryption-Iv": "coVfGS3I/CTrqexX5vUN+PQPoP9aUFiPYYrSzqTWfBA=", + }, + ExpectedErr: ErrSecretKeyMismatch, + }, + { // 2 - Valid HTTP headers but invalid metadata entries for bucket/object + Headers: http.Header{ + "X-Amz-Server-Side-Encryption-Customer-Algorithm": []string{"AES256"}, + "X-Amz-Server-Side-Encryption-Customer-Key": []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + "X-Amz-Server-Side-Encryption-Customer-Key-Md5": []string{"7PpPLAK26ONlVUGOWlusfg=="}, + }, + Bucket: "bucket", + Object: "object", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "IAAfAMBdYor5tf/UlVaQvwYlw5yKbPBeQqfygqsfHqhu1wHD9KDAP4bw38AhL12prFTS23JbbR9Re5Qv26ZnlQ==", + "X-Minio-Internal-Server-Side-Encryption-Iv": "coVfGS3I/CTrqexX5vUN+PQPoP9aUFiPYYrSzqTWfBA=", + }, + ExpectedErr: errMissingInternalSealAlgorithm, + }, + { // 3 - Invalid HTTP headers for valid metadata entries for bucket/object + Headers: http.Header{ + "X-Amz-Server-Side-Encryption-Customer-Algorithm": []string{"AES256"}, + "X-Amz-Server-Side-Encryption-Customer-Key": []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + }, + Bucket: "bucket", + Object: "object", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "IAAfAMBdYor5tf/UlVaQvwYlw5yKbPBeQqfygqsfHqhu1wHD9KDAP4bw38AhL12prFTS23JbbR9Re5Qv26ZnlQ==", + "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "DAREv2-HMAC-SHA256", + "X-Minio-Internal-Server-Side-Encryption-Iv": "coVfGS3I/CTrqexX5vUN+PQPoP9aUFiPYYrSzqTWfBA=", + }, + ExpectedErr: ErrMissingCustomerKeyMD5, + }, +} + +func TestSSECUnsealObjectKey(t *testing.T) { + for i, test := range ssecUnsealObjectKeyTests { + if _, err := SSEC.UnsealObjectKey(test.Headers, test.Metadata, test.Bucket, test.Object); err != test.ExpectedErr { + t.Errorf("Test %d: got: %v - want: %v", i, err, test.ExpectedErr) + } + } +} + +var sseCopyUnsealObjectKeyTests = []struct { + Headers http.Header + Bucket, Object string + Metadata map[string]string + + ExpectedErr error +}{ + { // 0 - Valid HTTP headers and valid metadata entries for bucket/object + Headers: http.Header{ + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": []string{"AES256"}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": []string{"7PpPLAK26ONlVUGOWlusfg=="}, + }, + Bucket: "bucket", + Object: "object", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "IAAfAMBdYor5tf/UlVaQvwYlw5yKbPBeQqfygqsfHqhu1wHD9KDAP4bw38AhL12prFTS23JbbR9Re5Qv26ZnlQ==", + "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "DAREv2-HMAC-SHA256", + "X-Minio-Internal-Server-Side-Encryption-Iv": "coVfGS3I/CTrqexX5vUN+PQPoP9aUFiPYYrSzqTWfBA=", + }, + ExpectedErr: nil, + }, + { // 1 - Valid HTTP headers but invalid metadata entries for bucket/object2 + Headers: http.Header{ + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": []string{"AES256"}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": []string{"7PpPLAK26ONlVUGOWlusfg=="}, + }, + Bucket: "bucket", + Object: "object2", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "IAAfAMBdYor5tf/UlVaQvwYlw5yKbPBeQqfygqsfHqhu1wHD9KDAP4bw38AhL12prFTS23JbbR9Re5Qv26ZnlQ==", + "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "DAREv2-HMAC-SHA256", + "X-Minio-Internal-Server-Side-Encryption-Iv": "coVfGS3I/CTrqexX5vUN+PQPoP9aUFiPYYrSzqTWfBA=", + }, + ExpectedErr: ErrSecretKeyMismatch, + }, + { // 2 - Valid HTTP headers but invalid metadata entries for bucket/object + Headers: http.Header{ + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": []string{"AES256"}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": []string{"7PpPLAK26ONlVUGOWlusfg=="}, + }, + Bucket: "bucket", + Object: "object", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "IAAfAMBdYor5tf/UlVaQvwYlw5yKbPBeQqfygqsfHqhu1wHD9KDAP4bw38AhL12prFTS23JbbR9Re5Qv26ZnlQ==", + "X-Minio-Internal-Server-Side-Encryption-Iv": "coVfGS3I/CTrqexX5vUN+PQPoP9aUFiPYYrSzqTWfBA=", + }, + ExpectedErr: errMissingInternalSealAlgorithm, + }, + { // 3 - Invalid HTTP headers for valid metadata entries for bucket/object + Headers: http.Header{ + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": []string{"AES256"}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + }, + Bucket: "bucket", + Object: "object", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "IAAfAMBdYor5tf/UlVaQvwYlw5yKbPBeQqfygqsfHqhu1wHD9KDAP4bw38AhL12prFTS23JbbR9Re5Qv26ZnlQ==", + "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "DAREv2-HMAC-SHA256", + "X-Minio-Internal-Server-Side-Encryption-Iv": "coVfGS3I/CTrqexX5vUN+PQPoP9aUFiPYYrSzqTWfBA=", + }, + ExpectedErr: ErrMissingCustomerKeyMD5, + }, +} + +func TestSSECopyUnsealObjectKey(t *testing.T) { + for i, test := range sseCopyUnsealObjectKeyTests { + if _, err := SSECopy.UnsealObjectKey(test.Headers, test.Metadata, test.Bucket, test.Object); err != test.ExpectedErr { + t.Errorf("Test %d: got: %v - want: %v", i, err, test.ExpectedErr) + } + } +} + +var s3UnsealObjectKeyTests = []struct { + KMS KMS + Bucket, Object string + Metadata map[string]string + + ExpectedErr error +}{ + { // 0 - Valid KMS key-ID and valid metadata entries for bucket/object + KMS: NewKMS([32]byte{}), + Bucket: "bucket", + Object: "object", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Iv": "hhVY0LKR1YtZbzAKxTWUfZt5enDfYX6Fxz1ma8Kiudc=", + "X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key": "IAAfALhsOeD5AE3s5Zgq3DZ5VFGsOa3B0ksVC86veDcaj+fXv2U0VadhPaOKYr9Emd5ssOsO0uIhIIrKiOy9rA==", + "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key": "IAAfAMRS2iw45FsfiF3QXajSYVWj1lxMpQm6DxDGPtADCX6fJQQ4atHBtfpgqJFyeQmIHsm0FBI+UlHw1Lv4ug==", + "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id": "test-key-1", + "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "DAREv2-HMAC-SHA256", + }, + ExpectedErr: nil, + }, + { // 1 - Valid KMS key-ID for invalid metadata entries for bucket/object + KMS: NewKMS([32]byte{}), + Bucket: "bucket", + Object: "object", + Metadata: map[string]string{ + "X-Minio-Internal-Server-Side-Encryption-Iv": "hhVY0LKR1YtZbzAKxTWUfZt5enDfYX6Fxz1ma8Kiudc=", + "X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key": "IAAfALhsOeD5AE3s5Zgq3DZ5VFGsOa3B0ksVC86veDcaj+fXv2U0VadhPaOKYr9Emd5ssOsO0uIhIIrKiOy9rA==", + "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key": "IAAfAMRS2iw45FsfiF3QXajSYVWj1lxMpQm6DxDGPtADCX6fJQQ4atHBtfpgqJFyeQmIHsm0FBI+UlHw1Lv4ug==", + "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id": "test-key-1", + }, + ExpectedErr: errMissingInternalSealAlgorithm, + }, +} + +func TestS3UnsealObjectKey(t *testing.T) { + for i, test := range s3UnsealObjectKeyTests { + if _, err := S3.UnsealObjectKey(test.KMS, test.Metadata, test.Bucket, test.Object); err != test.ExpectedErr { + t.Errorf("Test %d: got: %v - want: %v", i, err, test.ExpectedErr) + } + } +}