From 2d67c267946a68a6a061f11bb4c2cb6a5c1a518e Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Sun, 25 Aug 2024 20:07:13 +0200 Subject: [PATCH] improve multipart decryption (#20324) This commit simplifies and optimizes the decryption of large (multipart) objects. This PR does two things: - Re-write the init logic for the decryption reader - Reduce the number of OEK decryptions Before, the init logic copied some SSE HTTP request headers to parse them later. This is simplified to parsing them right away. This removes some fields from the decryption reader struct. Further, the decryption reader decrypted the OEK using the client-provided key (SSE-C) or the KMS (SSE-S3 / SSE-KMS) for each part. This is redundant since the OEK is the same for all parts. In particular, a KMS call might be a network request. Now, the OEK is decrypted once for the entire multipart object. This should improve latency when reading encrypted multipart objects and reduce requests to the KMS. Signed-off-by: Andreas Auernhammer --- cmd/encryption-v1.go | 83 ++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index da9e97bc9..3b93492b1 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -601,28 +601,44 @@ func DecryptBlocksRequestR(inputReader io.Reader, h http.Header, seqNumber uint3 partEncRelOffset := int64(seqNumber) * (SSEDAREPackageBlockSize + SSEDAREPackageMetaSize) w := &DecryptBlocksReader{ - reader: inputReader, - startSeqNum: seqNumber, - partDecRelOffset: partDecRelOffset, - partEncRelOffset: partEncRelOffset, - parts: oi.Parts, - partIndex: partStart, - header: h, - bucket: bucket, - object: object, - customerKeyHeader: h.Get(xhttp.AmzServerSideEncryptionCustomerKey), - copySource: copySource, - metadata: cloneMSS(oi.UserDefined), + reader: inputReader, + startSeqNum: seqNumber, + partDecRelOffset: partDecRelOffset, + partEncRelOffset: partEncRelOffset, + parts: oi.Parts, + partIndex: partStart, } - if w.copySource { - w.customerKeyHeader = h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey) + // In case of SSE-C, we have to decrypt the OEK using the client-provided key. + // In case of a SSE-C server-side copy, the client might provide two keys, + // one for the source and one for the target. This reader is the source. + var ssecClientKey []byte + if crypto.SSEC.IsEncrypted(oi.UserDefined) { + if copySource && crypto.SSECopy.IsRequested(h) { + key, err := crypto.SSECopy.ParseHTTP(h) + if err != nil { + return nil, err + } + ssecClientKey = key[:] + } else { + key, err := crypto.SSEC.ParseHTTP(h) + if err != nil { + return nil, err + } + ssecClientKey = key[:] + } } + // Decrypt the OEK once and reuse it for all subsequent parts. + objectEncryptionKey, err := decryptObjectMeta(ssecClientKey, bucket, object, oi.UserDefined) + if err != nil { + return nil, err + } + w.objectEncryptionKey = objectEncryptionKey + if err := w.buildDecrypter(w.parts[w.partIndex].Number); err != nil { return nil, err } - return w, nil } @@ -638,48 +654,17 @@ type DecryptBlocksReader struct { // Current part index partIndex int // Parts information - parts []ObjectPartInfo - header http.Header - bucket, object string - metadata map[string]string + parts []ObjectPartInfo + objectEncryptionKey []byte partDecRelOffset, partEncRelOffset int64 - - copySource bool - // Customer Key - customerKeyHeader string } func (d *DecryptBlocksReader) buildDecrypter(partID int) error { - m := cloneMSS(d.metadata) - // Initialize the first decrypter; new decrypters will be - // initialized in Read() operation as needed. - var key []byte - var err error - if d.copySource { - if crypto.SSEC.IsEncrypted(d.metadata) { - d.header.Set(xhttp.AmzServerSideEncryptionCopyCustomerKey, d.customerKeyHeader) - key, err = ParseSSECopyCustomerRequest(d.header, d.metadata) - } - } else { - if crypto.SSEC.IsEncrypted(d.metadata) { - d.header.Set(xhttp.AmzServerSideEncryptionCustomerKey, d.customerKeyHeader) - key, err = ParseSSECustomerHeader(d.header) - } - } - if err != nil { - return err - } - - objectEncryptionKey, err := decryptObjectMeta(key, d.bucket, d.object, m) - if err != nil { - return err - } - var partIDbin [4]byte binary.LittleEndian.PutUint32(partIDbin[:], uint32(partID)) // marshal part ID - mac := hmac.New(sha256.New, objectEncryptionKey) // derive part encryption key from part ID and object key + mac := hmac.New(sha256.New, d.objectEncryptionKey) // derive part encryption key from part ID and object key mac.Write(partIDbin[:]) partEncryptionKey := mac.Sum(nil)