You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

233 lines
6.6 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package coscrypto
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/tencentyun/cos-go-sdk-v5"
  6. "io"
  7. "net/http"
  8. "os"
  9. "strconv"
  10. )
  11. type CryptoObjectService struct {
  12. *cos.ObjectService
  13. cryptoClient *CryptoClient
  14. }
  15. type CryptoClient struct {
  16. *cos.Client
  17. Object *CryptoObjectService
  18. ContentCipherBuilder ContentCipherBuilder
  19. userAgent string
  20. }
  21. func NewCryptoClient(client *cos.Client, masterCipher MasterCipher) *CryptoClient {
  22. cc := &CryptoClient{
  23. Client: client,
  24. Object: &CryptoObjectService{
  25. client.Object,
  26. nil,
  27. },
  28. ContentCipherBuilder: CreateAesCtrBuilder(masterCipher),
  29. }
  30. cc.userAgent = cc.Client.UserAgent + "/" + EncryptionUaSuffix
  31. cc.Object.cryptoClient = cc
  32. return cc
  33. }
  34. func (s *CryptoObjectService) Put(ctx context.Context, name string, r io.Reader, opt *cos.ObjectPutOptions) (*cos.Response, error) {
  35. cc, err := s.cryptoClient.ContentCipherBuilder.ContentCipher()
  36. if err != nil {
  37. return nil, err
  38. }
  39. reader, err := cc.EncryptContent(r)
  40. if err != nil {
  41. return nil, err
  42. }
  43. opt = cos.CloneObjectPutOptions(opt)
  44. totalBytes, err := cos.GetReaderLen(r)
  45. if err != nil && opt != nil && opt.Listener != nil && opt.ContentLength == 0 {
  46. return nil, err
  47. }
  48. if err == nil {
  49. if opt != nil && opt.ContentLength == 0 {
  50. // 如果未设置Listener, 非bytes.Buffer/bytes.Reader/strings.Reader由用户指定Contength
  51. if opt.Listener != nil || cos.IsLenReader(r) {
  52. opt.ContentLength = totalBytes
  53. }
  54. }
  55. }
  56. if opt.XOptionHeader == nil {
  57. opt.XOptionHeader = &http.Header{}
  58. }
  59. if opt.ContentMD5 != "" {
  60. opt.XOptionHeader.Add(COSClientSideEncryptionUnencryptedContentMD5, opt.ContentMD5)
  61. opt.ContentMD5 = ""
  62. }
  63. if opt.ContentLength != 0 {
  64. opt.XOptionHeader.Add(COSClientSideEncryptionUnencryptedContentLength, strconv.FormatInt(opt.ContentLength, 10))
  65. opt.ContentLength = cc.GetEncryptedLen(opt.ContentLength)
  66. }
  67. opt.XOptionHeader.Add(UserAgent, s.cryptoClient.userAgent)
  68. addCryptoHeaders(opt.XOptionHeader, cc.GetCipherData())
  69. return s.ObjectService.Put(ctx, name, reader, opt)
  70. }
  71. func (s *CryptoObjectService) PutFromFile(ctx context.Context, name, filePath string, opt *cos.ObjectPutOptions) (resp *cos.Response, err error) {
  72. nr := 0
  73. for nr < 3 {
  74. fd, e := os.Open(filePath)
  75. if e != nil {
  76. err = e
  77. return
  78. }
  79. resp, err = s.Put(ctx, name, fd, opt)
  80. if err != nil {
  81. nr++
  82. fd.Close()
  83. continue
  84. }
  85. fd.Close()
  86. break
  87. }
  88. return
  89. }
  90. func (s *CryptoObjectService) Get(ctx context.Context, name string, opt *cos.ObjectGetOptions, id ...string) (*cos.Response, error) {
  91. meta, err := s.ObjectService.Head(ctx, name, nil, id...)
  92. if err != nil {
  93. return meta, err
  94. }
  95. _isEncrypted := isEncrypted(&meta.Header)
  96. if !_isEncrypted {
  97. return s.ObjectService.Get(ctx, name, opt, id...)
  98. }
  99. envelope := getEnvelopeFromHeader(&meta.Header)
  100. if !envelope.IsValid() {
  101. return nil, fmt.Errorf("get envelope from header failed, object:%v", name)
  102. }
  103. encryptMatDesc := s.cryptoClient.ContentCipherBuilder.GetMatDesc()
  104. if envelope.MatDesc != encryptMatDesc {
  105. return nil, fmt.Errorf("provided master cipher error, want:%v, return:%v, object:%v", encryptMatDesc, envelope.MatDesc, name)
  106. }
  107. cc, err := s.cryptoClient.ContentCipherBuilder.ContentCipherEnv(envelope)
  108. if err != nil {
  109. return nil, fmt.Errorf("get content cipher from envelope failed: %v, object:%v", err, name)
  110. }
  111. opt = cos.CloneObjectGetOptions(opt)
  112. if opt.XOptionHeader == nil {
  113. opt.XOptionHeader = &http.Header{}
  114. }
  115. optRange, err := cos.GetRangeOptions(opt)
  116. if err != nil {
  117. return nil, err
  118. }
  119. discardAlignLen := int64(0)
  120. // Range请求
  121. if optRange != nil && optRange.HasStart {
  122. // 加密block对齐
  123. adjustStart := adjustRangeStart(optRange.Start, int64(cc.GetAlignLen()))
  124. discardAlignLen = optRange.Start - adjustStart
  125. if discardAlignLen > 0 {
  126. optRange.Start = adjustStart
  127. opt.Range = cos.FormatRangeOptions(optRange)
  128. }
  129. cd := cc.GetCipherData().Clone()
  130. cd.SeekIV(uint64(adjustStart))
  131. cc, err = cc.Clone(cd)
  132. if err != nil {
  133. return nil, fmt.Errorf("ContentCipher Clone failed:%v, bject:%v", err, name)
  134. }
  135. }
  136. opt.XOptionHeader.Add(UserAgent, s.cryptoClient.userAgent)
  137. resp, err := s.ObjectService.Get(ctx, name, opt, id...)
  138. if err != nil {
  139. return resp, err
  140. }
  141. resp.Body, err = cc.DecryptContent(resp.Body)
  142. if err != nil {
  143. return resp, err
  144. }
  145. // 抛弃多读取的数据
  146. if discardAlignLen > 0 {
  147. resp.Body = &cos.DiscardReadCloser{
  148. RC: resp.Body,
  149. Discard: int(discardAlignLen),
  150. }
  151. }
  152. return resp, err
  153. }
  154. func (s *CryptoObjectService) GetToFile(ctx context.Context, name, localpath string, opt *cos.ObjectGetOptions, id ...string) (*cos.Response, error) {
  155. resp, err := s.Get(ctx, name, opt, id...)
  156. if err != nil {
  157. return resp, err
  158. }
  159. defer resp.Body.Close()
  160. // If file exist, overwrite it
  161. fd, err := os.OpenFile(localpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
  162. if err != nil {
  163. return resp, err
  164. }
  165. _, err = io.Copy(fd, resp.Body)
  166. fd.Close()
  167. if err != nil {
  168. return resp, err
  169. }
  170. return resp, nil
  171. }
  172. func (s *CryptoObjectService) MultiUpload(ctx context.Context, name string, filepath string, opt *cos.MultiUploadOptions) (*cos.CompleteMultipartUploadResult, *cos.Response, error) {
  173. return s.Upload(ctx, name, filepath, opt)
  174. }
  175. func (s *CryptoObjectService) Upload(ctx context.Context, name string, filepath string, opt *cos.MultiUploadOptions) (*cos.CompleteMultipartUploadResult, *cos.Response, error) {
  176. return nil, nil, fmt.Errorf("CryptoObjectService doesn't support Upload Now")
  177. }
  178. func (s *CryptoObjectService) Download(ctx context.Context, name string, filepath string, opt *cos.MultiDownloadOptions) (*cos.Response, error) {
  179. return nil, fmt.Errorf("CryptoObjectService doesn't support Download Now")
  180. }
  181. func adjustRangeStart(start int64, alignLen int64) int64 {
  182. return (start / alignLen) * alignLen
  183. }
  184. func addCryptoHeaders(header *http.Header, cd *CipherData) {
  185. if cd.MatDesc != "" {
  186. header.Add(COSClientSideEncryptionMatDesc, cd.MatDesc)
  187. }
  188. header.Add(COSClientSideEncryptionKey, string(cd.EncryptedKey))
  189. header.Add(COSClientSideEncryptionStart, string(cd.EncryptedIV))
  190. header.Add(COSClientSideEncryptionWrapAlg, cd.WrapAlgorithm)
  191. header.Add(COSClientSideEncryptionCekAlg, cd.CEKAlgorithm)
  192. }
  193. func getEnvelopeFromHeader(header *http.Header) Envelope {
  194. var envelope Envelope
  195. envelope.CipherKey = header.Get(COSClientSideEncryptionKey)
  196. envelope.IV = header.Get(COSClientSideEncryptionStart)
  197. envelope.MatDesc = header.Get(COSClientSideEncryptionMatDesc)
  198. envelope.WrapAlg = header.Get(COSClientSideEncryptionWrapAlg)
  199. envelope.CEKAlg = header.Get(COSClientSideEncryptionCekAlg)
  200. return envelope
  201. }
  202. func isEncrypted(header *http.Header) bool {
  203. encryptedKey := header.Get(COSClientSideEncryptionKey)
  204. if len(encryptedKey) > 0 {
  205. return true
  206. }
  207. return false
  208. }