7
ci.go
7
ci.go
@@ -271,10 +271,13 @@ func (s *CIService) Put(ctx context.Context, name string, r io.Reader, uopt *Obj
|
||||
if err := CheckReaderLen(r); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt := cloneObjectPutOptions(uopt)
|
||||
opt := CloneObjectPutOptions(uopt)
|
||||
totalBytes, err := GetReaderLen(r)
|
||||
if err != nil && opt != nil && opt.Listener != nil {
|
||||
return nil, nil, err
|
||||
if opt.ContentLength == 0 {
|
||||
return nil, nil, err
|
||||
}
|
||||
totalBytes = opt.ContentLength
|
||||
}
|
||||
if err == nil {
|
||||
// 与 go http 保持一致, 非bytes.Buffer/bytes.Reader/strings.Reader由用户指定ContentLength, 或使用 Chunk 上传
|
||||
|
||||
28
cos.go
28
cos.go
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version current go sdk version
|
||||
Version = "0.7.25"
|
||||
Version = "0.7.26"
|
||||
userAgent = "cos-go-sdk-v5/" + Version
|
||||
contentTypeXML = "application/xml"
|
||||
defaultServiceBaseURL = "http://service.cos.myqcloud.com"
|
||||
@@ -133,6 +133,26 @@ func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
|
||||
return c
|
||||
}
|
||||
|
||||
type Credential struct {
|
||||
SecretID string
|
||||
SecretKey string
|
||||
SessionToken string
|
||||
}
|
||||
|
||||
func (c *Client) GetCredential() *Credential {
|
||||
auth, ok := c.client.Transport.(*AuthorizationTransport)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
auth.rwLocker.Lock()
|
||||
defer auth.rwLocker.Unlock()
|
||||
return &Credential{
|
||||
SecretID: auth.SecretID,
|
||||
SecretKey: auth.SecretKey,
|
||||
SessionToken: auth.SessionToken,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(ctx context.Context, baseURL *url.URL, uri, method string, body interface{}, optQuery interface{}, optHeader interface{}) (req *http.Request, err error) {
|
||||
uri, err = addURLOptions(uri, optQuery)
|
||||
if err != nil {
|
||||
@@ -175,8 +195,10 @@ func (c *Client) newRequest(ctx context.Context, baseURL *url.URL, uri, method s
|
||||
if contentMD5 != "" {
|
||||
req.Header["Content-MD5"] = []string{contentMD5}
|
||||
}
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set("User-Agent", c.UserAgent)
|
||||
if v := req.Header.Get("User-Agent"); v == "" || !strings.HasPrefix(v, userAgent) {
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set("User-Agent", c.UserAgent)
|
||||
}
|
||||
}
|
||||
if req.Header.Get("Content-Type") == "" && contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
||||
67
crypto/aes_ctr.go
Normal file
67
crypto/aes_ctr.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package coscrypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"io"
|
||||
)
|
||||
|
||||
type aesCtr struct {
|
||||
encrypter cipher.Stream
|
||||
decrypter cipher.Stream
|
||||
}
|
||||
|
||||
func newAesCtr(cd CipherData) (Cipher, error) {
|
||||
block, err := aes.NewCipher(cd.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encrypter := cipher.NewCTR(block, cd.IV)
|
||||
decrypter := cipher.NewCTR(block, cd.IV)
|
||||
return &aesCtr{encrypter, decrypter}, nil
|
||||
}
|
||||
|
||||
func (c *aesCtr) Encrypt(src io.Reader) io.Reader {
|
||||
reader := &ctrEncryptReader{
|
||||
encrypter: c.encrypter,
|
||||
src: src,
|
||||
}
|
||||
return reader
|
||||
}
|
||||
|
||||
type ctrEncryptReader struct {
|
||||
encrypter cipher.Stream
|
||||
src io.Reader
|
||||
}
|
||||
|
||||
func (reader *ctrEncryptReader) Read(data []byte) (int, error) {
|
||||
plainText := make([]byte, len(data), len(data))
|
||||
n, err := reader.src.Read(plainText)
|
||||
if n > 0 {
|
||||
plainText = plainText[0:n]
|
||||
reader.encrypter.XORKeyStream(data, plainText)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *aesCtr) Decrypt(src io.Reader) io.Reader {
|
||||
return &ctrDecryptReader{
|
||||
decrypter: c.decrypter,
|
||||
src: src,
|
||||
}
|
||||
}
|
||||
|
||||
type ctrDecryptReader struct {
|
||||
decrypter cipher.Stream
|
||||
src io.Reader
|
||||
}
|
||||
|
||||
func (reader *ctrDecryptReader) Read(data []byte) (int, error) {
|
||||
cryptoText := make([]byte, len(data), len(data))
|
||||
n, err := reader.src.Read(cryptoText)
|
||||
if n > 0 {
|
||||
cryptoText = cryptoText[0:n]
|
||||
reader.decrypter.XORKeyStream(data, cryptoText)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
138
crypto/aes_ctr_cipher.go
Normal file
138
crypto/aes_ctr_cipher.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package coscrypto
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
aesKeySize = 32
|
||||
ivSize = 16
|
||||
)
|
||||
|
||||
type aesCtrCipherBuilder struct {
|
||||
MasterCipher MasterCipher
|
||||
}
|
||||
|
||||
type aesCtrCipher struct {
|
||||
CipherData CipherData
|
||||
Cipher Cipher
|
||||
}
|
||||
|
||||
func CreateAesCtrBuilder(cipher MasterCipher) ContentCipherBuilder {
|
||||
return aesCtrCipherBuilder{MasterCipher: cipher}
|
||||
}
|
||||
|
||||
func (builder aesCtrCipherBuilder) createCipherData() (CipherData, error) {
|
||||
var cd CipherData
|
||||
var err error
|
||||
err = cd.RandomKeyIv(aesKeySize, ivSize)
|
||||
if err != nil {
|
||||
return cd, err
|
||||
}
|
||||
|
||||
cd.WrapAlgorithm = builder.MasterCipher.GetWrapAlgorithm()
|
||||
cd.CEKAlgorithm = AesCtrAlgorithm
|
||||
cd.MatDesc = builder.MasterCipher.GetMatDesc()
|
||||
|
||||
// EncryptedKey
|
||||
cd.EncryptedKey, err = builder.MasterCipher.Encrypt(cd.Key)
|
||||
if err != nil {
|
||||
return cd, err
|
||||
}
|
||||
|
||||
// EncryptedIV
|
||||
cd.EncryptedIV, err = builder.MasterCipher.Encrypt(cd.IV)
|
||||
if err != nil {
|
||||
return cd, err
|
||||
}
|
||||
|
||||
return cd, nil
|
||||
}
|
||||
|
||||
func (builder aesCtrCipherBuilder) contentCipherCD(cd CipherData) (ContentCipher, error) {
|
||||
cipher, err := newAesCtr(cd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &aesCtrCipher{
|
||||
CipherData: cd,
|
||||
Cipher: cipher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (builder aesCtrCipherBuilder) ContentCipher() (ContentCipher, error) {
|
||||
cd, err := builder.createCipherData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.contentCipherCD(cd)
|
||||
}
|
||||
|
||||
func (builder aesCtrCipherBuilder) ContentCipherEnv(envelope Envelope) (ContentCipher, error) {
|
||||
var cd CipherData
|
||||
cd.EncryptedKey = make([]byte, len(envelope.CipherKey))
|
||||
copy(cd.EncryptedKey, []byte(envelope.CipherKey))
|
||||
|
||||
plainKey, err := builder.MasterCipher.Decrypt([]byte(envelope.CipherKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cd.Key = make([]byte, len(plainKey))
|
||||
copy(cd.Key, plainKey)
|
||||
|
||||
cd.EncryptedIV = make([]byte, len(envelope.IV))
|
||||
copy(cd.EncryptedIV, []byte(envelope.IV))
|
||||
|
||||
plainIV, err := builder.MasterCipher.Decrypt([]byte(envelope.IV))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cd.IV = make([]byte, len(plainIV))
|
||||
copy(cd.IV, plainIV)
|
||||
|
||||
cd.MatDesc = envelope.MatDesc
|
||||
cd.WrapAlgorithm = envelope.WrapAlg
|
||||
cd.CEKAlgorithm = envelope.CEKAlg
|
||||
|
||||
return builder.contentCipherCD(cd)
|
||||
}
|
||||
|
||||
func (builder aesCtrCipherBuilder) GetMatDesc() string {
|
||||
return builder.MasterCipher.GetMatDesc()
|
||||
}
|
||||
|
||||
func (cc *aesCtrCipher) EncryptContent(src io.Reader) (io.ReadCloser, error) {
|
||||
reader := cc.Cipher.Encrypt(src)
|
||||
return &CryptoEncrypter{Body: src, Encrypter: reader}, nil
|
||||
}
|
||||
|
||||
func (cc *aesCtrCipher) DecryptContent(src io.Reader) (io.ReadCloser, error) {
|
||||
reader := cc.Cipher.Decrypt(src)
|
||||
return &CryptoDecrypter{Body: src, Decrypter: reader}, nil
|
||||
}
|
||||
|
||||
func (cc *aesCtrCipher) GetCipherData() *CipherData {
|
||||
return &(cc.CipherData)
|
||||
}
|
||||
|
||||
func (cc *aesCtrCipher) GetEncryptedLen(plainTextLen int64) int64 {
|
||||
return plainTextLen
|
||||
}
|
||||
|
||||
func (cc *aesCtrCipher) GetAlignLen() int {
|
||||
return len(cc.CipherData.IV)
|
||||
}
|
||||
|
||||
func (cc *aesCtrCipher) Clone(cd CipherData) (ContentCipher, error) {
|
||||
cipher, err := newAesCtr(cd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &aesCtrCipher{
|
||||
CipherData: cd,
|
||||
Cipher: cipher,
|
||||
}, nil
|
||||
}
|
||||
107
crypto/aes_ctr_cipher_test.go
Normal file
107
crypto/aes_ctr_cipher_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package coscrypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tencentyun/cos-go-sdk-v5/crypto"
|
||||
"io/ioutil"
|
||||
math_rand "math/rand"
|
||||
)
|
||||
|
||||
type EmptyMasterCipher struct{}
|
||||
|
||||
func (mc EmptyMasterCipher) Encrypt(b []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
func (mc EmptyMasterCipher) Decrypt(b []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
func (mc EmptyMasterCipher) GetWrapAlgorithm() string {
|
||||
return "Test/EmptyWrapAlgo"
|
||||
}
|
||||
func (mc EmptyMasterCipher) GetMatDesc() string {
|
||||
return "Empty Desc"
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestCryptoObjectService_EncryptAndDecrypt() {
|
||||
var masterCipher EmptyMasterCipher
|
||||
builder := coscrypto.CreateAesCtrBuilder(masterCipher)
|
||||
|
||||
contentCipher, err := builder.ContentCipher()
|
||||
assert.Nil(s.T(), err, "CryptoObject.CreateAesCtrBuilder Failed")
|
||||
|
||||
dataSize := math_rand.Int63n(1024 * 1024 * 32)
|
||||
originData := make([]byte, dataSize)
|
||||
rand.Read(originData)
|
||||
// 加密
|
||||
r1 := bytes.NewReader(originData)
|
||||
reader1, err := contentCipher.EncryptContent(r1)
|
||||
assert.Nil(s.T(), err, "CryptoObject.contentCipher.Encrypt Failed")
|
||||
encryptedData, err := ioutil.ReadAll(reader1)
|
||||
assert.Nil(s.T(), err, "CryptoObject.Read Failed")
|
||||
|
||||
// 解密
|
||||
r2 := bytes.NewReader(encryptedData)
|
||||
reader2, err := contentCipher.DecryptContent(r2)
|
||||
decryptedData, err := ioutil.ReadAll(reader2)
|
||||
assert.Nil(s.T(), err, "CryptoObject.Read Failed")
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestCryptoObjectService_Encrypt() {
|
||||
var masterCipher EmptyMasterCipher
|
||||
builder := coscrypto.CreateAesCtrBuilder(masterCipher)
|
||||
|
||||
contentCipher, err := builder.ContentCipher()
|
||||
assert.Nil(s.T(), err, "CryptoObject.CreateAesCtrBuilder Failed")
|
||||
|
||||
dataSize := math_rand.Int63n(1024 * 1024 * 32)
|
||||
originData := make([]byte, dataSize)
|
||||
rand.Read(originData)
|
||||
|
||||
// 加密
|
||||
r := bytes.NewReader(originData)
|
||||
reader, err := contentCipher.EncryptContent(r)
|
||||
assert.Nil(s.T(), err, "CryptoObject.contentCipher.Encrypt Failed")
|
||||
encryptedData, err := ioutil.ReadAll(reader)
|
||||
assert.Nil(s.T(), err, "CryptoObject.Read Failed")
|
||||
|
||||
// 直接解密
|
||||
cd := contentCipher.GetCipherData()
|
||||
block, err := aes.NewCipher(cd.Key)
|
||||
assert.Nil(s.T(), err, "CryptoObject.NewCipher Failed")
|
||||
decrypter := cipher.NewCTR(block, cd.IV)
|
||||
decryptedData := make([]byte, len(originData))
|
||||
decrypter.XORKeyStream(decryptedData, encryptedData)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestCryptoObjectService_Decrypt() {
|
||||
var masterCipher EmptyMasterCipher
|
||||
builder := coscrypto.CreateAesCtrBuilder(masterCipher)
|
||||
|
||||
contentCipher, err := builder.ContentCipher()
|
||||
assert.Nil(s.T(), err, "CryptoObject.CreateAesCtrBuilder Failed")
|
||||
dataSize := math_rand.Int63n(1024 * 1024 * 32)
|
||||
originData := make([]byte, dataSize)
|
||||
rand.Read(originData)
|
||||
|
||||
// 直接加密
|
||||
cd := contentCipher.GetCipherData()
|
||||
block, err := aes.NewCipher(cd.Key)
|
||||
assert.Nil(s.T(), err, "CryptoObject.NewCipher Failed")
|
||||
encrypter := cipher.NewCTR(block, cd.IV)
|
||||
encryptedData := make([]byte, len(originData))
|
||||
encrypter.XORKeyStream(encryptedData, originData)
|
||||
|
||||
// 解密
|
||||
r := bytes.NewReader(encryptedData)
|
||||
reader, err := contentCipher.DecryptContent(r)
|
||||
assert.Nil(s.T(), err, "CryptoObject.contentCipher.Encrypt Failed")
|
||||
decryptedData, err := ioutil.ReadAll(reader)
|
||||
assert.Nil(s.T(), err, "CryptoObject.Read Failed")
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
69
crypto/cipher.go
Normal file
69
crypto/cipher.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package coscrypto
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Cipher is interface for encryption or decryption of an object
|
||||
type Cipher interface {
|
||||
Encrypter
|
||||
Decrypter
|
||||
}
|
||||
|
||||
// Encrypter is interface with only encrypt method
|
||||
type Encrypter interface {
|
||||
Encrypt(io.Reader) io.Reader
|
||||
}
|
||||
|
||||
// Decrypter is interface with only decrypt method
|
||||
type Decrypter interface {
|
||||
Decrypt(io.Reader) io.Reader
|
||||
}
|
||||
|
||||
// CryptoEncrypter provides close method for Encrypter
|
||||
type CryptoEncrypter struct {
|
||||
Body io.Reader
|
||||
Encrypter io.Reader
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
// Close lets the CryptoEncrypter satisfy io.ReadCloser interface
|
||||
func (rc *CryptoEncrypter) Close() error {
|
||||
rc.isClosed = true
|
||||
if closer, ok := rc.Body.(io.ReadCloser); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read lets the CryptoEncrypter satisfy io.ReadCloser interface
|
||||
func (rc *CryptoEncrypter) Read(b []byte) (int, error) {
|
||||
if rc.isClosed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return rc.Encrypter.Read(b)
|
||||
}
|
||||
|
||||
// CryptoDecrypter provides close method for Decrypter
|
||||
type CryptoDecrypter struct {
|
||||
Body io.Reader
|
||||
Decrypter io.Reader
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
// Close lets the CryptoDecrypter satisfy io.ReadCloser interface
|
||||
func (rc *CryptoDecrypter) Close() error {
|
||||
rc.isClosed = true
|
||||
if closer, ok := rc.Body.(io.ReadCloser); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read lets the CryptoDecrypter satisfy io.ReadCloser interface
|
||||
func (rc *CryptoDecrypter) Read(b []byte) (int, error) {
|
||||
if rc.isClosed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return rc.Decrypter.Read(b)
|
||||
}
|
||||
256
crypto/crypto_object.go
Normal file
256
crypto/crypto_object.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package coscrypto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/tencentyun/cos-go-sdk-v5"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
97
crypto/crypto_object_part.go
Normal file
97
crypto/crypto_object_part.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package coscrypto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/tencentyun/cos-go-sdk-v5"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type CryptoContext struct {
|
||||
DataSize int64
|
||||
PartSize int64
|
||||
ContentCipher ContentCipher
|
||||
}
|
||||
|
||||
func partSizeIsValid(partSize int64, alignLen int64) bool {
|
||||
if partSize%alignLen == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *CryptoObjectService) InitiateMultipartUpload(ctx context.Context, name string, opt *cos.InitiateMultipartUploadOptions, cryptoCtx *CryptoContext) (*cos.InitiateMultipartUploadResult, *cos.Response, error) {
|
||||
contentCipher, err := s.cryptoClient.ContentCipherBuilder.ContentCipher()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !partSizeIsValid(cryptoCtx.PartSize, int64(contentCipher.GetAlignLen())) {
|
||||
return nil, nil, fmt.Errorf("PartSize is invalid, it should be %v aligned", contentCipher.GetAlignLen())
|
||||
}
|
||||
// 添加自定义头部
|
||||
cryptoCtx.ContentCipher = contentCipher
|
||||
opt = cos.CloneInitiateMultipartUploadOptions(opt)
|
||||
if opt.XOptionHeader == nil {
|
||||
opt.XOptionHeader = &http.Header{}
|
||||
}
|
||||
if opt.ContentMD5 != "" {
|
||||
opt.XOptionHeader.Add(COSClientSideEncryptionUnencryptedContentMD5, opt.ContentMD5)
|
||||
opt.ContentMD5 = ""
|
||||
}
|
||||
if cryptoCtx.DataSize > 0 {
|
||||
opt.XOptionHeader.Add(COSClientSideEncryptionDataSize, strconv.FormatInt(cryptoCtx.DataSize, 10))
|
||||
}
|
||||
opt.XOptionHeader.Add(COSClientSideEncryptionPartSize, strconv.FormatInt(cryptoCtx.PartSize, 10))
|
||||
opt.XOptionHeader.Add(UserAgent, s.cryptoClient.userAgent)
|
||||
addCryptoHeaders(opt.XOptionHeader, contentCipher.GetCipherData())
|
||||
|
||||
return s.ObjectService.InitiateMultipartUpload(ctx, name, opt)
|
||||
}
|
||||
|
||||
func (s *CryptoObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, opt *cos.ObjectUploadPartOptions, cryptoCtx *CryptoContext) (*cos.Response, error) {
|
||||
if cryptoCtx.PartSize == 0 {
|
||||
return nil, fmt.Errorf("CryptoContext's PartSize is zero")
|
||||
}
|
||||
opt = cos.CloneObjectUploadPartOptions(opt)
|
||||
if opt.XOptionHeader == nil {
|
||||
opt.XOptionHeader = &http.Header{}
|
||||
}
|
||||
opt.XOptionHeader.Add(UserAgent, s.cryptoClient.userAgent)
|
||||
if cryptoCtx.ContentCipher == nil {
|
||||
return nil, fmt.Errorf("ContentCipher is nil, Please call the InitiateMultipartUpload")
|
||||
}
|
||||
totalBytes, err := cos.GetReaderLen(r)
|
||||
if err == nil {
|
||||
// 与 go http 保持一致, 非bytes.Buffer/bytes.Reader/strings.Reader需用户指定ContentLength
|
||||
if opt != nil && opt.ContentLength == 0 && cos.IsLenReader(r) {
|
||||
opt.ContentLength = totalBytes
|
||||
}
|
||||
}
|
||||
cd := cryptoCtx.ContentCipher.GetCipherData().Clone()
|
||||
cd.SeekIV(uint64(partNumber-1) * uint64(cryptoCtx.PartSize))
|
||||
cc, err := cryptoCtx.ContentCipher.Clone(cd)
|
||||
opt.ContentLength = cc.GetEncryptedLen(opt.ContentLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader, err := cc.EncryptContent(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.ObjectService.UploadPart(ctx, name, uploadID, partNumber, reader, opt)
|
||||
}
|
||||
|
||||
func (s *CryptoObjectService) CompleteMultipartUpload(ctx context.Context, name, uploadID string, opt *cos.CompleteMultipartUploadOptions) (*cos.CompleteMultipartUploadResult, *cos.Response, error) {
|
||||
opt = cos.CloneCompleteMultipartUploadOptions(opt)
|
||||
if opt.XOptionHeader == nil {
|
||||
opt.XOptionHeader = &http.Header{}
|
||||
}
|
||||
opt.XOptionHeader.Add(UserAgent, s.cryptoClient.userAgent)
|
||||
return s.ObjectService.CompleteMultipartUpload(ctx, name, uploadID, opt)
|
||||
}
|
||||
|
||||
func (s *CryptoObjectService) CopyPart(ctx context.Context, name, uploadID string, partNumber int, sourceURL string, opt *cos.ObjectCopyPartOptions) (*cos.CopyPartResult, *cos.Response, error) {
|
||||
return nil, nil, fmt.Errorf("CryptoObjectService doesn't support CopyPart")
|
||||
}
|
||||
391
crypto/crypto_object_part_test.go
Normal file
391
crypto/crypto_object_part_test.go
Normal file
@@ -0,0 +1,391 @@
|
||||
package coscrypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tencentyun/cos-go-sdk-v5"
|
||||
"github.com/tencentyun/cos-go-sdk-v5/crypto"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
math_rand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *CosTestSuite) TestMultiUpload_Normal() {
|
||||
name := "test/ObjectPut" + time.Now().Format(time.RFC3339)
|
||||
contentLength := int64(1024*1024*10 + 1)
|
||||
originData := make([]byte, contentLength)
|
||||
_, err := rand.Read(originData)
|
||||
|
||||
cryptoCtx := coscrypto.CryptoContext{
|
||||
DataSize: contentLength,
|
||||
PartSize: (contentLength / 16 / 3) * 16,
|
||||
}
|
||||
v, _, err := s.CClient.Object.InitiateMultipartUpload(context.Background(), name, nil, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "Init Failed")
|
||||
chunks, _, err := cos.SplitSizeIntoChunks(contentLength, cryptoCtx.PartSize)
|
||||
assert.Nil(s.T(), err, "Split Failed")
|
||||
optcom := &cos.CompleteMultipartUploadOptions{}
|
||||
for _, chunk := range chunks {
|
||||
opt := &cos.ObjectUploadPartOptions{
|
||||
ContentLength: chunk.Size,
|
||||
}
|
||||
f := bytes.NewReader(originData[chunk.OffSet : chunk.OffSet+chunk.Size])
|
||||
resp, err := s.CClient.Object.UploadPart(context.Background(), name, v.UploadID, chunk.Number, io.LimitReader(f, chunk.Size), opt, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "UploadPart failed")
|
||||
optcom.Parts = append(optcom.Parts, cos.Object{
|
||||
PartNumber: chunk.Number, ETag: resp.Header.Get("ETag"),
|
||||
})
|
||||
}
|
||||
_, _, err = s.CClient.Object.CompleteMultipartUpload(context.Background(), name, v.UploadID, optcom)
|
||||
assert.Nil(s.T(), err, "Complete Failed")
|
||||
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestMultiUpload_DecryptWithKey() {
|
||||
name := "test/ObjectPut" + time.Now().Format(time.RFC3339)
|
||||
contentLength := int64(1024*1024*10 + 1)
|
||||
originData := make([]byte, contentLength)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
|
||||
// 分块上传
|
||||
cryptoCtx := coscrypto.CryptoContext{
|
||||
DataSize: contentLength,
|
||||
PartSize: (contentLength / 16 / 3) * 16,
|
||||
}
|
||||
v, _, err := s.CClient.Object.InitiateMultipartUpload(context.Background(), name, nil, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "Init Failed")
|
||||
chunks, _, err := cos.SplitSizeIntoChunks(contentLength, cryptoCtx.PartSize)
|
||||
assert.Nil(s.T(), err, "Split Failed")
|
||||
optcom := &cos.CompleteMultipartUploadOptions{}
|
||||
for _, chunk := range chunks {
|
||||
opt := &cos.ObjectUploadPartOptions{
|
||||
ContentLength: chunk.Size,
|
||||
Listener: &cos.DefaultProgressListener{},
|
||||
}
|
||||
resp, err := s.CClient.Object.UploadPart(context.Background(), name, v.UploadID, chunk.Number, io.LimitReader(f, chunk.Size), opt, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "UploadPart failed")
|
||||
optcom.Parts = append(optcom.Parts, cos.Object{
|
||||
PartNumber: chunk.Number, ETag: resp.Header.Get("ETag"),
|
||||
})
|
||||
}
|
||||
_, _, err = s.CClient.Object.CompleteMultipartUpload(context.Background(), name, v.UploadID, optcom)
|
||||
assert.Nil(s.T(), err, "Complete Failed")
|
||||
|
||||
// 正常读取
|
||||
resp, err := s.Client.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
encryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.NotEqual(s.T(), bytes.Compare(encryptedData, originData), 0, "encryptedData == originData")
|
||||
|
||||
// 获取解密信息
|
||||
resp, err = s.CClient.Object.Head(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "HeadObject Failed")
|
||||
cipherKey := resp.Header.Get(coscrypto.COSClientSideEncryptionKey)
|
||||
cipherKeybs, err := base64.StdEncoding.DecodeString(cipherKey)
|
||||
assert.Nil(s.T(), err, "base64 Decode Failed")
|
||||
cipherIV := resp.Header.Get(coscrypto.COSClientSideEncryptionStart)
|
||||
cipherIVbs, err := base64.StdEncoding.DecodeString(cipherIV)
|
||||
assert.Nil(s.T(), err, "base64 Decode Failed")
|
||||
key, err := s.Master.Decrypt(cipherKeybs)
|
||||
assert.Nil(s.T(), err, "Master Decrypt Failed")
|
||||
iv, err := s.Master.Decrypt(cipherIVbs)
|
||||
assert.Nil(s.T(), err, "Master Decrypt Failed")
|
||||
|
||||
// 手动解密
|
||||
block, err := aes.NewCipher(key)
|
||||
assert.Nil(s.T(), err, "NewCipher Failed")
|
||||
decrypter := cipher.NewCTR(block, iv)
|
||||
decryptedData := make([]byte, len(originData))
|
||||
decrypter.XORKeyStream(decryptedData, encryptedData)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestMultiUpload_PutFromFile() {
|
||||
name := "test/ObjectPut" + time.Now().Format(time.RFC3339)
|
||||
filepath := "tmpfile" + time.Now().Format(time.RFC3339)
|
||||
newfile, err := os.Create(filepath)
|
||||
assert.Nil(s.T(), err, "Create File Failed")
|
||||
defer os.Remove(filepath)
|
||||
|
||||
contentLength := int64(1024*1024*10 + 1)
|
||||
originData := make([]byte, contentLength)
|
||||
_, err = rand.Read(originData)
|
||||
newfile.Write(originData)
|
||||
newfile.Close()
|
||||
|
||||
m := md5.New()
|
||||
m.Write(originData)
|
||||
contentMD5 := m.Sum(nil)
|
||||
cryptoCtx := coscrypto.CryptoContext{
|
||||
DataSize: contentLength,
|
||||
PartSize: (contentLength / 16 / 3) * 16,
|
||||
}
|
||||
v, _, err := s.CClient.Object.InitiateMultipartUpload(context.Background(), name, nil, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "Init Failed")
|
||||
_, chunks, _, err := cos.SplitFileIntoChunks(filepath, cryptoCtx.PartSize)
|
||||
assert.Nil(s.T(), err, "Split Failed")
|
||||
optcom := &cos.CompleteMultipartUploadOptions{}
|
||||
var wg sync.WaitGroup
|
||||
var mtx sync.Mutex
|
||||
for _, chunk := range chunks {
|
||||
wg.Add(1)
|
||||
go func(chk cos.Chunk) {
|
||||
defer wg.Done()
|
||||
fd, err := os.Open(filepath)
|
||||
assert.Nil(s.T(), err, "Open File Failed")
|
||||
opt := &cos.ObjectUploadPartOptions{
|
||||
ContentLength: chk.Size,
|
||||
}
|
||||
fd.Seek(chk.OffSet, os.SEEK_SET)
|
||||
resp, err := s.CClient.Object.UploadPart(context.Background(), name, v.UploadID, chk.Number, io.LimitReader(fd, chk.Size), opt, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "UploadPart failed")
|
||||
mtx.Lock()
|
||||
optcom.Parts = append(optcom.Parts, cos.Object{
|
||||
PartNumber: chk.Number, ETag: resp.Header.Get("ETag"),
|
||||
})
|
||||
mtx.Unlock()
|
||||
}(chunk)
|
||||
}
|
||||
wg.Wait()
|
||||
sort.Sort(cos.ObjectList(optcom.Parts))
|
||||
_, _, err = s.CClient.Object.CompleteMultipartUpload(context.Background(), name, v.UploadID, optcom)
|
||||
assert.Nil(s.T(), err, "Complete Failed")
|
||||
|
||||
downfile := "downfile" + time.Now().Format(time.RFC3339)
|
||||
_, err = s.CClient.Object.GetToFile(context.Background(), name, downfile, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
|
||||
m = md5.New()
|
||||
fd, err := os.Open(downfile)
|
||||
assert.Nil(s.T(), err, "Open File Failed")
|
||||
defer os.Remove(downfile)
|
||||
defer fd.Close()
|
||||
io.Copy(m, fd)
|
||||
downContentMD5 := m.Sum(nil)
|
||||
assert.Equal(s.T(), bytes.Compare(contentMD5, downContentMD5), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestMultiUpload_GetWithRange() {
|
||||
name := "test/ObjectPut" + time.Now().Format(time.RFC3339)
|
||||
filepath := "tmpfile" + time.Now().Format(time.RFC3339)
|
||||
newfile, err := os.Create(filepath)
|
||||
assert.Nil(s.T(), err, "Create File Failed")
|
||||
defer os.Remove(filepath)
|
||||
|
||||
contentLength := int64(1024*1024*10 + 1)
|
||||
originData := make([]byte, contentLength)
|
||||
_, err = rand.Read(originData)
|
||||
newfile.Write(originData)
|
||||
newfile.Close()
|
||||
|
||||
m := md5.New()
|
||||
m.Write(originData)
|
||||
contentMD5 := m.Sum(nil)
|
||||
cryptoCtx := coscrypto.CryptoContext{
|
||||
DataSize: contentLength,
|
||||
PartSize: (contentLength / 16 / 3) * 16,
|
||||
}
|
||||
iniopt := &cos.InitiateMultipartUploadOptions{
|
||||
&cos.ACLHeaderOptions{
|
||||
XCosACL: "private",
|
||||
},
|
||||
&cos.ObjectPutHeaderOptions{
|
||||
ContentMD5: base64.StdEncoding.EncodeToString(contentMD5),
|
||||
XCosMetaXXX: &http.Header{},
|
||||
},
|
||||
}
|
||||
iniopt.XCosMetaXXX.Add("x-cos-meta-isEncrypted", "true")
|
||||
|
||||
v, _, err := s.CClient.Object.InitiateMultipartUpload(context.Background(), name, iniopt, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "Init Failed")
|
||||
_, chunks, _, err := cos.SplitFileIntoChunks(filepath, cryptoCtx.PartSize)
|
||||
assert.Nil(s.T(), err, "Split Failed")
|
||||
optcom := &cos.CompleteMultipartUploadOptions{}
|
||||
var wg sync.WaitGroup
|
||||
var mtx sync.Mutex
|
||||
for _, chunk := range chunks {
|
||||
wg.Add(1)
|
||||
go func(chk cos.Chunk) {
|
||||
defer wg.Done()
|
||||
fd, err := os.Open(filepath)
|
||||
assert.Nil(s.T(), err, "Open File Failed")
|
||||
opt := &cos.ObjectUploadPartOptions{
|
||||
ContentLength: chk.Size,
|
||||
}
|
||||
fd.Seek(chk.OffSet, os.SEEK_SET)
|
||||
resp, err := s.CClient.Object.UploadPart(context.Background(), name, v.UploadID, chk.Number, io.LimitReader(fd, chk.Size), opt, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "UploadPart failed")
|
||||
mtx.Lock()
|
||||
optcom.Parts = append(optcom.Parts, cos.Object{
|
||||
PartNumber: chk.Number, ETag: resp.Header.Get("ETag"),
|
||||
})
|
||||
mtx.Unlock()
|
||||
}(chunk)
|
||||
}
|
||||
wg.Wait()
|
||||
sort.Sort(cos.ObjectList(optcom.Parts))
|
||||
_, _, err = s.CClient.Object.CompleteMultipartUpload(context.Background(), name, v.UploadID, optcom)
|
||||
assert.Nil(s.T(), err, "Complete Failed")
|
||||
|
||||
// Range解密读取
|
||||
for i := 0; i < 10; i++ {
|
||||
math_rand.Seed(time.Now().UnixNano())
|
||||
rangeStart := math_rand.Int63n(contentLength)
|
||||
rangeEnd := rangeStart + math_rand.Int63n(contentLength-rangeStart)
|
||||
if rangeEnd == rangeStart || rangeStart >= contentLength-1 {
|
||||
continue
|
||||
}
|
||||
opt := &cos.ObjectGetOptions{
|
||||
Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
|
||||
Listener: &cos.DefaultProgressListener{},
|
||||
}
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, opt)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData[rangeStart:rangeEnd+1], decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
|
||||
opt := &cos.ObjectGetOptions{
|
||||
Listener: &cos.DefaultProgressListener{},
|
||||
}
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, opt)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
assert.Equal(s.T(), resp.Header.Get("x-cos-meta-isEncrypted"), "true", "meta data isn't consistent")
|
||||
assert.Equal(s.T(), resp.Header.Get(coscrypto.COSClientSideEncryptionCekAlg), coscrypto.AesCtrAlgorithm, "meta data isn't consistent")
|
||||
assert.Equal(s.T(), resp.Header.Get(coscrypto.COSClientSideEncryptionWrapAlg), coscrypto.CosKmsCryptoWrap, "meta data isn't consistent")
|
||||
assert.Equal(s.T(), resp.Header.Get(coscrypto.COSClientSideEncryptionUnencryptedContentMD5), base64.StdEncoding.EncodeToString(contentMD5), "meta data isn't consistent")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestMultiUpload_GetWithNewClient() {
|
||||
name := "test/ObjectPut" + time.Now().Format(time.RFC3339)
|
||||
contentLength := int64(1024*1024*10 + 1)
|
||||
originData := make([]byte, contentLength)
|
||||
_, err := rand.Read(originData)
|
||||
|
||||
cryptoCtx := coscrypto.CryptoContext{
|
||||
DataSize: contentLength,
|
||||
PartSize: (contentLength / 16 / 3) * 16,
|
||||
}
|
||||
v, _, err := s.CClient.Object.InitiateMultipartUpload(context.Background(), name, nil, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "Init Failed")
|
||||
chunks, _, err := cos.SplitSizeIntoChunks(contentLength, cryptoCtx.PartSize)
|
||||
assert.Nil(s.T(), err, "Split Failed")
|
||||
optcom := &cos.CompleteMultipartUploadOptions{}
|
||||
for _, chunk := range chunks {
|
||||
opt := &cos.ObjectUploadPartOptions{
|
||||
ContentLength: chunk.Size,
|
||||
}
|
||||
f := bytes.NewReader(originData[chunk.OffSet : chunk.OffSet+chunk.Size])
|
||||
resp, err := s.CClient.Object.UploadPart(context.Background(), name, v.UploadID, chunk.Number, io.LimitReader(f, chunk.Size), opt, &cryptoCtx)
|
||||
assert.Nil(s.T(), err, "UploadPart failed")
|
||||
optcom.Parts = append(optcom.Parts, cos.Object{
|
||||
PartNumber: chunk.Number, ETag: resp.Header.Get("ETag"),
|
||||
})
|
||||
}
|
||||
_, _, err = s.CClient.Object.CompleteMultipartUpload(context.Background(), name, v.UploadID, optcom)
|
||||
assert.Nil(s.T(), err, "Complete Failed")
|
||||
|
||||
u, _ := url.Parse("https://" + kBucket + ".cos." + kRegion + ".myqcloud.com")
|
||||
b := &cos.BaseURL{BucketURL: u}
|
||||
c := cos.NewClient(b, &http.Client{
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
},
|
||||
})
|
||||
{
|
||||
// 使用不同的MatDesc客户端读取, 期待错误
|
||||
material := make(map[string]string)
|
||||
material["desc"] = "cos crypto suite test 2"
|
||||
kmsclient, _ := coscrypto.NewKMSClient(c.GetCredential(), kRegion)
|
||||
master, _ := coscrypto.CreateMasterKMS(kmsclient, os.Getenv("KMSID"), material)
|
||||
client := coscrypto.NewCryptoClient(c, master)
|
||||
resp, err := client.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), resp, "Get Object Failed")
|
||||
assert.NotNil(s.T(), err, "Get Object Failed")
|
||||
}
|
||||
|
||||
{
|
||||
// 使用相同的MatDesc客户端读取, 但KMSID不一样,期待正确,kms解密是不需要KMSID
|
||||
material := make(map[string]string)
|
||||
material["desc"] = "cos crypto suite test"
|
||||
kmsclient, _ := coscrypto.NewKMSClient(c.GetCredential(), kRegion)
|
||||
master, _ := coscrypto.CreateMasterKMS(kmsclient, "KMSID", material)
|
||||
client := coscrypto.NewCryptoClient(c, master)
|
||||
resp, err := client.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "Get Object Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
|
||||
{
|
||||
// 使用相同的MatDesc客户端读取, 地域不一样,期待错误
|
||||
material := make(map[string]string)
|
||||
material["desc"] = "cos crypto suite test"
|
||||
diffRegion := "ap-shanghai"
|
||||
if diffRegion == kRegion {
|
||||
diffRegion = "ap-guangzhou"
|
||||
}
|
||||
kmsclient, _ := coscrypto.NewKMSClient(c.GetCredential(), diffRegion)
|
||||
master, _ := coscrypto.CreateMasterKMS(kmsclient, "KMSID", material)
|
||||
client := coscrypto.NewCryptoClient(c, master)
|
||||
resp, err := client.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), resp, "Get Object Failed")
|
||||
assert.NotNil(s.T(), err, "Get Object Failed")
|
||||
}
|
||||
|
||||
{
|
||||
// 使用相同的MatDesc和KMSID客户端读取, 期待正确
|
||||
material := make(map[string]string)
|
||||
material["desc"] = "cos crypto suite test"
|
||||
kmsclient, _ := coscrypto.NewKMSClient(c.GetCredential(), kRegion)
|
||||
master, _ := coscrypto.CreateMasterKMS(kmsclient, os.Getenv("KMSID"), material)
|
||||
client := coscrypto.NewCryptoClient(c, master)
|
||||
resp, err := client.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "Get Object Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
544
crypto/crypto_object_test.go
Normal file
544
crypto/crypto_object_test.go
Normal file
@@ -0,0 +1,544 @@
|
||||
package coscrypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/tencentyun/cos-go-sdk-v5"
|
||||
"github.com/tencentyun/cos-go-sdk-v5/crypto"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
math_rand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
kAppid = 1259654469
|
||||
kBucket = "cosgosdktest-1259654469"
|
||||
kRegion = "ap-guangzhou"
|
||||
)
|
||||
|
||||
type CosTestSuite struct {
|
||||
suite.Suite
|
||||
Client *cos.Client
|
||||
CClient *coscrypto.CryptoClient
|
||||
Master coscrypto.MasterCipher
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) SetupSuite() {
|
||||
u, _ := url.Parse("https://" + kBucket + ".cos." + kRegion + ".myqcloud.com")
|
||||
b := &cos.BaseURL{BucketURL: u}
|
||||
s.Client = cos.NewClient(b, &http.Client{
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
},
|
||||
})
|
||||
material := make(map[string]string)
|
||||
material["desc"] = "cos crypto suite test"
|
||||
kmsclient, _ := coscrypto.NewKMSClient(s.Client.GetCredential(), kRegion)
|
||||
s.Master, _ = coscrypto.CreateMasterKMS(kmsclient, os.Getenv("KMSID"), material)
|
||||
s.CClient = coscrypto.NewCryptoClient(s.Client, s.Master)
|
||||
opt := &cos.BucketPutOptions{
|
||||
XCosACL: "public-read",
|
||||
}
|
||||
r, err := s.Client.Bucket.Put(context.Background(), opt)
|
||||
if err != nil && r != nil && r.StatusCode == 409 {
|
||||
fmt.Println("BucketAlreadyOwnedByYou")
|
||||
} else if err != nil {
|
||||
assert.Nil(s.T(), err, "PutBucket Failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_DecryptWithKey_10MB() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
originData := make([]byte, 1024*1024*10+1)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
|
||||
// 加密存储
|
||||
_, err = s.CClient.Object.Put(context.Background(), name, f, nil)
|
||||
assert.Nil(s.T(), err, "PutObject Failed")
|
||||
|
||||
// 获取解密信息
|
||||
resp, err := s.CClient.Object.Head(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "HeadObject Failed")
|
||||
cipherKey := resp.Header.Get(coscrypto.COSClientSideEncryptionKey)
|
||||
cipherKeybs, err := base64.StdEncoding.DecodeString(cipherKey)
|
||||
assert.Nil(s.T(), err, "base64 Decode Failed")
|
||||
cipherIV := resp.Header.Get(coscrypto.COSClientSideEncryptionStart)
|
||||
cipherIVbs, err := base64.StdEncoding.DecodeString(cipherIV)
|
||||
assert.Nil(s.T(), err, "base64 Decode Failed")
|
||||
key, err := s.Master.Decrypt(cipherKeybs)
|
||||
assert.Nil(s.T(), err, "Master Decrypt Failed")
|
||||
iv, err := s.Master.Decrypt(cipherIVbs)
|
||||
assert.Nil(s.T(), err, "Master Decrypt Failed")
|
||||
|
||||
// 正常读取
|
||||
resp, err = s.Client.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
encryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.NotEqual(s.T(), bytes.Compare(encryptedData, originData), 0, "encryptedData == originData")
|
||||
|
||||
// 手动解密
|
||||
block, err := aes.NewCipher(key)
|
||||
assert.Nil(s.T(), err, "NewCipher Failed")
|
||||
decrypter := cipher.NewCTR(block, iv)
|
||||
decryptedData := make([]byte, len(originData))
|
||||
decrypter.XORKeyStream(decryptedData, encryptedData)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_Normal_10MB() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
originData := make([]byte, 1024*1024*10+1)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
|
||||
// 加密存储
|
||||
_, err = s.CClient.Object.Put(context.Background(), name, f, nil)
|
||||
assert.Nil(s.T(), err, "PutObject Failed")
|
||||
|
||||
// 解密读取
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_VersionID() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
originData := make([]byte, 1024*1024*10+1)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
|
||||
opt := &cos.BucketPutVersionOptions{
|
||||
Status: "Enabled",
|
||||
}
|
||||
_, err = s.CClient.Bucket.PutVersioning(context.Background(), opt)
|
||||
assert.Nil(s.T(), err, "PutVersioning Failed")
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// 加密存储
|
||||
resp, err := s.CClient.Object.Put(context.Background(), name, f, nil)
|
||||
assert.Nil(s.T(), err, "PutObject Failed")
|
||||
versionId := resp.Header.Get("x-cos-version-id")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
|
||||
// 解密读取
|
||||
resp, err = s.CClient.Object.Get(context.Background(), name, nil, versionId)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
delopt := &cos.ObjectDeleteOptions{
|
||||
VersionId: versionId,
|
||||
}
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name, delopt)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
|
||||
opt.Status = "Suspended"
|
||||
_, err = s.CClient.Bucket.PutVersioning(context.Background(), opt)
|
||||
assert.Nil(s.T(), err, "PutVersioning Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_ZeroFile() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
// 加密存储
|
||||
_, err := s.CClient.Object.Put(context.Background(), name, bytes.NewReader([]byte("")), nil)
|
||||
assert.Nil(s.T(), err, "PutObject Failed")
|
||||
|
||||
// 解密读取
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare([]byte(""), decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_WithMetaData() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
originData := make([]byte, 1024*1024*10+1)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
|
||||
m := md5.New()
|
||||
m.Write(originData)
|
||||
contentMD5 := m.Sum(nil)
|
||||
opt := &cos.ObjectPutOptions{
|
||||
&cos.ACLHeaderOptions{
|
||||
XCosACL: "private",
|
||||
},
|
||||
&cos.ObjectPutHeaderOptions{
|
||||
ContentLength: 1024*1024*10 + 1,
|
||||
ContentMD5: base64.StdEncoding.EncodeToString(contentMD5),
|
||||
XCosMetaXXX: &http.Header{},
|
||||
},
|
||||
}
|
||||
opt.XCosMetaXXX.Add("x-cos-meta-isEncrypted", "true")
|
||||
// 加密存储
|
||||
_, err = s.CClient.Object.Put(context.Background(), name, f, opt)
|
||||
assert.Nil(s.T(), err, "PutObject Failed")
|
||||
|
||||
// 解密读取
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
assert.Equal(s.T(), resp.Header.Get("x-cos-meta-isEncrypted"), "true", "meta data isn't consistent")
|
||||
assert.Equal(s.T(), resp.Header.Get(coscrypto.COSClientSideEncryptionCekAlg), coscrypto.AesCtrAlgorithm, "meta data isn't consistent")
|
||||
assert.Equal(s.T(), resp.Header.Get(coscrypto.COSClientSideEncryptionWrapAlg), coscrypto.CosKmsCryptoWrap, "meta data isn't consistent")
|
||||
assert.Equal(s.T(), resp.Header.Get(coscrypto.COSClientSideEncryptionUnencryptedContentMD5), base64.StdEncoding.EncodeToString(contentMD5), "meta data isn't consistent")
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_ByFile() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
filepath := "tmpfile" + time.Now().Format(time.RFC3339)
|
||||
newfile, err := os.Create(filepath)
|
||||
assert.Nil(s.T(), err, "Create File Failed")
|
||||
defer os.Remove(filepath)
|
||||
|
||||
originData := make([]byte, 1024*1024*10+1)
|
||||
_, err = rand.Read(originData)
|
||||
newfile.Write(originData)
|
||||
newfile.Close()
|
||||
|
||||
m := md5.New()
|
||||
m.Write(originData)
|
||||
contentMD5 := m.Sum(nil)
|
||||
opt := &cos.ObjectPutOptions{
|
||||
&cos.ACLHeaderOptions{
|
||||
XCosACL: "private",
|
||||
},
|
||||
&cos.ObjectPutHeaderOptions{
|
||||
ContentLength: 1024*1024*10 + 1,
|
||||
ContentMD5: base64.StdEncoding.EncodeToString(contentMD5),
|
||||
XCosMetaXXX: &http.Header{},
|
||||
},
|
||||
}
|
||||
opt.XCosMetaXXX.Add("x-cos-meta-isEncrypted", "true")
|
||||
// 加密存储
|
||||
_, err = s.CClient.Object.PutFromFile(context.Background(), name, filepath, opt)
|
||||
assert.Nil(s.T(), err, "PutFromFile Failed")
|
||||
|
||||
// 解密读取
|
||||
downfile := "downfile" + time.Now().Format(time.RFC3339)
|
||||
resp, err := s.CClient.Object.GetToFile(context.Background(), name, downfile, nil)
|
||||
assert.Nil(s.T(), err, "GetToFile Failed")
|
||||
assert.Equal(s.T(), resp.Header.Get("x-cos-meta-isEncrypted"), "true", "meta data isn't consistent")
|
||||
assert.Equal(s.T(), resp.Header.Get(coscrypto.COSClientSideEncryptionCekAlg), coscrypto.AesCtrAlgorithm, "meta data isn't consistent")
|
||||
assert.Equal(s.T(), resp.Header.Get(coscrypto.COSClientSideEncryptionWrapAlg), coscrypto.CosKmsCryptoWrap, "meta data isn't consistent")
|
||||
assert.Equal(s.T(), resp.Header.Get(coscrypto.COSClientSideEncryptionUnencryptedContentMD5), base64.StdEncoding.EncodeToString(contentMD5), "meta data isn't consistent")
|
||||
|
||||
fd, err := os.Open(downfile)
|
||||
assert.Nil(s.T(), err, "Open File Failed")
|
||||
defer os.Remove(downfile)
|
||||
defer fd.Close()
|
||||
m = md5.New()
|
||||
io.Copy(m, fd)
|
||||
downContentMD5 := m.Sum(nil)
|
||||
assert.Equal(s.T(), bytes.Compare(contentMD5, downContentMD5), 0, "decryptData != originData")
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_DecryptWithNewClient_10MB() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
originData := make([]byte, 1024*1024*10+1)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
|
||||
// 加密存储
|
||||
_, err = s.CClient.Object.Put(context.Background(), name, f, nil)
|
||||
assert.Nil(s.T(), err, "PutObject Failed")
|
||||
|
||||
u, _ := url.Parse("https://" + kBucket + ".cos." + kRegion + ".myqcloud.com")
|
||||
b := &cos.BaseURL{BucketURL: u}
|
||||
c := cos.NewClient(b, &http.Client{
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
},
|
||||
})
|
||||
{
|
||||
// 使用不同的MatDesc客户端读取, 期待错误
|
||||
material := make(map[string]string)
|
||||
material["desc"] = "cos crypto suite test 2"
|
||||
kmsclient, _ := coscrypto.NewKMSClient(c.GetCredential(), kRegion)
|
||||
master, _ := coscrypto.CreateMasterKMS(kmsclient, os.Getenv("KMSID"), material)
|
||||
client := coscrypto.NewCryptoClient(c, master)
|
||||
resp, err := client.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), resp, "Get Object Failed")
|
||||
assert.NotNil(s.T(), err, "Get Object Failed")
|
||||
}
|
||||
|
||||
{
|
||||
// 使用相同的MatDesc客户端读取, 但KMSID不一样,期待正确,kms解密是不需要KMSID
|
||||
material := make(map[string]string)
|
||||
material["desc"] = "cos crypto suite test"
|
||||
kmsclient, _ := coscrypto.NewKMSClient(s.Client.GetCredential(), kRegion)
|
||||
master, _ := coscrypto.CreateMasterKMS(kmsclient, "KMSID", material)
|
||||
client := coscrypto.NewCryptoClient(c, master)
|
||||
resp, err := client.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "Get Object Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
|
||||
{
|
||||
// 使用相同的MatDesc和KMSID客户端读取, 期待正确
|
||||
material := make(map[string]string)
|
||||
material["desc"] = "cos crypto suite test"
|
||||
kmsclient, _ := coscrypto.NewKMSClient(s.Client.GetCredential(), kRegion)
|
||||
master, _ := coscrypto.CreateMasterKMS(kmsclient, os.Getenv("KMSID"), material)
|
||||
client := coscrypto.NewCryptoClient(c, master)
|
||||
resp, err := client.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "Get Object Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_RangeGet() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
contentLength := 1024*1024*10 + 1
|
||||
originData := make([]byte, contentLength)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
|
||||
// 加密存储
|
||||
_, err = s.CClient.Object.Put(context.Background(), name, f, nil)
|
||||
assert.Nil(s.T(), err, "PutObject Failed")
|
||||
|
||||
// Range解密读取
|
||||
for i := 0; i < 10; i++ {
|
||||
math_rand.Seed(time.Now().UnixNano())
|
||||
rangeStart := math_rand.Intn(contentLength)
|
||||
rangeEnd := rangeStart + math_rand.Intn(contentLength-rangeStart)
|
||||
if rangeEnd == rangeStart || rangeStart >= contentLength-1 {
|
||||
continue
|
||||
}
|
||||
opt := &cos.ObjectGetOptions{
|
||||
Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
|
||||
}
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, opt)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData[rangeStart:rangeEnd+1], decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
|
||||
// 解密读取
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_WithListenerAndRange() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
contentLength := 1024*1024*10 + 1
|
||||
originData := make([]byte, contentLength)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
|
||||
// 加密存储
|
||||
popt := &cos.ObjectPutOptions{
|
||||
nil,
|
||||
&cos.ObjectPutHeaderOptions{
|
||||
Listener: &cos.DefaultProgressListener{},
|
||||
},
|
||||
}
|
||||
_, err = s.CClient.Object.Put(context.Background(), name, f, popt)
|
||||
assert.Nil(s.T(), err, "PutObject Failed")
|
||||
|
||||
// Range解密读取
|
||||
for i := 0; i < 10; i++ {
|
||||
math_rand.Seed(time.Now().UnixNano())
|
||||
rangeStart := math_rand.Intn(contentLength)
|
||||
rangeEnd := rangeStart + math_rand.Intn(contentLength-rangeStart)
|
||||
if rangeEnd == rangeStart || rangeStart >= contentLength-1 {
|
||||
continue
|
||||
}
|
||||
opt := &cos.ObjectGetOptions{
|
||||
Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
|
||||
Listener: &cos.DefaultProgressListener{},
|
||||
}
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, opt)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData[rangeStart:rangeEnd+1], decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
// 解密读取
|
||||
opt := &cos.ObjectGetOptions{
|
||||
Listener: &cos.DefaultProgressListener{},
|
||||
}
|
||||
resp, err := s.CClient.Object.Get(context.Background(), name, opt)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestPutGetDeleteObject_Copy() {
|
||||
name := "test/objectPut" + time.Now().Format(time.RFC3339)
|
||||
contentLength := 1024*1024*10 + 1
|
||||
originData := make([]byte, contentLength)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
|
||||
// 加密存储
|
||||
popt := &cos.ObjectPutOptions{
|
||||
nil,
|
||||
&cos.ObjectPutHeaderOptions{
|
||||
Listener: &cos.DefaultProgressListener{},
|
||||
},
|
||||
}
|
||||
resp, err := s.CClient.Object.Put(context.Background(), name, f, popt)
|
||||
assert.Nil(s.T(), err, "PutObject Failed")
|
||||
encryptedDataCRC := resp.Header.Get("x-cos-hash-crc64ecma")
|
||||
time.Sleep(3 * time.Second)
|
||||
sourceURL := fmt.Sprintf("%s/%s", s.CClient.BaseURL.BucketURL.Host, name)
|
||||
{
|
||||
// x-cos-metadata-directive必须为Copy,否则丢失加密信息,无法解密
|
||||
dest := "test/ObjectCopy1" + time.Now().Format(time.RFC3339)
|
||||
res, _, err := s.CClient.Object.Copy(context.Background(), dest, sourceURL, nil)
|
||||
assert.Nil(s.T(), err, "ObjectCopy Failed")
|
||||
assert.Equal(s.T(), encryptedDataCRC, res.CRC64, "CRC isn't consistent, return:%v, want:%v", res.CRC64, encryptedDataCRC)
|
||||
|
||||
// Range解密读取
|
||||
for i := 0; i < 3; i++ {
|
||||
math_rand.Seed(time.Now().UnixNano())
|
||||
rangeStart := math_rand.Intn(contentLength)
|
||||
rangeEnd := rangeStart + math_rand.Intn(contentLength-rangeStart)
|
||||
if rangeEnd == rangeStart || rangeStart >= contentLength-1 {
|
||||
continue
|
||||
}
|
||||
opt := &cos.ObjectGetOptions{
|
||||
Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
|
||||
Listener: &cos.DefaultProgressListener{},
|
||||
}
|
||||
resp, err := s.CClient.Object.Get(context.Background(), dest, opt)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData[rangeStart:rangeEnd+1], decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
// 解密读取
|
||||
resp, err := s.CClient.Object.Get(context.Background(), dest, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), dest)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
{
|
||||
// x-cos-metadata-directive必须为Copy,否则丢失加密信息,无法解密
|
||||
opt := &cos.ObjectCopyOptions{
|
||||
&cos.ObjectCopyHeaderOptions{
|
||||
XCosMetadataDirective: "Replaced",
|
||||
},
|
||||
nil,
|
||||
}
|
||||
dest := "test/ObjectCopy2" + time.Now().Format(time.RFC3339)
|
||||
res, _, err := s.CClient.Object.Copy(context.Background(), dest, sourceURL, opt)
|
||||
assert.Nil(s.T(), err, "ObjectCopy Failed")
|
||||
assert.Equal(s.T(), encryptedDataCRC, res.CRC64, "CRC isn't consistent, return:%v, want:%v", res.CRC64, encryptedDataCRC)
|
||||
|
||||
// 解密读取
|
||||
resp, err := s.CClient.Object.Get(context.Background(), dest, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.NotEqual(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), dest)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
{
|
||||
// MultiCopy若是分块拷贝,则无法拷贝元数据
|
||||
dest := "test/ObjectCopy3" + time.Now().Format(time.RFC3339)
|
||||
res, _, err := s.CClient.Object.MultiCopy(context.Background(), dest, sourceURL, nil)
|
||||
assert.Nil(s.T(), err, "ObjectMultiCopy Failed")
|
||||
assert.Equal(s.T(), encryptedDataCRC, res.CRC64, "CRC isn't consistent, return:%v, want:%v", res.CRC64, encryptedDataCRC)
|
||||
// Range解密读取
|
||||
for i := 0; i < 3; i++ {
|
||||
math_rand.Seed(time.Now().UnixNano())
|
||||
rangeStart := math_rand.Intn(contentLength)
|
||||
rangeEnd := rangeStart + math_rand.Intn(contentLength-rangeStart)
|
||||
if rangeEnd == rangeStart || rangeStart >= contentLength-1 {
|
||||
continue
|
||||
}
|
||||
opt := &cos.ObjectGetOptions{
|
||||
Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
|
||||
Listener: &cos.DefaultProgressListener{},
|
||||
}
|
||||
resp, err := s.CClient.Object.Get(context.Background(), dest, opt)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData[rangeStart:rangeEnd+1], decryptedData), 0, "decryptData != originData")
|
||||
}
|
||||
// 解密读取
|
||||
resp, err := s.CClient.Object.Get(context.Background(), dest, nil)
|
||||
assert.Nil(s.T(), err, "GetObject Failed")
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "decryptData != originData")
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), dest)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
_, err = s.CClient.Object.Delete(context.Background(), name)
|
||||
assert.Nil(s.T(), err, "DeleteObject Failed")
|
||||
}
|
||||
|
||||
func TestCosTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(CosTestSuite))
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TearDownSuite() {
|
||||
}
|
||||
141
crypto/crypto_type.go
Normal file
141
crypto/crypto_type.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package coscrypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
math_rand "math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
COSClientSideEncryptionKey string = "x-cos-meta-client-side-encryption-key"
|
||||
COSClientSideEncryptionStart = "x-cos-meta-client-side-encryption-start"
|
||||
COSClientSideEncryptionCekAlg = "x-cos-meta-client-side-encryption-cek-alg"
|
||||
COSClientSideEncryptionWrapAlg = "x-cos-meta-client-side-encryption-wrap-alg"
|
||||
COSClientSideEncryptionMatDesc = "x-cos-meta-client-side-encryption-matdesc"
|
||||
COSClientSideEncryptionUnencryptedContentLength = "x-cos-meta-client-side-encryption-unencrypted-content-length"
|
||||
COSClientSideEncryptionUnencryptedContentMD5 = "x-cos-meta-client-side-encryption-unencrypted-content-md5"
|
||||
COSClientSideEncryptionDataSize = "x-cos-meta-client-side-encryption-data-size"
|
||||
COSClientSideEncryptionPartSize = "x-cos-meta-client-side-encryption-part-size"
|
||||
UserAgent = "User-Agent"
|
||||
)
|
||||
|
||||
const (
|
||||
CosKmsCryptoWrap = "KMS/TencentCloud"
|
||||
AesCtrAlgorithm = "AES/CTR/NoPadding"
|
||||
EncryptionUaSuffix = "COSEncryptionClient"
|
||||
)
|
||||
|
||||
type MasterCipher interface {
|
||||
Encrypt([]byte) ([]byte, error)
|
||||
Decrypt([]byte) ([]byte, error)
|
||||
GetWrapAlgorithm() string
|
||||
GetMatDesc() string
|
||||
}
|
||||
|
||||
type ContentCipherBuilder interface {
|
||||
ContentCipher() (ContentCipher, error)
|
||||
ContentCipherEnv(Envelope) (ContentCipher, error)
|
||||
GetMatDesc() string
|
||||
}
|
||||
|
||||
type ContentCipher interface {
|
||||
EncryptContent(io.Reader) (io.ReadCloser, error)
|
||||
DecryptContent(io.Reader) (io.ReadCloser, error)
|
||||
Clone(cd CipherData) (ContentCipher, error)
|
||||
GetEncryptedLen(int64) int64
|
||||
GetCipherData() *CipherData
|
||||
GetAlignLen() int
|
||||
}
|
||||
|
||||
type Envelope struct {
|
||||
IV string
|
||||
CipherKey string
|
||||
MatDesc string
|
||||
WrapAlg string
|
||||
CEKAlg string
|
||||
UnencryptedMD5 string
|
||||
UnencryptedContentLen string
|
||||
}
|
||||
|
||||
func (el Envelope) IsValid() bool {
|
||||
return len(el.IV) > 0 &&
|
||||
len(el.CipherKey) > 0 &&
|
||||
len(el.WrapAlg) > 0 &&
|
||||
len(el.CEKAlg) > 0
|
||||
}
|
||||
|
||||
func (el Envelope) String() string {
|
||||
return fmt.Sprintf("IV=%s&CipherKey=%s&WrapAlg=%s&CEKAlg=%s", el.IV, el.CipherKey, el.WrapAlg, el.CEKAlg)
|
||||
}
|
||||
|
||||
type CipherData struct {
|
||||
IV []byte
|
||||
Key []byte
|
||||
MatDesc string
|
||||
WrapAlgorithm string
|
||||
CEKAlgorithm string
|
||||
EncryptedIV []byte
|
||||
EncryptedKey []byte
|
||||
}
|
||||
|
||||
func (cd *CipherData) RandomKeyIv(keyLen int, ivLen int) error {
|
||||
math_rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Key
|
||||
cd.Key = make([]byte, keyLen)
|
||||
if _, err := io.ReadFull(rand.Reader, cd.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sizeof uint64
|
||||
if ivLen < 8 {
|
||||
return fmt.Errorf("ivLen:%d less than 8", ivLen)
|
||||
}
|
||||
|
||||
// IV: | nonce: 8 bytes | Serial number: 8 bytes |
|
||||
cd.IV = make([]byte, ivLen)
|
||||
if _, err := io.ReadFull(rand.Reader, cd.IV[0:ivLen-8]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// only use 4 byte,in order not to overflow when SeekIV()
|
||||
randNumber := math_rand.Uint32()
|
||||
cd.SetIV(uint64(randNumber))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cd *CipherData) SetIV(iv uint64) {
|
||||
ivLen := len(cd.IV)
|
||||
binary.BigEndian.PutUint64(cd.IV[ivLen-8:], iv)
|
||||
}
|
||||
|
||||
func (cd *CipherData) GetIV() uint64 {
|
||||
ivLen := len(cd.IV)
|
||||
return binary.BigEndian.Uint64(cd.IV[ivLen-8:])
|
||||
}
|
||||
|
||||
func (cd *CipherData) SeekIV(startPos uint64) {
|
||||
cd.SetIV(cd.GetIV() + startPos/uint64(len(cd.IV)))
|
||||
}
|
||||
|
||||
func (cd *CipherData) Clone() CipherData {
|
||||
var cloneCd CipherData
|
||||
cloneCd = *cd
|
||||
|
||||
cloneCd.Key = make([]byte, len(cd.Key))
|
||||
copy(cloneCd.Key, cd.Key)
|
||||
|
||||
cloneCd.IV = make([]byte, len(cd.IV))
|
||||
copy(cloneCd.IV, cd.IV)
|
||||
|
||||
cloneCd.EncryptedIV = make([]byte, len(cd.EncryptedIV))
|
||||
copy(cloneCd.EncryptedIV, cd.EncryptedIV)
|
||||
|
||||
cloneCd.EncryptedKey = make([]byte, len(cd.EncryptedKey))
|
||||
copy(cloneCd.EncryptedKey, cd.EncryptedKey)
|
||||
|
||||
return cloneCd
|
||||
}
|
||||
87
crypto/master_kms_cipher.go
Normal file
87
crypto/master_kms_cipher.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package coscrypto
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
kms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms/v20190118"
|
||||
"github.com/tencentyun/cos-go-sdk-v5"
|
||||
)
|
||||
|
||||
const (
|
||||
KMSEndPoint = "kms.tencentcloudapi.com"
|
||||
)
|
||||
|
||||
type MasterKMSCipher struct {
|
||||
Client *kms.Client
|
||||
KmsId string
|
||||
MatDesc string
|
||||
}
|
||||
|
||||
func NewKMSClient(cred *cos.Credential, region string) (*kms.Client, error) {
|
||||
if cred == nil {
|
||||
fmt.Errorf("credential is nil")
|
||||
}
|
||||
credential := common.NewTokenCredential(
|
||||
cred.SecretID,
|
||||
cred.SecretKey,
|
||||
cred.SessionToken,
|
||||
)
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.Endpoint = KMSEndPoint
|
||||
client, err := kms.NewClient(credential, region, cpf)
|
||||
return client, err
|
||||
}
|
||||
|
||||
func CreateMasterKMS(client *kms.Client, kmsId string, desc map[string]string) (MasterCipher, error) {
|
||||
if kmsId == "" || client == nil {
|
||||
return nil, fmt.Errorf("KMS ID is empty or kms client is nil")
|
||||
}
|
||||
var kmsCipher MasterKMSCipher
|
||||
var jdesc string
|
||||
if len(desc) > 0 {
|
||||
bs, err := json.Marshal(desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jdesc = string(bs)
|
||||
}
|
||||
kmsCipher.Client = client
|
||||
kmsCipher.KmsId = kmsId
|
||||
kmsCipher.MatDesc = jdesc
|
||||
return &kmsCipher, nil
|
||||
}
|
||||
|
||||
func (kc *MasterKMSCipher) Encrypt(plaintext []byte) ([]byte, error) {
|
||||
request := kms.NewEncryptRequest()
|
||||
request.KeyId = common.StringPtr(kc.KmsId)
|
||||
request.EncryptionContext = common.StringPtr(kc.MatDesc)
|
||||
request.Plaintext = common.StringPtr(base64.StdEncoding.EncodeToString(plaintext))
|
||||
resp, err := kc.Client.Encrypt(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(*resp.Response.CiphertextBlob), nil
|
||||
}
|
||||
|
||||
func (kc *MasterKMSCipher) Decrypt(ciphertext []byte) ([]byte, error) {
|
||||
request := kms.NewDecryptRequest()
|
||||
request.CiphertextBlob = common.StringPtr(string(ciphertext))
|
||||
request.EncryptionContext = common.StringPtr(kc.MatDesc)
|
||||
resp, err := kc.Client.Decrypt(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(*resp.Response.Plaintext)
|
||||
}
|
||||
|
||||
func (kc *MasterKMSCipher) GetWrapAlgorithm() string {
|
||||
return CosKmsCryptoWrap
|
||||
}
|
||||
|
||||
func (kc *MasterKMSCipher) GetMatDesc() string {
|
||||
return kc.MatDesc
|
||||
}
|
||||
91
crypto/master_kms_cipher_test.go
Normal file
91
crypto/master_kms_cipher_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package coscrypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
kms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms/v20190118"
|
||||
"github.com/tencentyun/cos-go-sdk-v5"
|
||||
"github.com/tencentyun/cos-go-sdk-v5/crypto"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (s *CosTestSuite) TestMasterKmsCipher_TestKmsClient() {
|
||||
kmsclient, _ := coscrypto.NewKMSClient(&cos.Credential{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
}, kRegion)
|
||||
|
||||
originData := make([]byte, 1024)
|
||||
_, err := rand.Read(originData)
|
||||
|
||||
ctx := make(map[string]string)
|
||||
ctx["desc"] = string(originData[:10])
|
||||
bs, _ := json.Marshal(ctx)
|
||||
ctxJson := string(bs)
|
||||
enReq := kms.NewEncryptRequest()
|
||||
enReq.KeyId = common.StringPtr(os.Getenv("KMSID"))
|
||||
enReq.EncryptionContext = common.StringPtr(ctxJson)
|
||||
enReq.Plaintext = common.StringPtr(base64.StdEncoding.EncodeToString(originData))
|
||||
enResp, err := kmsclient.Encrypt(enReq)
|
||||
assert.Nil(s.T(), err, "Encrypt Failed")
|
||||
encryptedData := []byte(*enResp.Response.CiphertextBlob)
|
||||
|
||||
deReq := kms.NewDecryptRequest()
|
||||
deReq.CiphertextBlob = common.StringPtr(string(encryptedData))
|
||||
deReq.EncryptionContext = common.StringPtr(ctxJson)
|
||||
deResp, err := kmsclient.Decrypt(deReq)
|
||||
assert.Nil(s.T(), err, "Decrypt Failed")
|
||||
decryptedData, err := base64.StdEncoding.DecodeString(*deResp.Response.Plaintext)
|
||||
assert.Nil(s.T(), err, "base64 Decode Failed")
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "originData != decryptedData")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestMasterKmsCipher_TestNormal() {
|
||||
kmsclient, _ := coscrypto.NewKMSClient(&cos.Credential{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
}, kRegion)
|
||||
|
||||
desc := make(map[string]string)
|
||||
desc["test"] = "TestMasterKmsCipher_TestNormal"
|
||||
master, err := coscrypto.CreateMasterKMS(kmsclient, os.Getenv("KMSID"), desc)
|
||||
assert.Nil(s.T(), err, "CreateMasterKMS Failed")
|
||||
|
||||
originData := make([]byte, 1024)
|
||||
_, err = rand.Read(originData)
|
||||
|
||||
encryptedData, err := master.Encrypt(originData)
|
||||
assert.Nil(s.T(), err, "Encrypt Failed")
|
||||
|
||||
decryptedData, err := master.Decrypt(encryptedData)
|
||||
assert.Nil(s.T(), err, "Decrypt Failed")
|
||||
|
||||
assert.Equal(s.T(), bytes.Compare(originData, decryptedData), 0, "originData != decryptedData")
|
||||
}
|
||||
|
||||
func (s *CosTestSuite) TestMasterKmsCipher_TestError() {
|
||||
kmsclient, _ := coscrypto.NewKMSClient(&cos.Credential{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
}, kRegion)
|
||||
|
||||
desc := make(map[string]string)
|
||||
desc["test"] = "TestMasterKmsCipher_TestNormal"
|
||||
master, err := coscrypto.CreateMasterKMS(kmsclient, "ErrorKMSID", desc)
|
||||
assert.Nil(s.T(), err, "CreateMasterKMS Failed")
|
||||
|
||||
originData := make([]byte, 1024)
|
||||
_, err = rand.Read(originData)
|
||||
|
||||
encryptedData, err := master.Encrypt(originData)
|
||||
assert.NotNil(s.T(), err, "Encrypt Failed")
|
||||
|
||||
decryptedData, err := master.Decrypt(encryptedData)
|
||||
assert.NotNil(s.T(), err, "Decrypt Failed")
|
||||
|
||||
assert.NotEqual(s.T(), bytes.Compare(originData, decryptedData), 0, "originData != decryptedData")
|
||||
}
|
||||
316
example/crypto/crypto_sample.go
Normal file
316
example/crypto/crypto_sample.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
math_rand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/tencentyun/cos-go-sdk-v5"
|
||||
"github.com/tencentyun/cos-go-sdk-v5/crypto"
|
||||
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||
)
|
||||
|
||||
func log_status(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if cos.IsNotFoundError(err) {
|
||||
// WARN
|
||||
fmt.Println("WARN: Resource is not existed")
|
||||
} else if e, ok := cos.IsCOSError(err); ok {
|
||||
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
|
||||
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
|
||||
// ERROR
|
||||
} else {
|
||||
fmt.Printf("ERROR: %v\n", err)
|
||||
// ERROR
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func cos_max(x, y int64) int64 {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func simple_put_object() {
|
||||
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||
b := &cos.BaseURL{BucketURL: u}
|
||||
c := cos.NewClient(b, &http.Client{
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
Transport: &debug.DebugRequestTransport{
|
||||
RequestHeader: true,
|
||||
RequestBody: false,
|
||||
ResponseHeader: true,
|
||||
ResponseBody: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
// Case1 上传对象
|
||||
name := "test/example2"
|
||||
|
||||
fmt.Println("============== simple_put_object ======================")
|
||||
// 该标识信息唯一确认一个主加密密钥, 解密时,需要传入相同的标识信息
|
||||
// KMS加密时,该信息设置成EncryptionContext,最大支持1024字符,如果Encrypt指定了该参数,则在Decrypt 时需要提供同样的参数
|
||||
materialDesc := make(map[string]string)
|
||||
//materialDesc["desc"] = "material information of your master encrypt key"
|
||||
|
||||
// 创建KMS客户端
|
||||
kmsclient, _ := coscrypto.NewKMSClient(c.GetCredential(), "ap-guangzhou")
|
||||
// 创建KMS主加密密钥,标识信息和主密钥一一对应
|
||||
kmsID := os.Getenv("KMSID")
|
||||
masterCipher, _ := coscrypto.CreateMasterKMS(kmsclient, kmsID, materialDesc)
|
||||
// 创建加密客户端
|
||||
client := coscrypto.NewCryptoClient(c, masterCipher)
|
||||
|
||||
contentLength := 1024*1024*10 + 1
|
||||
originData := make([]byte, contentLength)
|
||||
_, err := rand.Read(originData)
|
||||
f := bytes.NewReader(originData)
|
||||
// 加密上传
|
||||
_, err = client.Object.Put(context.Background(), name, f, nil)
|
||||
log_status(err)
|
||||
|
||||
math_rand.Seed(time.Now().UnixNano())
|
||||
rangeStart := math_rand.Intn(contentLength)
|
||||
rangeEnd := rangeStart + math_rand.Intn(contentLength-rangeStart)
|
||||
opt := &cos.ObjectGetOptions{
|
||||
Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
|
||||
}
|
||||
// 解密下载
|
||||
resp, err := client.Object.Get(context.Background(), name, opt)
|
||||
log_status(err)
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
if bytes.Compare(decryptedData, originData[rangeStart:rangeEnd+1]) != 0 {
|
||||
fmt.Println("Error: encryptedData != originData")
|
||||
}
|
||||
}
|
||||
|
||||
func simple_put_object_from_file() {
|
||||
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||
b := &cos.BaseURL{BucketURL: u}
|
||||
c := cos.NewClient(b, &http.Client{
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
Transport: &debug.DebugRequestTransport{
|
||||
RequestHeader: true,
|
||||
RequestBody: false,
|
||||
ResponseHeader: true,
|
||||
ResponseBody: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
// Case1 上传对象
|
||||
name := "test/example1"
|
||||
|
||||
fmt.Println("============== simple_put_object_from_file ======================")
|
||||
// 该标识信息唯一确认一个主加密密钥, 解密时,需要传入相同的标识信息
|
||||
// KMS加密时,该信息设置成EncryptionContext,最大支持1024字符,如果Encrypt指定了该参数,则在Decrypt 时需要提供同样的参数
|
||||
materialDesc := make(map[string]string)
|
||||
//materialDesc["desc"] = "material information of your master encrypt key"
|
||||
|
||||
// 创建KMS客户端
|
||||
kmsclient, _ := coscrypto.NewKMSClient(c.GetCredential(), "ap-guangzhou")
|
||||
// 创建KMS主加密密钥,标识信息和主密钥一一对应
|
||||
kmsID := os.Getenv("KMSID")
|
||||
masterCipher, _ := coscrypto.CreateMasterKMS(kmsclient, kmsID, materialDesc)
|
||||
// 创建加密客户端
|
||||
client := coscrypto.NewCryptoClient(c, masterCipher)
|
||||
|
||||
filepath := "test"
|
||||
fd, err := os.Open(filepath)
|
||||
log_status(err)
|
||||
defer fd.Close()
|
||||
m := md5.New()
|
||||
io.Copy(m, fd)
|
||||
originDataMD5 := m.Sum(nil)
|
||||
|
||||
// 加密上传
|
||||
_, err = client.Object.PutFromFile(context.Background(), name, filepath, nil)
|
||||
log_status(err)
|
||||
|
||||
// 解密下载
|
||||
_, err = client.Object.GetToFile(context.Background(), name, "./test.download", nil)
|
||||
log_status(err)
|
||||
|
||||
fd, err = os.Open("./test.download")
|
||||
log_status(err)
|
||||
defer fd.Close()
|
||||
m = md5.New()
|
||||
io.Copy(m, fd)
|
||||
decryptedDataMD5 := m.Sum(nil)
|
||||
|
||||
if bytes.Compare(decryptedDataMD5, originDataMD5) != 0 {
|
||||
fmt.Println("Error: encryptedData != originData")
|
||||
}
|
||||
}
|
||||
|
||||
func multi_put_object() {
|
||||
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||
b := &cos.BaseURL{BucketURL: u}
|
||||
c := cos.NewClient(b, &http.Client{
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
Transport: &debug.DebugRequestTransport{
|
||||
RequestHeader: true,
|
||||
RequestBody: false,
|
||||
ResponseHeader: true,
|
||||
ResponseBody: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
// Case1 上传对象
|
||||
name := "test/example1"
|
||||
|
||||
fmt.Println("============== multi_put_object ======================")
|
||||
// 该标识信息唯一确认一个主加密密钥, 解密时,需要传入相同的标识信息
|
||||
// KMS加密时,该信息设置成EncryptionContext,最大支持1024字符,如果Encrypt指定了该参数,则在Decrypt 时需要提供同样的参数
|
||||
materialDesc := make(map[string]string)
|
||||
//materialDesc["desc"] = "material information of your master encrypt key"
|
||||
|
||||
// 创建KMS客户端
|
||||
kmsclient, _ := coscrypto.NewKMSClient(c.GetCredential(), "ap-guangzhou")
|
||||
// 创建KMS主加密密钥,标识信息和主密钥一一对应
|
||||
kmsID := os.Getenv("KMSID")
|
||||
masterCipher, _ := coscrypto.CreateMasterKMS(kmsclient, kmsID, materialDesc)
|
||||
// 创建加密客户端
|
||||
client := coscrypto.NewCryptoClient(c, masterCipher)
|
||||
|
||||
contentLength := int64(1024*1024*10 + 1)
|
||||
originData := make([]byte, contentLength)
|
||||
_, err := rand.Read(originData)
|
||||
log_status(err)
|
||||
|
||||
// 分块上传
|
||||
cryptoCtx := coscrypto.CryptoContext{
|
||||
DataSize: contentLength,
|
||||
// 每个分块需要16字节对齐
|
||||
PartSize: cos_max(1024*1024, (contentLength/16/3)*16),
|
||||
}
|
||||
v, _, err := client.Object.InitiateMultipartUpload(context.Background(), name, nil, &cryptoCtx)
|
||||
log_status(err)
|
||||
// 切分数据
|
||||
chunks, _, err := cos.SplitSizeIntoChunks(contentLength, cryptoCtx.PartSize)
|
||||
log_status(err)
|
||||
optcom := &cos.CompleteMultipartUploadOptions{}
|
||||
for _, chunk := range chunks {
|
||||
opt := &cos.ObjectUploadPartOptions{
|
||||
ContentLength: chunk.Size,
|
||||
}
|
||||
f := bytes.NewReader(originData[chunk.OffSet : chunk.OffSet+chunk.Size])
|
||||
resp, err := client.Object.UploadPart(context.Background(), name, v.UploadID, chunk.Number, f, opt, &cryptoCtx)
|
||||
log_status(err)
|
||||
optcom.Parts = append(optcom.Parts, cos.Object{
|
||||
PartNumber: chunk.Number, ETag: resp.Header.Get("ETag"),
|
||||
})
|
||||
}
|
||||
_, _, err = client.Object.CompleteMultipartUpload(context.Background(), name, v.UploadID, optcom)
|
||||
log_status(err)
|
||||
|
||||
resp, err := client.Object.Get(context.Background(), name, nil)
|
||||
log_status(err)
|
||||
defer resp.Body.Close()
|
||||
decryptedData, _ := ioutil.ReadAll(resp.Body)
|
||||
if bytes.Compare(decryptedData, originData) != 0 {
|
||||
fmt.Println("Error: encryptedData != originData")
|
||||
}
|
||||
}
|
||||
|
||||
func multi_put_object_from_file() {
|
||||
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||
b := &cos.BaseURL{BucketURL: u}
|
||||
c := cos.NewClient(b, &http.Client{
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: os.Getenv("COS_SECRETID"),
|
||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||
Transport: &debug.DebugRequestTransport{
|
||||
RequestHeader: true,
|
||||
RequestBody: false,
|
||||
ResponseHeader: true,
|
||||
ResponseBody: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
// Case1 上传对象
|
||||
name := "test/example1"
|
||||
|
||||
fmt.Println("============== multi_put_object_from_file ======================")
|
||||
// 该标识信息唯一确认一个主加密密钥, 解密时,需要传入相同的标识信息
|
||||
// KMS加密时,该信息设置成EncryptionContext,最大支持1024字符,如果Encrypt指定了该参数,则在Decrypt 时需要提供同样的参数
|
||||
materialDesc := make(map[string]string)
|
||||
//materialDesc["desc"] = "material information of your master encrypt key"
|
||||
|
||||
// 创建KMS客户端
|
||||
kmsclient, _ := coscrypto.NewKMSClient(c.GetCredential(), "ap-guangzhou")
|
||||
// 创建KMS主加密密钥,标识信息和主密钥一一对应
|
||||
kmsID := os.Getenv("KMSID")
|
||||
masterCipher, _ := coscrypto.CreateMasterKMS(kmsclient, kmsID, materialDesc)
|
||||
// 创建加密客户端
|
||||
client := coscrypto.NewCryptoClient(c, masterCipher)
|
||||
|
||||
filepath := "test"
|
||||
stat, err := os.Stat(filepath)
|
||||
log_status(err)
|
||||
contentLength := stat.Size()
|
||||
|
||||
// 分块上传
|
||||
cryptoCtx := coscrypto.CryptoContext{
|
||||
DataSize: contentLength,
|
||||
// 每个分块需要16字节对齐
|
||||
PartSize: cos_max(1024*1024, (contentLength/16/3)*16),
|
||||
}
|
||||
// 切分数据
|
||||
_, chunks, _, err := cos.SplitFileIntoChunks(filepath, cryptoCtx.PartSize)
|
||||
log_status(err)
|
||||
|
||||
// init mulitupload
|
||||
v, _, err := client.Object.InitiateMultipartUpload(context.Background(), name, nil, &cryptoCtx)
|
||||
log_status(err)
|
||||
|
||||
// part upload
|
||||
optcom := &cos.CompleteMultipartUploadOptions{}
|
||||
for _, chunk := range chunks {
|
||||
fd, err := os.Open(filepath)
|
||||
log_status(err)
|
||||
opt := &cos.ObjectUploadPartOptions{
|
||||
ContentLength: chunk.Size,
|
||||
}
|
||||
fd.Seek(chunk.OffSet, os.SEEK_SET)
|
||||
resp, err := client.Object.UploadPart(context.Background(), name, v.UploadID, chunk.Number, cos.LimitReadCloser(fd, chunk.Size), opt, &cryptoCtx)
|
||||
log_status(err)
|
||||
optcom.Parts = append(optcom.Parts, cos.Object{
|
||||
PartNumber: chunk.Number, ETag: resp.Header.Get("ETag"),
|
||||
})
|
||||
}
|
||||
// complete upload
|
||||
_, _, err = client.Object.CompleteMultipartUpload(context.Background(), name, v.UploadID, optcom)
|
||||
log_status(err)
|
||||
|
||||
_, err = client.Object.GetToFile(context.Background(), name, "test.download", nil)
|
||||
log_status(err)
|
||||
}
|
||||
|
||||
func main() {
|
||||
simple_put_object()
|
||||
simple_put_object_from_file()
|
||||
multi_put_object()
|
||||
multi_put_object_from_file()
|
||||
}
|
||||
1
go.mod
1
go.mod
@@ -8,4 +8,5 @@ require (
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/mozillazg/go-httpheader v0.2.1
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible
|
||||
)
|
||||
|
||||
1
go.sum
1
go.sum
@@ -13,3 +13,4 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
|
||||
|
||||
103
helper.go
103
helper.go
@@ -12,6 +12,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -183,6 +184,19 @@ func CheckReaderLen(reader io.Reader) error {
|
||||
return errors.New("The single object size you upload can not be larger than 5GB")
|
||||
}
|
||||
|
||||
func cloneHeader(opt *http.Header) *http.Header {
|
||||
if opt == nil {
|
||||
return nil
|
||||
}
|
||||
h := make(http.Header, len(*opt))
|
||||
for k, vv := range *opt {
|
||||
vv2 := make([]string, len(vv))
|
||||
copy(vv2, vv)
|
||||
h[k] = vv2
|
||||
}
|
||||
return &h
|
||||
}
|
||||
|
||||
func CopyOptionsToMulti(opt *ObjectCopyOptions) *InitiateMultipartUploadOptions {
|
||||
if opt == nil {
|
||||
return nil
|
||||
@@ -213,8 +227,7 @@ func CopyOptionsToMulti(opt *ObjectCopyOptions) *InitiateMultipartUploadOptions
|
||||
return optini
|
||||
}
|
||||
|
||||
// 浅拷贝ObjectPutOptions
|
||||
func cloneObjectPutOptions(opt *ObjectPutOptions) *ObjectPutOptions {
|
||||
func CloneObjectPutOptions(opt *ObjectPutOptions) *ObjectPutOptions {
|
||||
res := &ObjectPutOptions{
|
||||
&ACLHeaderOptions{},
|
||||
&ObjectPutHeaderOptions{},
|
||||
@@ -225,20 +238,62 @@ func cloneObjectPutOptions(opt *ObjectPutOptions) *ObjectPutOptions {
|
||||
}
|
||||
if opt.ObjectPutHeaderOptions != nil {
|
||||
*res.ObjectPutHeaderOptions = *opt.ObjectPutHeaderOptions
|
||||
res.XCosMetaXXX = cloneHeader(opt.XCosMetaXXX)
|
||||
res.XOptionHeader = cloneHeader(opt.XOptionHeader)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 浅拷贝ObjectUploadPartOptions
|
||||
func cloneObjectUploadPartOptions(opt *ObjectUploadPartOptions) *ObjectUploadPartOptions {
|
||||
func CloneInitiateMultipartUploadOptions(opt *InitiateMultipartUploadOptions) *InitiateMultipartUploadOptions {
|
||||
res := &InitiateMultipartUploadOptions{
|
||||
&ACLHeaderOptions{},
|
||||
&ObjectPutHeaderOptions{},
|
||||
}
|
||||
if opt != nil {
|
||||
if opt.ACLHeaderOptions != nil {
|
||||
*res.ACLHeaderOptions = *opt.ACLHeaderOptions
|
||||
}
|
||||
if opt.ObjectPutHeaderOptions != nil {
|
||||
*res.ObjectPutHeaderOptions = *opt.ObjectPutHeaderOptions
|
||||
res.XCosMetaXXX = cloneHeader(opt.XCosMetaXXX)
|
||||
res.XOptionHeader = cloneHeader(opt.XOptionHeader)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func CloneObjectUploadPartOptions(opt *ObjectUploadPartOptions) *ObjectUploadPartOptions {
|
||||
var res ObjectUploadPartOptions
|
||||
if opt != nil {
|
||||
res = *opt
|
||||
res.XOptionHeader = cloneHeader(opt.XOptionHeader)
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
func CloneObjectGetOptions(opt *ObjectGetOptions) *ObjectGetOptions {
|
||||
var res ObjectGetOptions
|
||||
if opt != nil {
|
||||
res = *opt
|
||||
res.XOptionHeader = cloneHeader(opt.XOptionHeader)
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
func CloneCompleteMultipartUploadOptions(opt *CompleteMultipartUploadOptions) *CompleteMultipartUploadOptions {
|
||||
var res CompleteMultipartUploadOptions
|
||||
if opt != nil {
|
||||
res.XMLName = opt.XMLName
|
||||
if len(opt.Parts) > 0 {
|
||||
res.Parts = make([]Object, len(opt.Parts))
|
||||
copy(res.Parts, opt.Parts)
|
||||
}
|
||||
res.XOptionHeader = cloneHeader(opt.XOptionHeader)
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
type RangeOptions struct {
|
||||
HasStart bool
|
||||
HasEnd bool
|
||||
@@ -259,7 +314,45 @@ func FormatRangeOptions(opt *RangeOptions) string {
|
||||
if opt.HasEnd {
|
||||
return fmt.Sprintf("bytes=-%v", opt.End)
|
||||
}
|
||||
return "bytes=-"
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetRangeOptions(opt *ObjectGetOptions) (*RangeOptions, error) {
|
||||
if opt == nil || opt.Range == "" {
|
||||
return nil, nil
|
||||
}
|
||||
// bytes=M-N
|
||||
slices := strings.Split(opt.Range, "=")
|
||||
if len(slices) != 2 || slices[0] != "bytes" {
|
||||
return nil, fmt.Errorf("Invalid Parameter Range: %v", opt.Range)
|
||||
}
|
||||
// byte=M-N, X-Y
|
||||
fSlice := strings.Split(slices[1], ",")
|
||||
rstr := fSlice[0]
|
||||
|
||||
var err error
|
||||
var ropt RangeOptions
|
||||
sted := strings.Split(rstr, "-")
|
||||
if len(sted) != 2 {
|
||||
return nil, fmt.Errorf("Invalid Parameter Range: %v", opt.Range)
|
||||
}
|
||||
// M
|
||||
if len(sted[0]) > 0 {
|
||||
ropt.Start, err = strconv.ParseInt(sted[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid Parameter Range: %v,err: %v", opt.Range, err)
|
||||
}
|
||||
ropt.HasStart = true
|
||||
}
|
||||
// N
|
||||
if len(sted[1]) > 0 {
|
||||
ropt.End, err = strconv.ParseInt(sted[1], 10, 64)
|
||||
if err != nil || ropt.End == 0 {
|
||||
return nil, fmt.Errorf("Invalid Parameter Range: %v,err: %v", opt.Range, err)
|
||||
}
|
||||
ropt.HasEnd = true
|
||||
}
|
||||
return &ropt, nil
|
||||
}
|
||||
|
||||
var deliverHeader = map[string]bool{}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package cos
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -22,3 +25,73 @@ func Test_calMD5Digest(t *testing.T) {
|
||||
t.Errorf("calMD5Digest request md5: %+v, want %+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_cloneHeader(t *testing.T) {
|
||||
ori := http.Header{}
|
||||
opt := &ori
|
||||
opt.Add("TestHeader1", "h1")
|
||||
opt.Add("TestHeader1", "h2")
|
||||
res := cloneHeader(opt)
|
||||
if !reflect.DeepEqual(res, opt) {
|
||||
t.Errorf("cloneHeader, returned:%+v, want:%+v", res, opt)
|
||||
}
|
||||
if !reflect.DeepEqual(ori, *opt) {
|
||||
t.Errorf("cloneHeader, returned:%+v, want:%+v", *opt, ori)
|
||||
}
|
||||
res.Add("cloneHeader1", "c1")
|
||||
res.Add("cloneHeader2", "c2")
|
||||
if v := opt.Get("cloneHeader1"); v != "" {
|
||||
t.Errorf("cloneHeader, returned:%+v, want:%+v", res, opt)
|
||||
}
|
||||
if v := opt.Get("cloneHeader2"); v != "" {
|
||||
t.Errorf("cloneHeader, returned:%+v, want:%+v", res, opt)
|
||||
}
|
||||
opt = &http.Header{}
|
||||
res = cloneHeader(opt)
|
||||
if !reflect.DeepEqual(res, opt) {
|
||||
t.Errorf("cloneHeader, returned:%+v, want:%+v", res, opt)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CloneCompleteMultipartUploadOptions(t *testing.T) {
|
||||
ori := CompleteMultipartUploadOptions{
|
||||
XMLName: xml.Name{Local: "CompleteMultipartUploadResult"},
|
||||
Parts: []Object{
|
||||
{
|
||||
Key: "Key1",
|
||||
ETag: "Etag1",
|
||||
},
|
||||
{
|
||||
Key: "Key2",
|
||||
ETag: "Etag2",
|
||||
},
|
||||
},
|
||||
XOptionHeader: &http.Header{},
|
||||
}
|
||||
ori.XOptionHeader.Add("Test", "value")
|
||||
opt := &ori
|
||||
res := CloneCompleteMultipartUploadOptions(opt)
|
||||
if !reflect.DeepEqual(res, opt) {
|
||||
t.Errorf("CloneCompleteMultipartUploadOptions, returned:%+v,want:%+v", res, opt)
|
||||
}
|
||||
if !reflect.DeepEqual(ori, *opt) {
|
||||
t.Errorf("CloneCompleteMultipartUploadOptions, returned:%+v,want:%+v", *opt, ori)
|
||||
}
|
||||
res.XOptionHeader.Add("TestClone", "value")
|
||||
if v := opt.XOptionHeader.Get("TestClone"); v != "" {
|
||||
t.Errorf("CloneCompleteMultipartUploadOptions, returned:%+v,want:%+v", res, opt)
|
||||
}
|
||||
opt = &CompleteMultipartUploadOptions{}
|
||||
res = CloneCompleteMultipartUploadOptions(opt)
|
||||
if !reflect.DeepEqual(res, opt) {
|
||||
t.Errorf("CloneCompleteMultipartUploadOptions, returned:%+v,want:%+v", res, opt)
|
||||
}
|
||||
res.Parts = append(res.Parts, Object{Key: "K", ETag: "T"})
|
||||
if len(opt.Parts) > 0 {
|
||||
t.Errorf("CloneCompleteMultipartUploadOptions Failed")
|
||||
}
|
||||
if reflect.DeepEqual(res, opt) {
|
||||
t.Errorf("CloneCompleteMultipartUploadOptions, returned:%+v,want:%+v", res, opt)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
48
object.go
48
object.go
@@ -200,10 +200,13 @@ func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, uopt
|
||||
if err := CheckReaderLen(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt := cloneObjectPutOptions(uopt)
|
||||
opt := CloneObjectPutOptions(uopt)
|
||||
totalBytes, err := GetReaderLen(r)
|
||||
if err != nil && opt != nil && opt.Listener != nil {
|
||||
return nil, err
|
||||
if opt.ContentLength == 0 {
|
||||
return nil, err
|
||||
}
|
||||
totalBytes = opt.ContentLength
|
||||
}
|
||||
if err == nil {
|
||||
// 与 go http 保持一致, 非bytes.Buffer/bytes.Reader/strings.Reader由用户指定ContentLength, 或使用 Chunk 上传
|
||||
@@ -630,6 +633,35 @@ func (lc *LimitedReadCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type DiscardReadCloser struct {
|
||||
RC io.ReadCloser
|
||||
Discard int
|
||||
}
|
||||
|
||||
func (drc *DiscardReadCloser) Read(data []byte) (int, error) {
|
||||
n, err := drc.RC.Read(data)
|
||||
if drc.Discard == 0 || n <= 0 {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if n <= drc.Discard {
|
||||
drc.Discard -= n
|
||||
return 0, err
|
||||
}
|
||||
|
||||
realLen := n - drc.Discard
|
||||
copy(data[0:realLen], data[drc.Discard:n])
|
||||
drc.Discard = 0
|
||||
return realLen, err
|
||||
}
|
||||
|
||||
func (drc *DiscardReadCloser) Close() error {
|
||||
if rc, ok := drc.RC.(io.ReadCloser); ok {
|
||||
return rc.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func worker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
|
||||
for j := range jobs {
|
||||
j.Opt.ContentLength = j.Chunk.Size
|
||||
@@ -736,7 +768,9 @@ func SplitFileIntoChunks(filePath string, partSize int64) (int64, []Chunk, int,
|
||||
}
|
||||
var partNum int64
|
||||
if partSize > 0 {
|
||||
partSize = partSize * 1024 * 1024
|
||||
if partSize < 1024*1024 {
|
||||
return 0, nil, 0, errors.New("partSize>=1048576 is required")
|
||||
}
|
||||
partNum = stat.Size() / partSize
|
||||
if partNum >= 10000 {
|
||||
return 0, nil, 0, errors.New("Too many parts, out of 10000")
|
||||
@@ -855,7 +889,7 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
|
||||
}
|
||||
var localcrc uint64
|
||||
// 1.Get the file chunk
|
||||
totalBytes, chunks, partNum, err := SplitFileIntoChunks(filepath, opt.PartSize)
|
||||
totalBytes, chunks, partNum, err := SplitFileIntoChunks(filepath, opt.PartSize*1024*1024)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -1035,7 +1069,9 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
|
||||
func SplitSizeIntoChunks(totalBytes int64, partSize int64) ([]Chunk, int, error) {
|
||||
var partNum int64
|
||||
if partSize > 0 {
|
||||
partSize = partSize * 1024 * 1024
|
||||
if partSize < 1024*1024 {
|
||||
return nil, 0, errors.New("partSize>=1048576 is required")
|
||||
}
|
||||
partNum = totalBytes / partSize
|
||||
if partNum >= 10000 {
|
||||
return nil, 0, errors.New("Too manry parts, out of 10000")
|
||||
@@ -1130,7 +1166,7 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri
|
||||
}
|
||||
|
||||
// 切分
|
||||
chunks, partNum, err := SplitSizeIntoChunks(totalBytes, opt.PartSize)
|
||||
chunks, partNum, err := SplitSizeIntoChunks(totalBytes, opt.PartSize*1024*1024)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@@ -77,10 +77,13 @@ func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, p
|
||||
return nil, err
|
||||
}
|
||||
// opt 不为 nil
|
||||
opt := cloneObjectUploadPartOptions(uopt)
|
||||
opt := CloneObjectUploadPartOptions(uopt)
|
||||
totalBytes, err := GetReaderLen(r)
|
||||
if err != nil && opt.Listener != nil {
|
||||
return nil, err
|
||||
if opt.ContentLength == 0 {
|
||||
return nil, err
|
||||
}
|
||||
totalBytes = opt.ContentLength
|
||||
}
|
||||
// 分块上传不支持 Chunk 上传
|
||||
if err == nil {
|
||||
@@ -399,37 +402,7 @@ func (s *ObjectService) innerHead(ctx context.Context, sourceURL string, opt *Ob
|
||||
return
|
||||
}
|
||||
|
||||
func SplitCopyFileIntoChunks(totalBytes int64, partSize int64) ([]Chunk, int, error) {
|
||||
var partNum int64
|
||||
if partSize > 0 {
|
||||
partSize = partSize * 1024 * 1024
|
||||
partNum = totalBytes / partSize
|
||||
if partNum >= 10000 {
|
||||
return nil, 0, errors.New("Too many parts, out of 10000")
|
||||
}
|
||||
} else {
|
||||
partNum, partSize = DividePart(totalBytes, 128)
|
||||
}
|
||||
|
||||
var chunks []Chunk
|
||||
var chunk = Chunk{}
|
||||
for i := int64(0); i < partNum; i++ {
|
||||
chunk.Number = int(i + 1)
|
||||
chunk.OffSet = i * partSize
|
||||
chunk.Size = partSize
|
||||
chunks = append(chunks, chunk)
|
||||
}
|
||||
|
||||
if totalBytes%partSize > 0 {
|
||||
chunk.Number = len(chunks) + 1
|
||||
chunk.OffSet = int64(len(chunks)) * partSize
|
||||
chunk.Size = totalBytes % partSize
|
||||
chunks = append(chunks, chunk)
|
||||
partNum++
|
||||
}
|
||||
return chunks, int(partNum), nil
|
||||
}
|
||||
|
||||
// 如果源对象大于5G,则采用分块复制的方式进行拷贝,此时源对象的元信息如果COPY
|
||||
func (s *ObjectService) MultiCopy(ctx context.Context, name string, sourceURL string, opt *MultiCopyOptions, id ...string) (*ObjectCopyResult, *Response, error) {
|
||||
resp, err := s.innerHead(ctx, sourceURL, nil, id)
|
||||
if err != nil {
|
||||
@@ -452,7 +425,7 @@ func (s *ObjectService) MultiCopy(ctx context.Context, name string, sourceURL st
|
||||
if opt == nil {
|
||||
opt = &MultiCopyOptions{}
|
||||
}
|
||||
chunks, partNum, err := SplitCopyFileIntoChunks(totalBytes, opt.PartSize)
|
||||
chunks, partNum, err := SplitSizeIntoChunks(totalBytes, opt.PartSize*1024*1024)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -564,7 +564,6 @@ func TestObjectService_Upload2(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestObjectService_Download(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
@@ -637,7 +636,7 @@ func TestObjectService_Download(t *testing.T) {
|
||||
t.Fatalf("Object.Upload returned error: %v", err)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestObjectService_DownloadWithCheckPoint(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
Reference in New Issue
Block a user