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