You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
7.2 KiB
257 lines
7.2 KiB
package coscrypto
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
|
|
"git.ouxuan.net/tommy/cos-go-sdk-v5"
|
|
)
|
|
|
|
type CryptoObjectService struct {
|
|
*cos.ObjectService
|
|
cryptoClient *CryptoClient
|
|
}
|
|
|
|
type CryptoClient struct {
|
|
*cos.Client
|
|
Object *CryptoObjectService
|
|
ContentCipherBuilder ContentCipherBuilder
|
|
|
|
userAgent string
|
|
}
|
|
|
|
func NewCryptoClient(client *cos.Client, masterCipher MasterCipher) *CryptoClient {
|
|
cc := &CryptoClient{
|
|
Client: client,
|
|
Object: &CryptoObjectService{
|
|
client.Object,
|
|
nil,
|
|
},
|
|
ContentCipherBuilder: CreateAesCtrBuilder(masterCipher),
|
|
}
|
|
cc.userAgent = cc.Client.UserAgent + "/" + EncryptionUaSuffix
|
|
cc.Object.cryptoClient = cc
|
|
|
|
return cc
|
|
}
|
|
|
|
func (s *CryptoObjectService) Put(ctx context.Context, name string, r io.Reader, opt *cos.ObjectPutOptions) (*cos.Response, error) {
|
|
cc, err := s.cryptoClient.ContentCipherBuilder.ContentCipher()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
reader, err := cc.EncryptContent(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opt = cos.CloneObjectPutOptions(opt)
|
|
totalBytes, err := cos.GetReaderLen(r)
|
|
if err != nil && opt != nil && opt.Listener != nil && opt.ContentLength == 0 {
|
|
return nil, err
|
|
}
|
|
if err == nil {
|
|
if opt != nil && opt.ContentLength == 0 {
|
|
// 如果未设置Listener, 非bytes.Buffer/bytes.Reader/strings.Reader由用户指定Contength
|
|
if opt.Listener != nil || cos.IsLenReader(r) {
|
|
opt.ContentLength = totalBytes
|
|
}
|
|
}
|
|
}
|
|
if opt.XOptionHeader == nil {
|
|
opt.XOptionHeader = &http.Header{}
|
|
}
|
|
if opt.ContentMD5 != "" {
|
|
opt.XOptionHeader.Add(COSClientSideEncryptionUnencryptedContentMD5, opt.ContentMD5)
|
|
opt.ContentMD5 = ""
|
|
}
|
|
if opt.ContentLength != 0 {
|
|
opt.XOptionHeader.Add(COSClientSideEncryptionUnencryptedContentLength, strconv.FormatInt(opt.ContentLength, 10))
|
|
opt.ContentLength = cc.GetEncryptedLen(opt.ContentLength)
|
|
}
|
|
opt.XOptionHeader.Add(UserAgent, s.cryptoClient.userAgent)
|
|
addCryptoHeaders(opt.XOptionHeader, cc.GetCipherData())
|
|
|
|
return s.ObjectService.Put(ctx, name, reader, opt)
|
|
}
|
|
|
|
func (s *CryptoObjectService) PutFromFile(ctx context.Context, name, filePath string, opt *cos.ObjectPutOptions) (resp *cos.Response, err error) {
|
|
nr := 0
|
|
for nr < 3 {
|
|
fd, e := os.Open(filePath)
|
|
if e != nil {
|
|
err = e
|
|
return
|
|
}
|
|
resp, err = s.Put(ctx, name, fd, opt)
|
|
if err != nil {
|
|
nr++
|
|
fd.Close()
|
|
continue
|
|
}
|
|
fd.Close()
|
|
break
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *CryptoObjectService) Get(ctx context.Context, name string, opt *cos.ObjectGetOptions, id ...string) (*cos.Response, error) {
|
|
meta, err := s.ObjectService.Head(ctx, name, nil, id...)
|
|
if err != nil {
|
|
return meta, err
|
|
}
|
|
_isEncrypted := isEncrypted(&meta.Header)
|
|
if !_isEncrypted {
|
|
return s.ObjectService.Get(ctx, name, opt, id...)
|
|
}
|
|
|
|
envelope, err := getEnvelopeFromHeader(&meta.Header)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !envelope.IsValid() {
|
|
return nil, fmt.Errorf("get envelope from header failed, object:%v", name)
|
|
}
|
|
encryptMatDesc := s.cryptoClient.ContentCipherBuilder.GetMatDesc()
|
|
if envelope.MatDesc != encryptMatDesc {
|
|
return nil, fmt.Errorf("provided master cipher error, want:%v, return:%v, object:%v", encryptMatDesc, envelope.MatDesc, name)
|
|
}
|
|
|
|
cc, err := s.cryptoClient.ContentCipherBuilder.ContentCipherEnv(envelope)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get content cipher from envelope failed: %v, object:%v", err, name)
|
|
}
|
|
|
|
opt = cos.CloneObjectGetOptions(opt)
|
|
if opt.XOptionHeader == nil {
|
|
opt.XOptionHeader = &http.Header{}
|
|
}
|
|
optRange, err := cos.GetRangeOptions(opt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
discardAlignLen := int64(0)
|
|
// Range请求
|
|
if optRange != nil && optRange.HasStart {
|
|
// 加密block对齐
|
|
adjustStart := adjustRangeStart(optRange.Start, int64(cc.GetAlignLen()))
|
|
discardAlignLen = optRange.Start - adjustStart
|
|
if discardAlignLen > 0 {
|
|
optRange.Start = adjustStart
|
|
opt.Range = cos.FormatRangeOptions(optRange)
|
|
}
|
|
|
|
cd := cc.GetCipherData().Clone()
|
|
cd.SeekIV(uint64(adjustStart))
|
|
cc, err = cc.Clone(cd)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ContentCipher Clone failed:%v, bject:%v", err, name)
|
|
}
|
|
}
|
|
opt.XOptionHeader.Add(UserAgent, s.cryptoClient.userAgent)
|
|
resp, err := s.ObjectService.Get(ctx, name, opt, id...)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
resp.Body, err = cc.DecryptContent(resp.Body)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
// 抛弃多读取的数据
|
|
if discardAlignLen > 0 {
|
|
resp.Body = &cos.DiscardReadCloser{
|
|
RC: resp.Body,
|
|
Discard: int(discardAlignLen),
|
|
}
|
|
}
|
|
return resp, err
|
|
}
|
|
|
|
func (s *CryptoObjectService) GetToFile(ctx context.Context, name, localpath string, opt *cos.ObjectGetOptions, id ...string) (*cos.Response, error) {
|
|
resp, err := s.Get(ctx, name, opt, id...)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// If file exist, overwrite it
|
|
fd, err := os.OpenFile(localpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
_, err = io.Copy(fd, resp.Body)
|
|
fd.Close()
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (s *CryptoObjectService) MultiUpload(ctx context.Context, name string, filepath string, opt *cos.MultiUploadOptions) (*cos.CompleteMultipartUploadResult, *cos.Response, error) {
|
|
return s.Upload(ctx, name, filepath, opt)
|
|
}
|
|
|
|
func (s *CryptoObjectService) Upload(ctx context.Context, name string, filepath string, opt *cos.MultiUploadOptions) (*cos.CompleteMultipartUploadResult, *cos.Response, error) {
|
|
return nil, nil, fmt.Errorf("CryptoObjectService doesn't support Upload Now")
|
|
}
|
|
|
|
func (s *CryptoObjectService) Download(ctx context.Context, name string, filepath string, opt *cos.MultiDownloadOptions) (*cos.Response, error) {
|
|
return nil, fmt.Errorf("CryptoObjectService doesn't support Download Now")
|
|
}
|
|
|
|
func adjustRangeStart(start int64, alignLen int64) int64 {
|
|
return (start / alignLen) * alignLen
|
|
}
|
|
|
|
func addCryptoHeaders(header *http.Header, cd *CipherData) {
|
|
if cd.MatDesc != "" {
|
|
header.Add(COSClientSideEncryptionMatDesc, cd.MatDesc)
|
|
}
|
|
// encrypted key
|
|
strEncryptedKey := base64.StdEncoding.EncodeToString(cd.EncryptedKey)
|
|
header.Add(COSClientSideEncryptionKey, strEncryptedKey)
|
|
|
|
// encrypted iv
|
|
strEncryptedIV := base64.StdEncoding.EncodeToString(cd.EncryptedIV)
|
|
header.Add(COSClientSideEncryptionStart, strEncryptedIV)
|
|
|
|
header.Add(COSClientSideEncryptionWrapAlg, cd.WrapAlgorithm)
|
|
header.Add(COSClientSideEncryptionCekAlg, cd.CEKAlgorithm)
|
|
}
|
|
|
|
func getEnvelopeFromHeader(header *http.Header) (Envelope, error) {
|
|
var envelope Envelope
|
|
|
|
envelope.CipherKey = header.Get(COSClientSideEncryptionKey)
|
|
decodedKey, err := base64.StdEncoding.DecodeString(envelope.CipherKey)
|
|
if err != nil {
|
|
return envelope, err
|
|
}
|
|
envelope.CipherKey = string(decodedKey)
|
|
|
|
envelope.IV = header.Get(COSClientSideEncryptionStart)
|
|
decodedIV, err := base64.StdEncoding.DecodeString(envelope.IV)
|
|
if err != nil {
|
|
return envelope, err
|
|
}
|
|
envelope.IV = string(decodedIV)
|
|
|
|
envelope.MatDesc = header.Get(COSClientSideEncryptionMatDesc)
|
|
envelope.WrapAlg = header.Get(COSClientSideEncryptionWrapAlg)
|
|
envelope.CEKAlg = header.Get(COSClientSideEncryptionCekAlg)
|
|
return envelope, nil
|
|
}
|
|
|
|
func isEncrypted(header *http.Header) bool {
|
|
encryptedKey := header.Get(COSClientSideEncryptionKey)
|
|
if len(encryptedKey) > 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|