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.
This commit is contained in:
Andreas Auernhammer 2018-07-10 02:18:28 +02:00 committed by kannappanr
parent 4ddc222f46
commit b181a693fb
5 changed files with 178 additions and 96 deletions

View File

@ -623,7 +623,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
} }
reader, err = newEncryptReader(hashReader, key, metadata) reader, err = newEncryptReader(hashReader, key, bucket, object, metadata)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return

View File

@ -32,7 +32,7 @@
// Input: ClientKey, bucket, object, metadata, object_data // Input: ClientKey, bucket, object, metadata, object_data
// - IV := Random({0,1}²⁵⁶) // - IV := Random({0,1}²⁵⁶)
// - ObjectKey := SHA256(ClientKey || 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) // - SealedKey := DAREv2_Enc(KeyEncKey, ObjectKey)
// - enc_object_data := DAREv2_Enc(ObjectKey, object_data) // - enc_object_data := DAREv2_Enc(ObjectKey, object_data)
// - metadata <- IV // - metadata <- IV
@ -43,7 +43,7 @@
// Input: ClientKey, bucket, object, metadata, enc_object_data // Input: ClientKey, bucket, object, metadata, enc_object_data
// - IV <- metadata // - IV <- metadata
// - SealedKey <- 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) // - ObjectKey := DAREv2_Dec(KeyEncKey, SealedKey)
// - object_data := DAREv2_Dec(ObjectKey, enc_object_data) // - object_data := DAREv2_Dec(ObjectKey, enc_object_data)
// Output: object_data // Output: object_data
@ -64,7 +64,7 @@
// Input: MasterKey, bucket, object, metadata, object_data // Input: MasterKey, bucket, object, metadata, object_data
// - IV := Random({0,1}²⁵⁶) // - IV := Random({0,1}²⁵⁶)
// - ObjectKey := SHA256(MasterKey || 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) // - SealedKey := DAREv2_Enc(KeyEncKey, ObjectKey)
// - enc_object_data := DAREv2_Enc(ObjectKey, object_data) // - enc_object_data := DAREv2_Enc(ObjectKey, object_data)
// - metadata <- IV // - metadata <- IV
@ -75,7 +75,7 @@
// Input: MasterKey, bucket, object, metadata, enc_object_data // Input: MasterKey, bucket, object, metadata, enc_object_data
// - IV <- metadata // - IV <- metadata
// - SealedKey <- 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) // - ObjectKey := DAREv2_Dec(KeyEncKey, SealedKey)
// - object_data := DAREv2_Dec(ObjectKey, enc_object_data) // - object_data := DAREv2_Dec(ObjectKey, enc_object_data)
// Output: object_data // Output: object_data
@ -92,7 +92,7 @@
// - Key, EncKey := Generate(KeyID) // - Key, EncKey := Generate(KeyID)
// - IV := Random({0,1}²⁵⁶) // - IV := Random({0,1}²⁵⁶)
// - ObjectKey := SHA256(Key, 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) // - SealedKey := DAREv2_Enc(KeyEncKey, ObjectKey)
// - enc_object_data := DAREv2_Enc(ObjectKey, object_data) // - enc_object_data := DAREv2_Enc(ObjectKey, object_data)
// - metadata <- IV // - metadata <- IV
@ -108,7 +108,7 @@
// - IV <- metadata // - IV <- metadata
// - SealedKey <- metadata // - SealedKey <- metadata
// - Key := Unseal(KeyID, EncKey) // - 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) // - ObjectKey := DAREv2_Dec(KeyEncKey, SealedKey)
// - object_data := DAREv2_Dec(ObjectKey, enc_object_data) // - object_data := DAREv2_Dec(ObjectKey, enc_object_data)
// Output: object_data // Output: object_data

View File

@ -28,6 +28,7 @@ import (
"errors" "errors"
"io" "io"
"net/http" "net/http"
"path"
"strconv" "strconv"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@ -144,9 +145,20 @@ const (
ServerSideEncryptionSealedKey = ReservedMetadataPrefix + "Server-Side-Encryption-Sealed-Key" ServerSideEncryptionSealedKey = ReservedMetadataPrefix + "Server-Side-Encryption-Sealed-Key"
) )
// SSESealAlgorithmDareSha256 specifies DARE as authenticated en/decryption scheme and SHA256 as cryptographic const (
// hash function. // SSESealAlgorithmDareSha256 specifies DARE as authenticated en/decryption scheme and SHA256 as cryptographic
const SSESealAlgorithmDareSha256 = "DARE-SHA256" // 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 // hasSSECustomerHeader returns true if the given HTTP header
// contains server-side-encryption with customer provided key fields. // 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. // 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 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 return errObjectTampered
} }
iv, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionIV]) iv, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionIV])
@ -265,14 +278,33 @@ func rotateKey(oldKey []byte, newKey []byte, metadata map[string]string) error {
return errObjectTampered return errObjectTampered
} }
sha := sha256.New() // derive key encryption key var (
sha.Write(oldKey) minDAREVersion byte
sha.Write(iv) keyEncryptionKey [32]byte
keyEncryptionKey := sha.Sum(nil) )
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 objectEncryptionKey := bytes.NewBuffer(nil) // decrypt object encryption key
n, err := sio.Decrypt(objectEncryptionKey, bytes.NewReader(sealedKey), sio.Config{ 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 n != 32 || err != nil { // Either the provided key does not match or the object was tampered.
if subtle.ConstantTimeCompare(oldKey, newKey) == 1 { 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. return errSSEKeyMismatch // To provide strict AWS S3 compatibility we return: access denied.
} }
if subtle.ConstantTimeCompare(oldKey, newKey) == 1 { if subtle.ConstantTimeCompare(oldKey, newKey) == 1 && algorithm != SSESealAlgorithmDareSha256 {
return nil // we don't need to rotate keys if newKey == oldKey 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 mac := hmac.New(sha256.New, newKey) // key-encryption-key derivation - See: crypto/doc.go
if _, err = io.ReadFull(rand.Reader, nonce); err != nil { mac.Write(iv)
return err mac.Write([]byte(SSEDomain))
} mac.Write([]byte(SSESealAlgorithmDareV2HmacSha256))
mac.Write([]byte(path.Join(bucket, object)))
niv := sha256.Sum256(nonce[:]) // derive key encryption key mac.Sum(keyEncryptionKey[:0])
sha = sha256.New()
sha.Write(newKey)
sha.Write(niv[:])
keyEncryptionKey = sha.Sum(nil)
sealedKeyW := bytes.NewBuffer(nil) // sealedKey := 16 byte header + 32 byte payload + 16 byte tag 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{ n, err = sio.Encrypt(sealedKeyW, bytes.NewReader(objectEncryptionKey.Bytes()), sio.Config{
Key: keyEncryptionKey, Key: keyEncryptionKey[:],
}) })
if n != 64 || err != nil { 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 ?) 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[ServerSideEncryptionIV] = base64.StdEncoding.EncodeToString(iv[:])
metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareSha256 metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareV2HmacSha256
metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKeyW.Bytes()) metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKeyW.Bytes())
return nil 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 delete(metadata, SSECustomerKey) // make sure we do not save the key by accident
// See crypto/doc.go for detailed description
// 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) ]
nonce := make([]byte, 32+SSEIVSize) // generate random values for key derivation nonce := make([]byte, 32+SSEIVSize) // generate random values for key derivation
if _, err := io.ReadFull(rand.Reader, nonce); err != nil { if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err return nil, err
@ -329,11 +349,13 @@ func newEncryptMetadata(key []byte, metadata map[string]string) ([]byte, error)
sha.Write(nonce[:32]) sha.Write(nonce[:32])
objectEncryptionKey := sha.Sum(nil) objectEncryptionKey := sha.Sum(nil)
iv := sha256.Sum256(nonce[32:]) // derive key encryption key iv := sha256.Sum256(nonce[32:]) // key-encryption-key derivation - See: crypto/doc.go
sha = sha256.New() mac := hmac.New(sha256.New, key)
sha.Write(key) mac.Write(iv[:])
sha.Write(iv[:]) mac.Write([]byte(SSEDomain))
keyEncryptionKey := sha.Sum(nil) 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 sealedKey := bytes.NewBuffer(nil) // sealedKey := 16 byte header + 32 byte payload + 16 byte tag
n, err := sio.Encrypt(sealedKey, bytes.NewReader(objectEncryptionKey), sio.Config{ 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[ServerSideEncryptionIV] = base64.StdEncoding.EncodeToString(iv[:])
metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareSha256 metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareV2HmacSha256
metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Bytes()) metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Bytes())
return objectEncryptionKey, nil return objectEncryptionKey, nil
} }
func newEncryptReader(content io.Reader, key []byte, metadata map[string]string) (io.Reader, error) { func newEncryptReader(content io.Reader, key []byte, bucket, object string, metadata map[string]string) (io.Reader, error) {
objectEncryptionKey, err := newEncryptMetadata(key, metadata) objectEncryptionKey, err := newEncryptMetadata(key, bucket, object, metadata)
if err != nil { if err != nil {
return nil, err 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 // EncryptRequest takes the client provided content and encrypts the data
// with the client provided key. It also marks the object as client-side-encrypted // with the client provided key. It also marks the object as client-side-encrypted
// and sets the correct headers. // 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) key, err := ParseSSECustomerRequest(r)
if err != nil { if err != nil {
return nil, err 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 // 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. // 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) key, err := ParseSSECopyCustomerRequest(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
delete(metadata, SSECopyCustomerKey) // make sure we do not save the key by accident 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) { func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) {
if metadata[ServerSideEncryptionSealAlgorithm] != SSESealAlgorithmDareSha256 { // currently DARE-SHA256 is the only option
return nil, errObjectTampered
}
iv, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionIV]) iv, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionIV])
if err != nil || len(iv) != SSEIVSize { if err != nil || len(iv) != SSEIVSize {
return nil, errObjectTampered return nil, errObjectTampered
@ -399,14 +418,33 @@ func decryptObjectInfo(key []byte, metadata map[string]string) ([]byte, error) {
return nil, errObjectTampered return nil, errObjectTampered
} }
sha := sha256.New() // derive key encryption key var (
sha.Write(key) minDAREVersion byte
sha.Write(iv) keyEncryptionKey [32]byte
keyEncryptionKey := sha.Sum(nil) )
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 objectEncryptionKey := bytes.NewBuffer(nil) // decrypt object encryption key
n, err := sio.Decrypt(objectEncryptionKey, bytes.NewReader(sealedKey), sio.Config{ n, err := sio.Decrypt(objectEncryptionKey, bytes.NewReader(sealedKey), sio.Config{
Key: keyEncryptionKey, MinVersion: minDAREVersion,
Key: keyEncryptionKey[:],
}) })
if n != 32 || err != nil { if n != 32 || err != nil {
// Either the provided key does not match or the object was tampered. // 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 return objectEncryptionKey.Bytes(), nil
} }
func newDecryptWriter(client io.Writer, key []byte, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) { func newDecryptWriter(client io.Writer, key []byte, bucket, object string, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) {
objectEncryptionKey, err := decryptObjectInfo(key, metadata) objectEncryptionKey, err := decryptObjectInfo(key, bucket, object, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newDecryptWriterWithObjectKey(client, objectEncryptionKey, seqNumber, metadata) 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 // 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. // 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) key, err := ParseSSECustomerRequest(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
delete(metadata, SSECustomerKey) // make sure we do not save the key by accident 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 // 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. // 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) { func DecryptRequest(client io.Writer, r *http.Request, bucket, object string, metadata map[string]string) (io.WriteCloser, error) {
return DecryptRequestWithSequenceNumber(client, r, 0, metadata) return DecryptRequestWithSequenceNumber(client, r, bucket, object, 0, metadata)
} }
// DecryptBlocksWriter - decrypts multipart parts, while implementing a io.Writer compatible interface. // DecryptBlocksWriter - decrypts multipart parts, while implementing a io.Writer compatible interface.
@ -469,9 +506,10 @@ type DecryptBlocksWriter struct {
// Current part index // Current part index
partIndex int partIndex int
// Parts information // Parts information
parts []objectPartInfo parts []objectPartInfo
req *http.Request req *http.Request
metadata map[string]string bucket, object string
metadata map[string]string
partEncRelOffset int64 partEncRelOffset int64
@ -499,7 +537,7 @@ func (w *DecryptBlocksWriter) buildDecrypter(partID int) error {
return err return err
} }
objectEncryptionKey, err := decryptObjectInfo(key, m) objectEncryptionKey, err := decryptObjectInfo(key, w.bucket, w.object, m)
if err != nil { if err != nil {
return err return err
} }
@ -594,14 +632,14 @@ func (w *DecryptBlocksWriter) Close() error {
// DecryptAllBlocksCopyRequest - setup a struct which can decrypt many concatenated encrypted data // 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 // parts information helps to know the boundaries of each encrypted data block, this function decrypts
// all parts starting from part-1. // all parts starting from part-1.
func DecryptAllBlocksCopyRequest(client io.Writer, r *http.Request, objInfo ObjectInfo) (io.WriteCloser, int64, error) { func DecryptAllBlocksCopyRequest(client io.Writer, r *http.Request, bucket, object string, objInfo ObjectInfo) (io.WriteCloser, int64, error) {
w, _, size, err := DecryptBlocksRequest(client, r, 0, objInfo.Size, objInfo, true) w, _, size, err := DecryptBlocksRequest(client, r, bucket, object, 0, objInfo.Size, objInfo, true)
return w, size, err return w, size, err
} }
// DecryptBlocksRequest - setup a struct which can decrypt many concatenated encrypted data // DecryptBlocksRequest - setup a struct which can decrypt many concatenated encrypted data
// parts information helps to know the boundaries of each encrypted data block. // 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) seqNumber, encStartOffset, encLength := getEncryptedStartOffset(startOffset, length)
// Encryption length cannot be bigger than the file size, if it is // 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 writer io.WriteCloser
var err error var err error
if copySource { if copySource {
writer, err = DecryptCopyRequest(client, r, objInfo.UserDefined) writer, err = DecryptCopyRequest(client, r, bucket, object, objInfo.UserDefined)
} else { } else {
writer, err = DecryptRequestWithSequenceNumber(client, r, seqNumber, objInfo.UserDefined) writer, err = DecryptRequestWithSequenceNumber(client, r, bucket, object, seqNumber, objInfo.UserDefined)
} }
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
@ -656,6 +694,8 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, startOffset, length
parts: objInfo.Parts, parts: objInfo.Parts,
partIndex: partStartIndex, partIndex: partStartIndex,
req: r, req: r,
bucket: bucket,
object: object,
customerKeyHeader: r.Header.Get(SSECustomerKey), customerKeyHeader: r.Header.Get(SSECustomerKey),
copySource: copySource, copySource: copySource,
} }

View File

@ -308,7 +308,7 @@ func TestEncryptRequest(t *testing.T) {
for k, v := range test.header { for k, v := range test.header {
req.Header.Set(k, v) req.Header.Set(k, v)
} }
_, err := EncryptRequest(content, req, test.metadata) _, err := EncryptRequest(content, req, "bucket", "object", test.metadata)
if err != nil { if err != nil {
t.Fatalf("Test %d: Failed to encrypt request: %v", i, err) t.Fatalf("Test %d: Failed to encrypt request: %v", i, err)
} }
@ -328,11 +328,14 @@ func TestEncryptRequest(t *testing.T) {
} }
var decryptRequestTests = []struct { var decryptRequestTests = []struct {
header map[string]string bucket, object string
metadata map[string]string header map[string]string
shouldFail bool metadata map[string]string
shouldFail bool
}{ }{
{ {
bucket: "bucket",
object: "object",
header: map[string]string{ header: map[string]string{
SSECustomerAlgorithm: "AES256", SSECustomerAlgorithm: "AES256",
SSECustomerKey: "MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", SSECustomerKey: "MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=",
@ -346,6 +349,23 @@ var decryptRequestTests = []struct {
shouldFail: false, 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{ header: map[string]string{
SSECustomerAlgorithm: "AES256", SSECustomerAlgorithm: "AES256",
SSECustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", SSECustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
@ -359,6 +379,8 @@ var decryptRequestTests = []struct {
shouldFail: true, shouldFail: true,
}, },
{ {
bucket: "bucket",
object: "object",
header: map[string]string{ header: map[string]string{
SSECustomerAlgorithm: "AES256", SSECustomerAlgorithm: "AES256",
SSECustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", SSECustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
@ -372,6 +394,8 @@ var decryptRequestTests = []struct {
shouldFail: true, shouldFail: true,
}, },
{ {
bucket: "bucket",
object: "object",
header: map[string]string{ header: map[string]string{
SSECustomerAlgorithm: "AES256", SSECustomerAlgorithm: "AES256",
SSECustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", SSECustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
@ -384,21 +408,39 @@ var decryptRequestTests = []struct {
}, },
shouldFail: true, 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) { func TestDecryptRequest(t *testing.T) {
defer func(flag bool) { globalIsSSL = flag }(globalIsSSL) defer func(flag bool) { globalIsSSL = flag }(globalIsSSL)
globalIsSSL = true globalIsSSL = true
for i, test := range decryptRequestTests { for i, test := range decryptRequestTests[1:] {
client := bytes.NewBuffer(nil) client := bytes.NewBuffer(nil)
req := &http.Request{Header: http.Header{}} req := &http.Request{Header: http.Header{}}
for k, v := range test.header { for k, v := range test.header {
req.Header.Set(k, v) 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 { if err != nil && !test.shouldFail {
t.Fatalf("Test %d: Failed to encrypt request: %v", i, err) 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 { if key, ok := test.metadata[SSECustomerKey]; ok {
t.Errorf("Test %d: Client provided key survived in metadata - key: %s", i, key) t.Errorf("Test %d: Client provided key survived in metadata - key: %s", i, key)
} }

View File

@ -161,7 +161,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
// additionally also skipping mod(offset)64KiB boundaries. // additionally also skipping mod(offset)64KiB boundaries.
writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length) 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 { if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
@ -270,7 +270,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
writeErrorResponse(w, apiErr, r.URL) writeErrorResponse(w, apiErr, r.URL)
return return
} else if encrypted { } 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) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
} }
@ -463,7 +463,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
for k, v := range srcInfo.UserDefined { for k, v := range srcInfo.UserDefined {
encMetadata[k] = v encMetadata[k] = v
} }
if err = rotateKey(oldKey, newKey, encMetadata); err != nil { if err = rotateKey(oldKey, newKey, srcBucket, srcObject, encMetadata); err != nil {
pipeWriter.CloseWithError(err) pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
@ -475,7 +475,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
if sseCopyC { if sseCopyC {
// Source is encrypted make sure to save the encrypted size. // Source is encrypted make sure to save the encrypted size.
writer = ioutil.LimitedWriter(writer, 0, srcInfo.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 { if err != nil {
pipeWriter.CloseWithError(err) pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@ -490,7 +490,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
} }
} }
if sseC { if sseC {
reader, err = newEncryptReader(pipeReader, newKey, encMetadata) reader, err = newEncryptReader(reader, newKey, dstBucket, dstObject, encMetadata)
if err != nil { if err != nil {
pipeWriter.CloseWithError(err) pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@ -789,7 +789,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
if objectAPI.IsEncryptionSupported() { if objectAPI.IsEncryptionSupported() {
if hasSSECustomerHeader(r.Header) && !hasSuffix(object, slashSeparator) { // handle SSE-C requests 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 { if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
@ -888,7 +888,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
} }
_, err = newEncryptMetadata(key, encMetadata) _, err = newEncryptMetadata(key, bucket, object, encMetadata)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return 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, // Response writer should be limited early on for decryption upto required length,
// additionally also skipping mod(offset)64KiB boundaries. // additionally also skipping mod(offset)64KiB boundaries.
writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length) 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 { if err != nil {
pipeWriter.CloseWithError(err) pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@ -1078,14 +1078,14 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
// Calculating object encryption key // Calculating object encryption key
var objectEncryptionKey []byte var objectEncryptionKey []byte
objectEncryptionKey, err = decryptObjectInfo(key, li.UserDefined) objectEncryptionKey, err = decryptObjectInfo(key, dstBucket, dstObject, li.UserDefined)
if err != nil { if err != nil {
pipeWriter.CloseWithError(err) pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
} }
reader, err = sio.EncryptReader(pipeReader, sio.Config{Key: objectEncryptionKey}) reader, err = sio.EncryptReader(reader, sio.Config{Key: objectEncryptionKey})
if err != nil { if err != nil {
pipeWriter.CloseWithError(err) pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@ -1281,7 +1281,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
// Calculating object encryption key // Calculating object encryption key
var objectEncryptionKey []byte var objectEncryptionKey []byte
objectEncryptionKey, err = decryptObjectInfo(key, li.UserDefined) objectEncryptionKey, err = decryptObjectInfo(key, bucket, object, li.UserDefined)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return