From b181a693fb0b3971cf68137b643271f235af5bbe Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Tue, 10 Jul 2018 02:18:28 +0200 Subject: [PATCH] fix object rebinding SSE-C security guarantee violation (#6121) This commit fixes a weakness of the key-encryption-key derivation for SSE-C encrypted objects. Before this change the key-encryption-key was not bound to / didn't depend on the object path. This allows an attacker to repalce objects - encrypted with the same client-key - with each other. This change fixes this issue by updating the key-encryption-key derivation to include: - the domain (in this case SSE-C) - a canonical object path representation - the encryption & key derivation algorithm Changing the object path now causes the KDF to derive a different key-encryption-key such that the object-key unsealing fails. Including the domain (SSE-C) and encryption & key derivation algorithm is not directly neccessary for this fix. However, both will be included for the SSE-S3 KDF. So they are included here to avoid updating the KDF again when we add SSE-S3. The leagcy KDF 'DARE-SHA256' is only used for existing objects and never for new objects / key rotation. --- cmd/bucket-handlers.go | 2 +- cmd/crypto/doc.go | 12 +-- cmd/encryption-v1.go | 184 +++++++++++++++++++++++--------------- cmd/encryption-v1_test.go | 54 +++++++++-- cmd/object-handlers.go | 22 ++--- 5 files changed, 178 insertions(+), 96 deletions(-) diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 48f50f14c..0434bf1e3 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -623,7 +623,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } - reader, err = newEncryptReader(hashReader, key, metadata) + reader, err = newEncryptReader(hashReader, key, bucket, object, metadata) if err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return diff --git a/cmd/crypto/doc.go b/cmd/crypto/doc.go index a938f911a..ec0efb613 100644 --- a/cmd/crypto/doc.go +++ b/cmd/crypto/doc.go @@ -32,7 +32,7 @@ // Input: ClientKey, bucket, object, metadata, object_data // - IV := Random({0,1}²⁵⁶) // - ObjectKey := SHA256(ClientKey || Random({0,1}²⁵⁶)) -// - KeyEncKey := HMAC-SHA256(ClientKey, IV || bucket || object) +// - KeyEncKey := HMAC-SHA256(ClientKey, IV || 'SSE-C' || 'DAREv2-HMAC-SHA256' || bucket || '/' || object) // - SealedKey := DAREv2_Enc(KeyEncKey, ObjectKey) // - enc_object_data := DAREv2_Enc(ObjectKey, object_data) // - metadata <- IV @@ -43,7 +43,7 @@ // Input: ClientKey, bucket, object, metadata, enc_object_data // - IV <- metadata // - SealedKey <- metadata -// - KeyEncKey := HMAC-SHA256(ClientKey, IV || bucket || object) +// - KeyEncKey := HMAC-SHA256(ClientKey, IV || 'SSE-C' || 'DAREv2-HMAC-SHA256' || bucket || '/' || object) // - ObjectKey := DAREv2_Dec(KeyEncKey, SealedKey) // - object_data := DAREv2_Dec(ObjectKey, enc_object_data) // Output: object_data @@ -64,7 +64,7 @@ // Input: MasterKey, bucket, object, metadata, object_data // - IV := Random({0,1}²⁵⁶) // - ObjectKey := SHA256(MasterKey || Random({0,1}²⁵⁶)) -// - KeyEncKey := HMAC-SHA256(MasterKey, IV || bucket || object) +// - KeyEncKey := HMAC-SHA256(MasterKey, IV || 'SSE-S3' || 'DAREv2-HMAC-SHA256' || bucket || '/' || object) // - SealedKey := DAREv2_Enc(KeyEncKey, ObjectKey) // - enc_object_data := DAREv2_Enc(ObjectKey, object_data) // - metadata <- IV @@ -75,7 +75,7 @@ // Input: MasterKey, bucket, object, metadata, enc_object_data // - IV <- metadata // - SealedKey <- metadata -// - KeyEncKey := HMAC-SHA256(MasterKey, IV || bucket || object) +// - KeyEncKey := HMAC-SHA256(MasterKey, IV || 'SSE-S3' || 'DAREv2-HMAC-SHA256' || bucket || '/' || object) // - ObjectKey := DAREv2_Dec(KeyEncKey, SealedKey) // - object_data := DAREv2_Dec(ObjectKey, enc_object_data) // Output: object_data @@ -92,7 +92,7 @@ // - Key, EncKey := Generate(KeyID) // - IV := Random({0,1}²⁵⁶) // - ObjectKey := SHA256(Key, Random({0,1}²⁵⁶)) -// - KeyEncKey := HMAC-SHA256(Key, IV || bucket || object) +// - KeyEncKey := HMAC-SHA256(Key, IV || 'SSE-S3' || 'DAREv2-HMAC-SHA256' || bucket || '/' || object) // - SealedKey := DAREv2_Enc(KeyEncKey, ObjectKey) // - enc_object_data := DAREv2_Enc(ObjectKey, object_data) // - metadata <- IV @@ -108,7 +108,7 @@ // - IV <- metadata // - SealedKey <- metadata // - Key := Unseal(KeyID, EncKey) -// - KeyEncKey := HMAC-SHA256(Key, IV || bucket || object) +// - KeyEncKey := HMAC-SHA256(Key, IV || 'SSE-S3' || 'DAREv2-HMAC-SHA256' || bucket || '/' || object) // - ObjectKey := DAREv2_Dec(KeyEncKey, SealedKey) // - object_data := DAREv2_Dec(ObjectKey, enc_object_data) // Output: object_data diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index 3e5e2b5ea..ab3422244 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -28,6 +28,7 @@ import ( "errors" "io" "net/http" + "path" "strconv" "github.com/minio/minio/cmd/logger" @@ -144,9 +145,20 @@ const ( ServerSideEncryptionSealedKey = ReservedMetadataPrefix + "Server-Side-Encryption-Sealed-Key" ) -// SSESealAlgorithmDareSha256 specifies DARE as authenticated en/decryption scheme and SHA256 as cryptographic -// hash function. -const SSESealAlgorithmDareSha256 = "DARE-SHA256" +const ( + // SSESealAlgorithmDareSha256 specifies DARE as authenticated en/decryption scheme and SHA256 as cryptographic + // hash function. The key derivation of DARE-SHA256 is not optimal and does not include the object path. + // It is considered legacy and should not be used anymore. + SSESealAlgorithmDareSha256 = "DARE-SHA256" + + // SSESealAlgorithmDareV2HmacSha256 specifies DAREv2 as authenticated en/decryption scheme and SHA256 as cryptographic + // hash function for the HMAC PRF. + SSESealAlgorithmDareV2HmacSha256 = "DAREv2-HMAC-SHA256" + + // SSEDomain specifies the domain for the derived key - in this case the + // key should be used for SSE-C. + SSEDomain = "SSE-C" +) // hasSSECustomerHeader returns true if the given HTTP header // contains server-side-encryption with customer provided key fields. @@ -250,10 +262,11 @@ func ParseSSECustomerHeader(header http.Header) (key []byte, err error) { } // This function rotates old to new key. -func rotateKey(oldKey []byte, newKey []byte, metadata map[string]string) error { +func rotateKey(oldKey []byte, newKey []byte, bucket, object string, metadata map[string]string) error { delete(metadata, SSECustomerKey) // make sure we do not save the key by accident - if metadata[ServerSideEncryptionSealAlgorithm] != SSESealAlgorithmDareSha256 { // currently DARE-SHA256 is the only option + algorithm := metadata[ServerSideEncryptionSealAlgorithm] + if algorithm != SSESealAlgorithmDareSha256 && algorithm != SSESealAlgorithmDareV2HmacSha256 { return errObjectTampered } iv, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionIV]) @@ -265,14 +278,33 @@ func rotateKey(oldKey []byte, newKey []byte, metadata map[string]string) error { return errObjectTampered } - sha := sha256.New() // derive key encryption key - sha.Write(oldKey) - sha.Write(iv) - keyEncryptionKey := sha.Sum(nil) + var ( + minDAREVersion byte + keyEncryptionKey [32]byte + ) + switch algorithm { + default: + return errObjectTampered + case SSESealAlgorithmDareSha256: // legacy key-encryption-key derivation + minDAREVersion = sio.Version10 + sha := sha256.New() + sha.Write(oldKey) + sha.Write(iv) + sha.Sum(keyEncryptionKey[:0]) + case SSESealAlgorithmDareV2HmacSha256: // key-encryption-key derivation - See: crypto/doc.go + minDAREVersion = sio.Version20 + mac := hmac.New(sha256.New, oldKey) + mac.Write(iv) + mac.Write([]byte(SSEDomain)) + mac.Write([]byte(SSESealAlgorithmDareV2HmacSha256)) + mac.Write([]byte(path.Join(bucket, object))) + mac.Sum(keyEncryptionKey[:0]) + } objectEncryptionKey := bytes.NewBuffer(nil) // decrypt object encryption key n, err := sio.Decrypt(objectEncryptionKey, bytes.NewReader(sealedKey), sio.Config{ - Key: keyEncryptionKey, + MinVersion: minDAREVersion, + Key: keyEncryptionKey[:], }) if n != 32 || err != nil { // Either the provided key does not match or the object was tampered. if subtle.ConstantTimeCompare(oldKey, newKey) == 1 { @@ -280,46 +312,34 @@ func rotateKey(oldKey []byte, newKey []byte, metadata map[string]string) error { } return errSSEKeyMismatch // To provide strict AWS S3 compatibility we return: access denied. } - if subtle.ConstantTimeCompare(oldKey, newKey) == 1 { - return nil // we don't need to rotate keys if newKey == oldKey + if subtle.ConstantTimeCompare(oldKey, newKey) == 1 && algorithm != SSESealAlgorithmDareSha256 { + return nil // we don't need to rotate keys if newKey == oldKey but we may have to upgrade KDF algorithm } - nonce := make([]byte, 32) // generate random values for key derivation - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return err - } - - niv := sha256.Sum256(nonce[:]) // derive key encryption key - sha = sha256.New() - sha.Write(newKey) - sha.Write(niv[:]) - keyEncryptionKey = sha.Sum(nil) + mac := hmac.New(sha256.New, newKey) // key-encryption-key derivation - See: crypto/doc.go + mac.Write(iv) + mac.Write([]byte(SSEDomain)) + mac.Write([]byte(SSESealAlgorithmDareV2HmacSha256)) + mac.Write([]byte(path.Join(bucket, object))) + mac.Sum(keyEncryptionKey[:0]) sealedKeyW := bytes.NewBuffer(nil) // sealedKey := 16 byte header + 32 byte payload + 16 byte tag n, err = sio.Encrypt(sealedKeyW, bytes.NewReader(objectEncryptionKey.Bytes()), sio.Config{ - Key: keyEncryptionKey, + Key: keyEncryptionKey[:], }) if n != 64 || err != nil { return errors.New("failed to seal object encryption key") // if this happens there's a bug in the code (may panic ?) } - metadata[ServerSideEncryptionIV] = base64.StdEncoding.EncodeToString(niv[:]) - metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareSha256 + metadata[ServerSideEncryptionIV] = base64.StdEncoding.EncodeToString(iv[:]) + metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareV2HmacSha256 metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKeyW.Bytes()) return nil } -func newEncryptMetadata(key []byte, metadata map[string]string) ([]byte, error) { +func newEncryptMetadata(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) { delete(metadata, SSECustomerKey) // make sure we do not save the key by accident - - // security notice: - // - If the first 32 bytes of the random value are ever repeated under the same client-provided - // key the encrypted object will not be tamper-proof. [ P(coll) ~= 1 / 2^(256 / 2)] - // - If the last 32 bytes of the random value are ever repeated under the same client-provided - // key an adversary may be able to extract the object encryption key. This depends on the - // authenticated en/decryption scheme. The DARE format will generate an 8 byte nonce which must - // be repeated in addition to reveal the object encryption key. - // [ P(coll) ~= 1 / 2^((256 + 64) / 2) ] + // See crypto/doc.go for detailed description nonce := make([]byte, 32+SSEIVSize) // generate random values for key derivation if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return nil, err @@ -329,11 +349,13 @@ func newEncryptMetadata(key []byte, metadata map[string]string) ([]byte, error) sha.Write(nonce[:32]) objectEncryptionKey := sha.Sum(nil) - iv := sha256.Sum256(nonce[32:]) // derive key encryption key - sha = sha256.New() - sha.Write(key) - sha.Write(iv[:]) - keyEncryptionKey := sha.Sum(nil) + iv := sha256.Sum256(nonce[32:]) // key-encryption-key derivation - See: crypto/doc.go + mac := hmac.New(sha256.New, key) + mac.Write(iv[:]) + mac.Write([]byte(SSEDomain)) + mac.Write([]byte(SSESealAlgorithmDareV2HmacSha256)) + mac.Write([]byte(path.Join(bucket, object))) + keyEncryptionKey := mac.Sum(nil) sealedKey := bytes.NewBuffer(nil) // sealedKey := 16 byte header + 32 byte payload + 16 byte tag n, err := sio.Encrypt(sealedKey, bytes.NewReader(objectEncryptionKey), sio.Config{ @@ -344,14 +366,14 @@ func newEncryptMetadata(key []byte, metadata map[string]string) ([]byte, error) } metadata[ServerSideEncryptionIV] = base64.StdEncoding.EncodeToString(iv[:]) - metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareSha256 + metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareV2HmacSha256 metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Bytes()) return objectEncryptionKey, nil } -func newEncryptReader(content io.Reader, key []byte, metadata map[string]string) (io.Reader, error) { - objectEncryptionKey, err := newEncryptMetadata(key, metadata) +func newEncryptReader(content io.Reader, key []byte, bucket, object string, metadata map[string]string) (io.Reader, error) { + objectEncryptionKey, err := newEncryptMetadata(key, bucket, object, metadata) if err != nil { return nil, err } @@ -367,29 +389,26 @@ func newEncryptReader(content io.Reader, key []byte, metadata map[string]string) // EncryptRequest takes the client provided content and encrypts the data // with the client provided key. It also marks the object as client-side-encrypted // and sets the correct headers. -func EncryptRequest(content io.Reader, r *http.Request, metadata map[string]string) (io.Reader, error) { +func EncryptRequest(content io.Reader, r *http.Request, bucket, object string, metadata map[string]string) (io.Reader, error) { key, err := ParseSSECustomerRequest(r) if err != nil { return nil, err } - return newEncryptReader(content, key, metadata) + return newEncryptReader(content, key, bucket, object, metadata) } // DecryptCopyRequest decrypts the object with the client provided key. It also removes // the client-side-encryption metadata from the object and sets the correct headers. -func DecryptCopyRequest(client io.Writer, r *http.Request, metadata map[string]string) (io.WriteCloser, error) { +func DecryptCopyRequest(client io.Writer, r *http.Request, bucket, object string, metadata map[string]string) (io.WriteCloser, error) { key, err := ParseSSECopyCustomerRequest(r) if err != nil { return nil, err } delete(metadata, SSECopyCustomerKey) // make sure we do not save the key by accident - return newDecryptWriter(client, key, 0, metadata) + return newDecryptWriter(client, key, bucket, object, 0, metadata) } -func decryptObjectInfo(key []byte, metadata map[string]string) ([]byte, error) { - if metadata[ServerSideEncryptionSealAlgorithm] != SSESealAlgorithmDareSha256 { // currently DARE-SHA256 is the only option - return nil, errObjectTampered - } +func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) { iv, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionIV]) if err != nil || len(iv) != SSEIVSize { return nil, errObjectTampered @@ -399,14 +418,33 @@ func decryptObjectInfo(key []byte, metadata map[string]string) ([]byte, error) { return nil, errObjectTampered } - sha := sha256.New() // derive key encryption key - sha.Write(key) - sha.Write(iv) - keyEncryptionKey := sha.Sum(nil) + var ( + minDAREVersion byte + keyEncryptionKey [32]byte + ) + switch algorithm := metadata[ServerSideEncryptionSealAlgorithm]; algorithm { + default: + return nil, errObjectTampered + case SSESealAlgorithmDareSha256: // legacy key-encryption-key derivation + minDAREVersion = sio.Version10 + sha := sha256.New() + sha.Write(key) + sha.Write(iv) + sha.Sum(keyEncryptionKey[:0]) + case SSESealAlgorithmDareV2HmacSha256: // key-encryption-key derivation - See: crypto/doc.go + minDAREVersion = sio.Version20 + mac := hmac.New(sha256.New, key) + mac.Write(iv) + mac.Write([]byte(SSEDomain)) + mac.Write([]byte(SSESealAlgorithmDareV2HmacSha256)) + mac.Write([]byte(path.Join(bucket, object))) + mac.Sum(keyEncryptionKey[:0]) + } objectEncryptionKey := bytes.NewBuffer(nil) // decrypt object encryption key n, err := sio.Decrypt(objectEncryptionKey, bytes.NewReader(sealedKey), sio.Config{ - Key: keyEncryptionKey, + MinVersion: minDAREVersion, + Key: keyEncryptionKey[:], }) if n != 32 || err != nil { // Either the provided key does not match or the object was tampered. @@ -416,11 +454,10 @@ func decryptObjectInfo(key []byte, metadata map[string]string) ([]byte, error) { return objectEncryptionKey.Bytes(), nil } -func newDecryptWriter(client io.Writer, key []byte, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) { - objectEncryptionKey, err := decryptObjectInfo(key, metadata) +func newDecryptWriter(client io.Writer, key []byte, bucket, object string, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) { + objectEncryptionKey, err := decryptObjectInfo(key, bucket, object, metadata) if err != nil { return nil, err - } return newDecryptWriterWithObjectKey(client, objectEncryptionKey, seqNumber, metadata) } @@ -443,19 +480,19 @@ func newDecryptWriterWithObjectKey(client io.Writer, objectEncryptionKey []byte, // DecryptRequestWithSequenceNumber decrypts the object with the client provided key. It also removes // the client-side-encryption metadata from the object and sets the correct headers. -func DecryptRequestWithSequenceNumber(client io.Writer, r *http.Request, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) { +func DecryptRequestWithSequenceNumber(client io.Writer, r *http.Request, bucket, object string, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) { key, err := ParseSSECustomerRequest(r) if err != nil { return nil, err } delete(metadata, SSECustomerKey) // make sure we do not save the key by accident - return newDecryptWriter(client, key, seqNumber, metadata) + return newDecryptWriter(client, key, bucket, object, seqNumber, metadata) } // DecryptRequest decrypts the object with the client provided key. It also removes // the client-side-encryption metadata from the object and sets the correct headers. -func DecryptRequest(client io.Writer, r *http.Request, metadata map[string]string) (io.WriteCloser, error) { - return DecryptRequestWithSequenceNumber(client, r, 0, metadata) +func DecryptRequest(client io.Writer, r *http.Request, bucket, object string, metadata map[string]string) (io.WriteCloser, error) { + return DecryptRequestWithSequenceNumber(client, r, bucket, object, 0, metadata) } // DecryptBlocksWriter - decrypts multipart parts, while implementing a io.Writer compatible interface. @@ -469,9 +506,10 @@ type DecryptBlocksWriter struct { // Current part index partIndex int // Parts information - parts []objectPartInfo - req *http.Request - metadata map[string]string + parts []objectPartInfo + req *http.Request + bucket, object string + metadata map[string]string partEncRelOffset int64 @@ -499,7 +537,7 @@ func (w *DecryptBlocksWriter) buildDecrypter(partID int) error { return err } - objectEncryptionKey, err := decryptObjectInfo(key, m) + objectEncryptionKey, err := decryptObjectInfo(key, w.bucket, w.object, m) if err != nil { return err } @@ -594,14 +632,14 @@ func (w *DecryptBlocksWriter) Close() error { // DecryptAllBlocksCopyRequest - setup a struct which can decrypt many concatenated encrypted data // parts information helps to know the boundaries of each encrypted data block, this function decrypts // all parts starting from part-1. -func DecryptAllBlocksCopyRequest(client io.Writer, r *http.Request, objInfo ObjectInfo) (io.WriteCloser, int64, error) { - w, _, size, err := DecryptBlocksRequest(client, r, 0, objInfo.Size, objInfo, true) +func DecryptAllBlocksCopyRequest(client io.Writer, r *http.Request, bucket, object string, objInfo ObjectInfo) (io.WriteCloser, int64, error) { + w, _, size, err := DecryptBlocksRequest(client, r, bucket, object, 0, objInfo.Size, objInfo, true) return w, size, err } // DecryptBlocksRequest - setup a struct which can decrypt many concatenated encrypted data // parts information helps to know the boundaries of each encrypted data block. -func DecryptBlocksRequest(client io.Writer, r *http.Request, startOffset, length int64, objInfo ObjectInfo, copySource bool) (io.WriteCloser, int64, int64, error) { +func DecryptBlocksRequest(client io.Writer, r *http.Request, bucket, object string, startOffset, length int64, objInfo ObjectInfo, copySource bool) (io.WriteCloser, int64, int64, error) { seqNumber, encStartOffset, encLength := getEncryptedStartOffset(startOffset, length) // Encryption length cannot be bigger than the file size, if it is @@ -614,9 +652,9 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, startOffset, length var writer io.WriteCloser var err error if copySource { - writer, err = DecryptCopyRequest(client, r, objInfo.UserDefined) + writer, err = DecryptCopyRequest(client, r, bucket, object, objInfo.UserDefined) } else { - writer, err = DecryptRequestWithSequenceNumber(client, r, seqNumber, objInfo.UserDefined) + writer, err = DecryptRequestWithSequenceNumber(client, r, bucket, object, seqNumber, objInfo.UserDefined) } if err != nil { return nil, 0, 0, err @@ -656,6 +694,8 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, startOffset, length parts: objInfo.Parts, partIndex: partStartIndex, req: r, + bucket: bucket, + object: object, customerKeyHeader: r.Header.Get(SSECustomerKey), copySource: copySource, } diff --git a/cmd/encryption-v1_test.go b/cmd/encryption-v1_test.go index 283d4a388..217000660 100644 --- a/cmd/encryption-v1_test.go +++ b/cmd/encryption-v1_test.go @@ -308,7 +308,7 @@ func TestEncryptRequest(t *testing.T) { for k, v := range test.header { req.Header.Set(k, v) } - _, err := EncryptRequest(content, req, test.metadata) + _, err := EncryptRequest(content, req, "bucket", "object", test.metadata) if err != nil { t.Fatalf("Test %d: Failed to encrypt request: %v", i, err) } @@ -328,11 +328,14 @@ func TestEncryptRequest(t *testing.T) { } var decryptRequestTests = []struct { - header map[string]string - metadata map[string]string - shouldFail bool + bucket, object string + header map[string]string + metadata map[string]string + shouldFail bool }{ { + bucket: "bucket", + object: "object", header: map[string]string{ SSECustomerAlgorithm: "AES256", SSECustomerKey: "MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", @@ -346,6 +349,23 @@ var decryptRequestTests = []struct { shouldFail: false, }, { + bucket: "bucket", + object: "object", + header: map[string]string{ + SSECustomerAlgorithm: "AES256", + SSECustomerKey: "MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", + SSECustomerKeyMD5: "7PpPLAK26ONlVUGOWlusfg==", + }, + metadata: map[string]string{ + ServerSideEncryptionSealAlgorithm: SSESealAlgorithmDareV2HmacSha256, + ServerSideEncryptionIV: "qEqmsONcorqlcZXJxaw32H04eyXyXwUgjHzlhkaIYrU=", + ServerSideEncryptionSealedKey: "IAAfAIM14ugTGcM/dIrn4iQMrkl1sjKyeBQ8FBEvRebYj8vWvxG+0cJRpC6NXRU1wJN50JaUOATjO7kz0wZ2mA==", + }, + shouldFail: false, + }, + { + bucket: "bucket", + object: "object", header: map[string]string{ SSECustomerAlgorithm: "AES256", SSECustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", @@ -359,6 +379,8 @@ var decryptRequestTests = []struct { shouldFail: true, }, { + bucket: "bucket", + object: "object", header: map[string]string{ SSECustomerAlgorithm: "AES256", SSECustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", @@ -372,6 +394,8 @@ var decryptRequestTests = []struct { shouldFail: true, }, { + bucket: "bucket", + object: "object", header: map[string]string{ SSECustomerAlgorithm: "AES256", SSECustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", @@ -384,21 +408,39 @@ var decryptRequestTests = []struct { }, shouldFail: true, }, + { + bucket: "bucket", + object: "object-2", + header: map[string]string{ + SSECustomerAlgorithm: "AES256", + SSECustomerKey: "MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", + SSECustomerKeyMD5: "7PpPLAK26ONlVUGOWlusfg==", + }, + metadata: map[string]string{ + ServerSideEncryptionSealAlgorithm: SSESealAlgorithmDareV2HmacSha256, + ServerSideEncryptionIV: "qEqmsONcorqlcZXJxaw32H04eyXyXwUgjHzlhkaIYrU=", + ServerSideEncryptionSealedKey: "IAAfAIM14ugTGcM/dIrn4iQMrkl1sjKyeBQ8FBEvRebYj8vWvxG+0cJRpC6NXRU1wJN50JaUOATjO7kz0wZ2mA==", + }, + shouldFail: true, + }, } func TestDecryptRequest(t *testing.T) { defer func(flag bool) { globalIsSSL = flag }(globalIsSSL) globalIsSSL = true - for i, test := range decryptRequestTests { + for i, test := range decryptRequestTests[1:] { client := bytes.NewBuffer(nil) req := &http.Request{Header: http.Header{}} for k, v := range test.header { req.Header.Set(k, v) } - _, err := DecryptRequest(client, req, test.metadata) + _, err := DecryptRequest(client, req, test.bucket, test.object, test.metadata) if err != nil && !test.shouldFail { t.Fatalf("Test %d: Failed to encrypt request: %v", i, err) } + if err == nil && test.shouldFail { + t.Fatalf("Test %d: should fail but passed", i) + } if key, ok := test.metadata[SSECustomerKey]; ok { t.Errorf("Test %d: Client provided key survived in metadata - key: %s", i, key) } diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 61cfbfe40..63cd9b287 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -161,7 +161,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req // additionally also skipping mod(offset)64KiB boundaries. writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length) - writer, startOffset, length, err = DecryptBlocksRequest(writer, r, startOffset, length, objInfo, false) + writer, startOffset, length, err = DecryptBlocksRequest(writer, r, bucket, object, startOffset, length, objInfo, false) if err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return @@ -270,7 +270,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re writeErrorResponse(w, apiErr, r.URL) return } else if encrypted { - if _, err = DecryptRequest(w, r, objInfo.UserDefined); err != nil { + if _, err = DecryptRequest(w, r, bucket, object, objInfo.UserDefined); err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } @@ -463,7 +463,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re for k, v := range srcInfo.UserDefined { encMetadata[k] = v } - if err = rotateKey(oldKey, newKey, encMetadata); err != nil { + if err = rotateKey(oldKey, newKey, srcBucket, srcObject, encMetadata); err != nil { pipeWriter.CloseWithError(err) writeErrorResponse(w, toAPIErrorCode(err), r.URL) return @@ -475,7 +475,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re if sseCopyC { // Source is encrypted make sure to save the encrypted size. writer = ioutil.LimitedWriter(writer, 0, srcInfo.Size) - writer, srcInfo.Size, err = DecryptAllBlocksCopyRequest(writer, r, srcInfo) + writer, srcInfo.Size, err = DecryptAllBlocksCopyRequest(writer, r, srcBucket, srcObject, srcInfo) if err != nil { pipeWriter.CloseWithError(err) writeErrorResponse(w, toAPIErrorCode(err), r.URL) @@ -490,7 +490,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re } } if sseC { - reader, err = newEncryptReader(pipeReader, newKey, encMetadata) + reader, err = newEncryptReader(reader, newKey, dstBucket, dstObject, encMetadata) if err != nil { pipeWriter.CloseWithError(err) writeErrorResponse(w, toAPIErrorCode(err), r.URL) @@ -789,7 +789,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req if objectAPI.IsEncryptionSupported() { if hasSSECustomerHeader(r.Header) && !hasSuffix(object, slashSeparator) { // handle SSE-C requests - reader, err = EncryptRequest(hashReader, r, metadata) + reader, err = EncryptRequest(hashReader, r, bucket, object, metadata) if err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return @@ -888,7 +888,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } - _, err = newEncryptMetadata(key, encMetadata) + _, err = newEncryptMetadata(key, bucket, object, encMetadata) if err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return @@ -1056,7 +1056,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt // Response writer should be limited early on for decryption upto required length, // additionally also skipping mod(offset)64KiB boundaries. writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length) - writer, startOffset, length, err = DecryptBlocksRequest(pipeWriter, r, startOffset, length, srcInfo, true) + writer, startOffset, length, err = DecryptBlocksRequest(writer, r, srcBucket, srcObject, startOffset, length, srcInfo, true) if err != nil { pipeWriter.CloseWithError(err) writeErrorResponse(w, toAPIErrorCode(err), r.URL) @@ -1078,14 +1078,14 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt // Calculating object encryption key var objectEncryptionKey []byte - objectEncryptionKey, err = decryptObjectInfo(key, li.UserDefined) + objectEncryptionKey, err = decryptObjectInfo(key, dstBucket, dstObject, li.UserDefined) if err != nil { pipeWriter.CloseWithError(err) writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } - reader, err = sio.EncryptReader(pipeReader, sio.Config{Key: objectEncryptionKey}) + reader, err = sio.EncryptReader(reader, sio.Config{Key: objectEncryptionKey}) if err != nil { pipeWriter.CloseWithError(err) writeErrorResponse(w, toAPIErrorCode(err), r.URL) @@ -1281,7 +1281,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http // Calculating object encryption key var objectEncryptionKey []byte - objectEncryptionKey, err = decryptObjectInfo(key, li.UserDefined) + objectEncryptionKey, err = decryptObjectInfo(key, bucket, object, li.UserDefined) if err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return