diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index 75c76efc8..4e8759baf 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -80,6 +80,28 @@ func hasServerSideEncryptionHeader(header http.Header) bool { return crypto.S3.IsRequested(header) || crypto.SSEC.IsRequested(header) } +// isEncryptedMultipart returns true if the current object is +// uploaded by the user using multipart mechanism: +// initiate new multipart, upload part, complete upload +func isEncryptedMultipart(objInfo ObjectInfo) bool { + if len(objInfo.Parts) == 0 { + return false + } + if !crypto.IsMultiPart(objInfo.UserDefined) { + return false + } + for _, part := range objInfo.Parts { + _, err := sio.DecryptedSize(uint64(part.Size)) + if err != nil { + return false + } + } + // Further check if this object is uploaded using multipart mechanism + // by the user and it is not about XL internally splitting the + // object into parts in PutObject() + return !(objInfo.backendType == BackendErasure && len(objInfo.ETag) == 32) +} + // ParseSSECopyCustomerRequest parses the SSE-C header fields of the provided request. // It returns the client provided key on success. func ParseSSECopyCustomerRequest(h http.Header, metadata map[string]string) (key []byte, err error) { @@ -361,7 +383,7 @@ func newDecryptReaderWithObjectKey(client io.Reader, objectEncryptionKey []byte, // GetEncryptedOffsetLength - returns encrypted offset and length // along with sequence number func GetEncryptedOffsetLength(startOffset, length int64, objInfo ObjectInfo) (seqNumber uint32, encStartOffset, encLength int64) { - if len(objInfo.Parts) == 0 || !crypto.IsMultiPart(objInfo.UserDefined) { + if !isEncryptedMultipart(objInfo) { seqNumber, encStartOffset, encLength = getEncryptedSinglePartOffsetLength(startOffset, length, objInfo) return } @@ -378,7 +400,7 @@ func DecryptBlocksRequestR(inputReader io.Reader, h http.Header, offset, bucket, object := oi.Bucket, oi.Name // Single part case - if len(oi.Parts) == 0 || !crypto.IsMultiPart(oi.UserDefined) { + if !isEncryptedMultipart(oi) { var reader io.Reader var err error if copySource { @@ -708,7 +730,7 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, bucket, object stri var seqNumber uint32 var encStartOffset, encLength int64 - if len(objInfo.Parts) == 0 || !crypto.IsMultiPart(objInfo.UserDefined) { + if !isEncryptedMultipart(objInfo) { seqNumber, encStartOffset, encLength = getEncryptedSinglePartOffsetLength(startOffset, length, objInfo) var writer io.WriteCloser @@ -870,7 +892,7 @@ func (o *ObjectInfo) DecryptedSize() (int64, error) { if !crypto.IsEncrypted(o.UserDefined) { return 0, errors.New("Cannot compute decrypted size of an unencrypted object") } - if len(o.Parts) == 0 || !crypto.IsMultiPart(o.UserDefined) { + if !isEncryptedMultipart(*o) { size, err := sio.DecryptedSize(uint64(o.Size)) if err != nil { err = errObjectTampered // assign correct error type @@ -918,7 +940,7 @@ func (o *ObjectInfo) GetDecryptedRange(rs *HTTPRangeSpec) (encOff, encLength, sk } sizes := []int64{int64(partSize)} decObjSize = sizes[0] - if crypto.IsMultiPart(o.UserDefined) { + if isEncryptedMultipart(*o) { sizes = make([]int64, len(o.Parts)) decObjSize = 0 for i, part := range o.Parts { diff --git a/cmd/object-api-datatypes.go b/cmd/object-api-datatypes.go index 3b2208b50..6eb7fd2d4 100644 --- a/cmd/object-api-datatypes.go +++ b/cmd/object-api-datatypes.go @@ -109,8 +109,12 @@ type ObjectInfo struct { Writer io.WriteCloser `json:"-"` Reader *hash.Reader `json:"-"` metadataOnly bool + // Date and time when the object was last accessed. AccTime time.Time + + // backendType indicates which backend filled this structure + backendType BackendType } // ListPartsInfo - represents list of all parts. diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index af06697b1..c85ab1d50 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -795,10 +795,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re return } - // Save the original size for later use when we want to copy - // encrypted file into an unencrypted one. - size := srcInfo.Size - var encMetadata = make(map[string]string) if objectAPI.IsEncryptionSupported() && !srcInfo.IsCompressed() { var oldKey, newKey []byte @@ -806,10 +802,12 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re sseCopyC := crypto.SSECopy.IsRequested(r.Header) sseC := crypto.SSEC.IsRequested(r.Header) sseS3 := crypto.S3.IsRequested(r.Header) - if sseC || sseS3 { - if sseC { - newKey, err = ParseSSECustomerRequest(r) - } + + isSourceEncrypted := sseCopyC || sseCopyS3 + isTargetEncrypted := sseC || sseS3 + + if sseC { + newKey, err = ParseSSECustomerRequest(r) if err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return @@ -836,42 +834,49 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re // Since we are rotating the keys, make sure to update the metadata. srcInfo.metadataOnly = true } else { - if sseCopyC || sseCopyS3 { + if isSourceEncrypted || isTargetEncrypted { // We are not only copying just metadata instead // we are creating a new object at this point, even // if source and destination are same objects. srcInfo.metadataOnly = false - if sseC || sseS3 { - size = srcInfo.Size - } } - if sseC || sseS3 { + + // Calculate the size of the target object + var targetSize int64 + + switch { + case !isSourceEncrypted && !isTargetEncrypted: + fallthrough + case isSourceEncrypted && isTargetEncrypted: + targetSize = srcInfo.Size + // Source not encrypted and target encrypted + case !isSourceEncrypted && isTargetEncrypted: + targetSize = srcInfo.EncryptedSize() + case isSourceEncrypted && !isTargetEncrypted: + targetSize, _ = srcInfo.DecryptedSize() + } + + if isTargetEncrypted { reader, err = newEncryptReader(reader, newKey, dstBucket, dstObject, encMetadata, sseS3) if err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } - // We are not only copying just metadata instead - // we are creating a new object at this point, even - // if source and destination are same objects. - srcInfo.metadataOnly = false - if !sseCopyC && !sseCopyS3 { - size = srcInfo.EncryptedSize() - } - } else { - if sseCopyC || sseCopyS3 { - size, _ = srcInfo.DecryptedSize() - delete(srcInfo.UserDefined, crypto.SSEIV) - delete(srcInfo.UserDefined, crypto.SSESealAlgorithm) - delete(srcInfo.UserDefined, crypto.SSECSealedKey) - delete(srcInfo.UserDefined, crypto.SSEMultipart) - delete(srcInfo.UserDefined, crypto.S3SealedKey) - delete(srcInfo.UserDefined, crypto.S3KMSSealedKey) - delete(srcInfo.UserDefined, crypto.S3KMSKeyID) - } } - srcInfo.Reader, err = hash.NewReader(reader, size, "", "", size) // do not try to verify encrypted content + if isSourceEncrypted { + // Remove all source encrypted related metadata to + // avoid copying them in target object. + delete(srcInfo.UserDefined, crypto.SSEIV) + delete(srcInfo.UserDefined, crypto.SSESealAlgorithm) + delete(srcInfo.UserDefined, crypto.SSECSealedKey) + delete(srcInfo.UserDefined, crypto.SSEMultipart) + delete(srcInfo.UserDefined, crypto.S3SealedKey) + delete(srcInfo.UserDefined, crypto.S3KMSSealedKey) + delete(srcInfo.UserDefined, crypto.S3KMSKeyID) + } + + srcInfo.Reader, err = hash.NewReader(reader, targetSize, "", "", targetSize) // do not try to verify encrypted content if err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return diff --git a/cmd/xl-v1-metadata.go b/cmd/xl-v1-metadata.go index 00f6e1407..9cfda2039 100644 --- a/cmd/xl-v1-metadata.go +++ b/cmd/xl-v1-metadata.go @@ -218,6 +218,8 @@ func (m xlMetaV1) ToObjectInfo(bucket, object string) ObjectInfo { ContentEncoding: m.Meta["content-encoding"], } + objInfo.backendType = BackendErasure + // Extract etag from metadata. objInfo.ETag = extractETag(m.Meta)