oss/encryption_client.go (375 lines of code) (raw):
package oss
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/crypto"
)
// EncryptionUaSuffix user agent tag for client encryption
const (
EncryptionUaSuffix string = "OssEncryptionClient"
)
type EncryptionClientOptions struct {
MasterCiphers []crypto.MasterCipher
}
type EncryptionClient struct {
client *Client
defualtCCBuilder crypto.ContentCipherBuilder
ccBuilderMap map[string]crypto.ContentCipherBuilder
alignLen int
}
// EncryptionMultiPartContext save encryption or decryption information
type EncryptionMultiPartContext struct {
ContentCipher crypto.ContentCipher
DataSize int64
PartSize int64
}
// Valid judge PartCryptoContext is valid or not
func (ec EncryptionMultiPartContext) Valid() bool {
if ec.ContentCipher == nil || ec.DataSize == 0 || ec.PartSize == 0 {
return false
}
return true
}
func NewEncryptionClient(c *Client, masterCipher crypto.MasterCipher, optFns ...func(*EncryptionClientOptions)) (*EncryptionClient, error) {
options := EncryptionClientOptions{}
for _, fn := range optFns {
fn(&options)
}
if masterCipher == nil {
return nil, NewErrParamNull("masterCipher")
}
defualtCCBuilder := crypto.CreateAesCtrCipher(masterCipher)
ccBuilderMap := map[string]crypto.ContentCipherBuilder{}
for _, m := range options.MasterCiphers {
if m != nil && len(m.GetMatDesc()) > 0 {
ccBuilderMap[m.GetMatDesc()] = crypto.CreateAesCtrCipher(m)
}
}
e := &EncryptionClient{
client: c,
defualtCCBuilder: defualtCCBuilder,
ccBuilderMap: ccBuilderMap,
alignLen: 16,
}
return e, nil
}
func (e *EncryptionClient) Unwrap() *Client { return e.client }
// GetObjectMeta Queries the metadata of an object, including ETag, Size, and LastModified.
// The content of the object is not returned.
func (e *EncryptionClient) GetObjectMeta(ctx context.Context, request *GetObjectMetaRequest, optFns ...func(*Options)) (*GetObjectMetaResult, error) {
return e.client.GetObjectMeta(ctx, request, optFns...)
}
// HeadObject Queries information about all objects in a bucket.
func (e *EncryptionClient) HeadObject(ctx context.Context, request *HeadObjectRequest, optFns ...func(*Options)) (*HeadObjectResult, error) {
return e.client.HeadObject(ctx, request, optFns...)
}
// GetObject Downloads a object.
func (e *EncryptionClient) GetObject(ctx context.Context, request *GetObjectRequest, optFns ...func(*Options)) (*GetObjectResult, error) {
return e.getObjectSecurely(ctx, request, optFns...)
}
// PutObject Uploads a object.
func (e *EncryptionClient) PutObject(ctx context.Context, request *PutObjectRequest, optFns ...func(*Options)) (*PutObjectResult, error) {
return e.putObjectSecurely(ctx, request, optFns...)
}
// InitiateMultipartUpload Initiates a multipart upload task before you can upload data in parts to Object Storage Service (OSS).
func (e *EncryptionClient) InitiateMultipartUpload(ctx context.Context, request *InitiateMultipartUploadRequest, optFns ...func(*Options)) (*InitiateMultipartUploadResult, error) {
return e.initiateMultipartUploadSecurely(ctx, request, optFns...)
}
// UploadPart Call the UploadPart interface to upload data in blocks (parts) based on the specified Object name and uploadId.
func (e *EncryptionClient) UploadPart(ctx context.Context, request *UploadPartRequest, optFns ...func(*Options)) (*UploadPartResult, error) {
return e.uploadPartSecurely(ctx, request, optFns...)
}
// CompleteMultipartUpload Completes the multipart upload task of an object after all parts of the object are uploaded.
func (e *EncryptionClient) CompleteMultipartUpload(ctx context.Context, request *CompleteMultipartUploadRequest, optFns ...func(*Options)) (*CompleteMultipartUploadResult, error) {
return e.client.CompleteMultipartUpload(ctx, request, optFns...)
}
// AbortMultipartUpload Cancels a multipart upload task and deletes the parts uploaded in the task.
func (e *EncryptionClient) AbortMultipartUpload(ctx context.Context, request *AbortMultipartUploadRequest, optFns ...func(*Options)) (*AbortMultipartUploadResult, error) {
return e.client.AbortMultipartUpload(ctx, request, optFns...)
}
// ListParts Lists all parts that are uploaded by using a specified upload ID.
func (e *EncryptionClient) ListParts(ctx context.Context, request *ListPartsRequest, optFns ...func(*Options)) (*ListPartsResult, error) {
return e.client.ListParts(ctx, request, optFns...)
}
// NewDownloader creates a new Downloader instance to download objects.
func (c *EncryptionClient) NewDownloader(optFns ...func(*DownloaderOptions)) *Downloader {
return NewDownloader(c, optFns...)
}
// NewUploader creates a new Uploader instance to upload objects.
func (c *EncryptionClient) NewUploader(optFns ...func(*UploaderOptions)) *Uploader {
return NewUploader(c, optFns...)
}
// OpenFile opens the named file for reading.
func (c *EncryptionClient) OpenFile(ctx context.Context, bucket string, key string, optFns ...func(*OpenOptions)) (*ReadOnlyFile, error) {
return NewReadOnlyFile(ctx, c, bucket, key, optFns...)
}
func (e *EncryptionClient) getObjectSecurely(ctx context.Context, request *GetObjectRequest, optFns ...func(*Options)) (*GetObjectResult, error) {
if request == nil {
return nil, NewErrParamNull("request")
}
var (
err error
httpRange *HTTPRange
discardCount int64 = 0
adjustOffset int64 = 0
closeBody bool = true
)
if request.Range != nil {
httpRange, err = ParseRange(*request.Range)
if err != nil {
return nil, err
}
offset := httpRange.Offset
count := httpRange.Count
adjustOffset = adjustRangeStart(offset, int64(e.alignLen))
discardCount = httpRange.Offset - adjustOffset
if discardCount != 0 {
if count > 0 {
count += discardCount
}
httpRange.Offset = adjustOffset
httpRange.Count = count
}
}
eRequest := request
if httpRange != nil && discardCount > 0 {
_request := *request
eRequest = &_request
eRequest.Range = httpRange.FormatHTTPRange()
eRequest.RangeBehavior = Ptr("standard")
}
result, err := e.client.GetObject(ctx, eRequest, optFns...)
if err != nil {
return nil, err
}
defer func() {
if closeBody && result.Body != nil {
result.Body.Close()
}
}()
if hasEncryptedHeader(result.Headers) {
envelope, err := getEnvelopeFromHeader(result.Headers)
if err != nil {
return nil, err
}
if !isValidContentAlg(envelope.CEKAlg) {
return nil, fmt.Errorf("not supported content algorithm %s,object:%s", envelope.CEKAlg, ToString(request.Key))
}
if !envelope.IsValid() {
return nil, fmt.Errorf("getEnvelopeFromHeader error,object:%s", ToString(request.Key))
}
// use ContentCipherBuilder to decrpt object by default
cc, err := e.getContentCipherBuilder(envelope).ContentCipherEnv(envelope)
if err != nil {
return nil, fmt.Errorf("%s,object:%s", err.Error(), ToString(request.Key))
}
if adjustOffset > 0 {
cipherData := cc.GetCipherData().Clone()
cipherData.SeekIV(uint64(adjustOffset))
cc, _ = cc.Clone(cipherData)
}
result.Body, err = cc.DecryptContent(result.Body)
}
if discardCount > 0 && err == nil {
//rewrite ContentRange & ContentRange
if result.ContentRange != nil {
if from, to, total, cerr := ParseContentRange(*result.ContentRange); cerr == nil {
from += discardCount
value := fmt.Sprintf("bytes %v-%v/%v", from, to, total)
result.ContentRange = Ptr(value)
result.Headers.Set(HTTPHeaderContentRange, value)
}
} else {
result.Headers.Set(HTTPHeaderContentRange, fmt.Sprintf("bytes %v-/*", discardCount))
}
if result.ContentLength > 0 {
result.ContentLength -= discardCount
result.Headers.Set(HTTPHeaderContentLength, fmt.Sprint(result.ContentLength))
}
result.Body = &DiscardReadCloser{
RC: result.Body,
Discard: int(discardCount),
}
}
closeBody = false
return result, err
}
func (e *EncryptionClient) putObjectSecurely(ctx context.Context, request *PutObjectRequest, optFns ...func(*Options)) (*PutObjectResult, error) {
if request == nil {
return nil, NewErrParamNull("request")
}
cc, err := e.defualtCCBuilder.ContentCipher()
if err != nil {
return nil, err
}
cryptoReader, err := cc.EncryptContent(request.Body)
if err != nil {
return nil, err
}
eRequest := *request
eRequest.Body = cryptoReader
addCryptoHeaders(&eRequest, cc.GetCipherData())
return e.client.PutObject(ctx, &eRequest, optFns...)
}
func (e *EncryptionClient) initiateMultipartUploadSecurely(ctx context.Context, request *InitiateMultipartUploadRequest, optFns ...func(*Options)) (*InitiateMultipartUploadResult, error) {
var err error
if request == nil {
return nil, NewErrParamNull("request")
}
if err = e.validEncryptionContext(request); err != nil {
return nil, err
}
cc, err := e.defualtCCBuilder.ContentCipher()
if err != nil {
return nil, err
}
eRequest := *request
addMultiPartCryptoHeaders(&eRequest, cc.GetCipherData())
result, err := e.client.InitiateMultipartUpload(ctx, &eRequest, optFns...)
if err != nil {
return nil, err
}
result.CSEMultiPartContext = &EncryptionMultiPartContext{
ContentCipher: cc,
PartSize: ToInt64(request.CSEPartSize),
DataSize: ToInt64(request.CSEDataSize),
}
return result, nil
}
func (e *EncryptionClient) uploadPartSecurely(ctx context.Context, request *UploadPartRequest, optFns ...func(*Options)) (*UploadPartResult, error) {
if request == nil {
return nil, NewErrParamNull("request")
}
if request.CSEMultiPartContext == nil {
return nil, NewErrParamNull("request.CSEMultiPartContext")
}
cseCtx := request.CSEMultiPartContext
if !cseCtx.Valid() {
return nil, fmt.Errorf("request.CSEMultiPartContext is invalid")
}
if cseCtx.PartSize%int64(e.alignLen) != 0 {
return nil, fmt.Errorf("CSEMultiPartContext's PartSize must be aligned to %v", e.alignLen)
}
cipherData := cseCtx.ContentCipher.GetCipherData().Clone()
// caclulate iv based on part number
if request.PartNumber > 1 {
cipherData.SeekIV(uint64(request.PartNumber-1) * uint64(cseCtx.PartSize))
}
// for parallel upload part
cc, _ := cseCtx.ContentCipher.Clone(cipherData)
cryptoReader, err := cc.EncryptContent(request.Body)
if err != nil {
return nil, err
}
eRequest := *request
eRequest.Body = cryptoReader
addUploadPartCryptoHeaders(&eRequest, cseCtx, cc.GetCipherData())
return e.client.UploadPart(ctx, &eRequest, optFns...)
}
func (e *EncryptionClient) getContentCipherBuilder(envelope crypto.Envelope) crypto.ContentCipherBuilder {
if ccb, ok := e.ccBuilderMap[envelope.MatDesc]; ok {
return ccb
}
return e.defualtCCBuilder
}
func (e *EncryptionClient) validEncryptionContext(request *InitiateMultipartUploadRequest) error {
partSize := ToInt64(request.CSEPartSize)
if partSize <= 0 {
return NewErrParamInvalid("request.CSEPartSize")
}
if partSize%int64(e.alignLen) != 0 {
return fmt.Errorf("request.CSEPartSize must aligned to the %v", e.alignLen)
}
return nil
}
func hasEncryptedHeader(headers http.Header) bool {
return len(headers.Get(OssClientSideEncryptionKey)) > 0
}
// addCryptoHeaders save Envelope information in oss meta
func addCryptoHeaders(request *PutObjectRequest, cd *crypto.CipherData) {
if request.Headers == nil {
request.Headers = map[string]string{}
}
// convert content-md5
if request.ContentMD5 != nil {
request.Headers[OssClientSideEncryptionUnencryptedContentMD5] = *request.ContentMD5
request.ContentMD5 = nil
}
// convert content-length
if request.ContentLength != nil {
request.Headers[OssClientSideEncryptionUnencryptedContentLength] = fmt.Sprint(*request.ContentLength)
request.ContentLength = nil
}
// matDesc
if len(cd.MatDesc) > 0 {
request.Headers[OssClientSideEncryptionMatDesc] = cd.MatDesc
}
// encrypted key
strEncryptedKey := base64.StdEncoding.EncodeToString(cd.EncryptedKey)
request.Headers[OssClientSideEncryptionKey] = strEncryptedKey
// encrypted iv
strEncryptedIV := base64.StdEncoding.EncodeToString(cd.EncryptedIV)
request.Headers[OssClientSideEncryptionStart] = strEncryptedIV
// wrap alg
request.Headers[OssClientSideEncryptionWrapAlg] = cd.WrapAlgorithm
// cek alg
request.Headers[OssClientSideEncryptionCekAlg] = cd.CEKAlgorithm
}
// addMultiPartCryptoHeaders save Envelope information in oss meta
func addMultiPartCryptoHeaders(request *InitiateMultipartUploadRequest, cd *crypto.CipherData) {
if request.Headers == nil {
request.Headers = map[string]string{}
}
// matDesc
if len(cd.MatDesc) > 0 {
request.Headers[OssClientSideEncryptionMatDesc] = cd.MatDesc
}
if ToInt64(request.CSEDataSize) > 0 {
request.Headers[OssClientSideEncryptionDataSize] = fmt.Sprint(*request.CSEDataSize)
}
request.Headers[OssClientSideEncryptionPartSize] = fmt.Sprint(*request.CSEPartSize)
// encrypted key
strEncryptedKey := base64.StdEncoding.EncodeToString(cd.EncryptedKey)
request.Headers[OssClientSideEncryptionKey] = strEncryptedKey
// encrypted iv
strEncryptedIV := base64.StdEncoding.EncodeToString(cd.EncryptedIV)
request.Headers[OssClientSideEncryptionStart] = strEncryptedIV
// wrap alg
request.Headers[OssClientSideEncryptionWrapAlg] = cd.WrapAlgorithm
// cek alg
request.Headers[OssClientSideEncryptionCekAlg] = cd.CEKAlgorithm
}
// addUploadPartCryptoHeaders save Envelope information in oss meta
func addUploadPartCryptoHeaders(request *UploadPartRequest, cseContext *EncryptionMultiPartContext, cd *crypto.CipherData) {
if request.Headers == nil {
request.Headers = map[string]string{}
}
// matDesc
if len(cd.MatDesc) > 0 {
request.Headers[OssClientSideEncryptionMatDesc] = cd.MatDesc
}
if cseContext.DataSize > 0 {
request.Headers[OssClientSideEncryptionDataSize] = fmt.Sprint(cseContext.DataSize)
}
request.Headers[OssClientSideEncryptionPartSize] = fmt.Sprint(cseContext.PartSize)
// encrypted key
strEncryptedKey := base64.StdEncoding.EncodeToString(cd.EncryptedKey)
request.Headers[OssClientSideEncryptionKey] = strEncryptedKey
// encrypted iv
strEncryptedIV := base64.StdEncoding.EncodeToString(cd.EncryptedIV)
request.Headers[OssClientSideEncryptionStart] = strEncryptedIV
// wrap alg
request.Headers[OssClientSideEncryptionWrapAlg] = cd.WrapAlgorithm
// cek alg
request.Headers[OssClientSideEncryptionCekAlg] = cd.CEKAlgorithm
}
func isValidContentAlg(algName string) bool {
// now content encyrption only support aec/ctr algorithm
return algName == crypto.AesCtrAlgorithm
}
func adjustRangeStart(start, align int64) int64 {
return (start / align) * align
}
func getEnvelopeFromHeader(header http.Header) (crypto.Envelope, error) {
var envelope crypto.Envelope
envelope.IV = header.Get(OssClientSideEncryptionStart)
decodedIV, err := base64.StdEncoding.DecodeString(envelope.IV)
if err != nil {
return envelope, err
}
envelope.IV = string(decodedIV)
envelope.CipherKey = header.Get(OssClientSideEncryptionKey)
decodedKey, err := base64.StdEncoding.DecodeString(envelope.CipherKey)
if err != nil {
return envelope, err
}
envelope.CipherKey = string(decodedKey)
envelope.MatDesc = header.Get(OssClientSideEncryptionMatDesc)
envelope.WrapAlg = header.Get(OssClientSideEncryptionWrapAlg)
envelope.CEKAlg = header.Get(OssClientSideEncryptionCekAlg)
envelope.UnencryptedMD5 = header.Get(OssClientSideEncryptionUnencryptedContentMD5)
envelope.UnencryptedContentLen = header.Get(OssClientSideEncryptionUnencryptedContentLength)
return envelope, err
}
func getEnvelopeFromListParts(result *ListPartsResult) (crypto.Envelope, error) {
var envelope crypto.Envelope
if result == nil {
return envelope, NewErrParamNull("result.*ListPartsResult")
}
envelope.IV = ToString(result.ClientEncryptionStart)
decodedIV, err := base64.StdEncoding.DecodeString(envelope.IV)
if err != nil {
return envelope, err
}
envelope.IV = string(decodedIV)
envelope.CipherKey = ToString(result.ClientEncryptionKey)
decodedKey, err := base64.StdEncoding.DecodeString(envelope.CipherKey)
if err != nil {
return envelope, err
}
envelope.CipherKey = string(decodedKey)
envelope.WrapAlg = ToString(result.ClientEncryptionWrapAlg)
envelope.CEKAlg = ToString(result.ClientEncryptionCekAlg)
return envelope, err
}