mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
encryption: Fix copy from encrypted multipart to single part (#6604)
CopyObject handler forgot to remove multipart encryption flag in metadata when source is an encrypted multipart object and the target is also encrypted but single part object. This PR also simplifies the code to facilitate review.
This commit is contained in:
parent
3ef3fefd54
commit
5b3090dffc
@ -80,6 +80,28 @@ func hasServerSideEncryptionHeader(header http.Header) bool {
|
|||||||
return crypto.S3.IsRequested(header) || crypto.SSEC.IsRequested(header)
|
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.
|
// ParseSSECopyCustomerRequest parses the SSE-C header fields of the provided request.
|
||||||
// It returns the client provided key on success.
|
// It returns the client provided key on success.
|
||||||
func ParseSSECopyCustomerRequest(h http.Header, metadata map[string]string) (key []byte, err error) {
|
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
|
// GetEncryptedOffsetLength - returns encrypted offset and length
|
||||||
// along with sequence number
|
// along with sequence number
|
||||||
func GetEncryptedOffsetLength(startOffset, length int64, objInfo ObjectInfo) (seqNumber uint32, encStartOffset, encLength int64) {
|
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)
|
seqNumber, encStartOffset, encLength = getEncryptedSinglePartOffsetLength(startOffset, length, objInfo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -378,7 +400,7 @@ func DecryptBlocksRequestR(inputReader io.Reader, h http.Header, offset,
|
|||||||
bucket, object := oi.Bucket, oi.Name
|
bucket, object := oi.Bucket, oi.Name
|
||||||
|
|
||||||
// Single part case
|
// Single part case
|
||||||
if len(oi.Parts) == 0 || !crypto.IsMultiPart(oi.UserDefined) {
|
if !isEncryptedMultipart(oi) {
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
var err error
|
var err error
|
||||||
if copySource {
|
if copySource {
|
||||||
@ -708,7 +730,7 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, bucket, object stri
|
|||||||
var seqNumber uint32
|
var seqNumber uint32
|
||||||
var encStartOffset, encLength int64
|
var encStartOffset, encLength int64
|
||||||
|
|
||||||
if len(objInfo.Parts) == 0 || !crypto.IsMultiPart(objInfo.UserDefined) {
|
if !isEncryptedMultipart(objInfo) {
|
||||||
seqNumber, encStartOffset, encLength = getEncryptedSinglePartOffsetLength(startOffset, length, objInfo)
|
seqNumber, encStartOffset, encLength = getEncryptedSinglePartOffsetLength(startOffset, length, objInfo)
|
||||||
|
|
||||||
var writer io.WriteCloser
|
var writer io.WriteCloser
|
||||||
@ -870,7 +892,7 @@ func (o *ObjectInfo) DecryptedSize() (int64, error) {
|
|||||||
if !crypto.IsEncrypted(o.UserDefined) {
|
if !crypto.IsEncrypted(o.UserDefined) {
|
||||||
return 0, errors.New("Cannot compute decrypted size of an unencrypted object")
|
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))
|
size, err := sio.DecryptedSize(uint64(o.Size))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errObjectTampered // assign correct error type
|
err = errObjectTampered // assign correct error type
|
||||||
@ -918,7 +940,7 @@ func (o *ObjectInfo) GetDecryptedRange(rs *HTTPRangeSpec) (encOff, encLength, sk
|
|||||||
}
|
}
|
||||||
sizes := []int64{int64(partSize)}
|
sizes := []int64{int64(partSize)}
|
||||||
decObjSize = sizes[0]
|
decObjSize = sizes[0]
|
||||||
if crypto.IsMultiPart(o.UserDefined) {
|
if isEncryptedMultipart(*o) {
|
||||||
sizes = make([]int64, len(o.Parts))
|
sizes = make([]int64, len(o.Parts))
|
||||||
decObjSize = 0
|
decObjSize = 0
|
||||||
for i, part := range o.Parts {
|
for i, part := range o.Parts {
|
||||||
|
@ -109,8 +109,12 @@ type ObjectInfo struct {
|
|||||||
Writer io.WriteCloser `json:"-"`
|
Writer io.WriteCloser `json:"-"`
|
||||||
Reader *hash.Reader `json:"-"`
|
Reader *hash.Reader `json:"-"`
|
||||||
metadataOnly bool
|
metadataOnly bool
|
||||||
|
|
||||||
// Date and time when the object was last accessed.
|
// Date and time when the object was last accessed.
|
||||||
AccTime time.Time
|
AccTime time.Time
|
||||||
|
|
||||||
|
// backendType indicates which backend filled this structure
|
||||||
|
backendType BackendType
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPartsInfo - represents list of all parts.
|
// ListPartsInfo - represents list of all parts.
|
||||||
|
@ -795,10 +795,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
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)
|
var encMetadata = make(map[string]string)
|
||||||
if objectAPI.IsEncryptionSupported() && !srcInfo.IsCompressed() {
|
if objectAPI.IsEncryptionSupported() && !srcInfo.IsCompressed() {
|
||||||
var oldKey, newKey []byte
|
var oldKey, newKey []byte
|
||||||
@ -806,10 +802,12 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
sseCopyC := crypto.SSECopy.IsRequested(r.Header)
|
sseCopyC := crypto.SSECopy.IsRequested(r.Header)
|
||||||
sseC := crypto.SSEC.IsRequested(r.Header)
|
sseC := crypto.SSEC.IsRequested(r.Header)
|
||||||
sseS3 := crypto.S3.IsRequested(r.Header)
|
sseS3 := crypto.S3.IsRequested(r.Header)
|
||||||
if sseC || sseS3 {
|
|
||||||
if sseC {
|
isSourceEncrypted := sseCopyC || sseCopyS3
|
||||||
newKey, err = ParseSSECustomerRequest(r)
|
isTargetEncrypted := sseC || sseS3
|
||||||
}
|
|
||||||
|
if sseC {
|
||||||
|
newKey, err = ParseSSECustomerRequest(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||||
return
|
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.
|
// Since we are rotating the keys, make sure to update the metadata.
|
||||||
srcInfo.metadataOnly = true
|
srcInfo.metadataOnly = true
|
||||||
} else {
|
} else {
|
||||||
if sseCopyC || sseCopyS3 {
|
if isSourceEncrypted || isTargetEncrypted {
|
||||||
// We are not only copying just metadata instead
|
// We are not only copying just metadata instead
|
||||||
// we are creating a new object at this point, even
|
// we are creating a new object at this point, even
|
||||||
// if source and destination are same objects.
|
// if source and destination are same objects.
|
||||||
srcInfo.metadataOnly = false
|
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)
|
reader, err = newEncryptReader(reader, newKey, dstBucket, dstObject, encMetadata, sseS3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||||
return
|
return
|
||||||
|
@ -218,6 +218,8 @@ func (m xlMetaV1) ToObjectInfo(bucket, object string) ObjectInfo {
|
|||||||
ContentEncoding: m.Meta["content-encoding"],
|
ContentEncoding: m.Meta["content-encoding"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
objInfo.backendType = BackendErasure
|
||||||
|
|
||||||
// Extract etag from metadata.
|
// Extract etag from metadata.
|
||||||
objInfo.ETag = extractETag(m.Meta)
|
objInfo.ETag = extractETag(m.Meta)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user