crypto_bucket.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. package osscrypto
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "fmt"
  6. "hash"
  7. "hash/crc64"
  8. "io"
  9. "net/http"
  10. "os"
  11. "strconv"
  12. kms "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
  13. "github.com/aliyun/aliyun-oss-go-sdk/oss"
  14. )
  15. // MasterCipherManager is interface for getting master key with MatDesc(material desc)
  16. // If you may use different master keys for encrypting and decrypting objects,each master
  17. // key must have a unique, non-emtpy, unalterable MatDesc(json string format) and you must provide this interface
  18. // If you always use the same master key for encrypting and decrypting objects, MatDesc
  19. // can be empty and you don't need to provide this interface
  20. //
  21. // matDesc map[string]string:is converted by matDesc json string
  22. // return: []string the secret key information,such as {"rsa-public-key","rsa-private-key"} or {"non-rsa-key"}
  23. type MasterCipherManager interface {
  24. GetMasterKey(matDesc map[string]string) ([]string, error)
  25. }
  26. // DecryptCipherManager is interface for creating a decrypt ContentCipher with Envelope
  27. // If the objects you need to decrypt are neither encrypted with ContentCipherBuilder
  28. // you provided, nor encrypted with rsa and ali kms master keys, you must provide this interface
  29. //
  30. // ContentCipher the interface used to decrypt objects
  31. type DecryptCipherManager interface {
  32. GetDecryptCipher(envelope Envelope) (ContentCipher, error)
  33. }
  34. // CryptoBucketOption CryptoBucket option such as SetAliKmsClient, SetMasterCipherManager, SetDecryptCipherManager.
  35. type CryptoBucketOption func(*CryptoBucket)
  36. // SetAliKmsClient set field AliKmsClient of CryptoBucket
  37. // If the objects you need to decrypt are encrypted with ali kms master key,but not with ContentCipherBuilder
  38. // you provided, you must provide this interface
  39. func SetAliKmsClient(client *kms.Client) CryptoBucketOption {
  40. return func(bucket *CryptoBucket) {
  41. bucket.AliKmsClient = client
  42. }
  43. }
  44. // SetMasterCipherManager set field MasterCipherManager of CryptoBucket
  45. func SetMasterCipherManager(manager MasterCipherManager) CryptoBucketOption {
  46. return func(bucket *CryptoBucket) {
  47. bucket.MasterCipherManager = manager
  48. }
  49. }
  50. // SetDecryptCipherManager set field DecryptCipherManager of CryptoBucket
  51. func SetDecryptCipherManager(manager DecryptCipherManager) CryptoBucketOption {
  52. return func(bucket *CryptoBucket) {
  53. bucket.DecryptCipherManager = manager
  54. }
  55. }
  56. // CryptoBucket implements the operations for encrypting and decrypting objects
  57. // ContentCipherBuilder is used to encrypt and decrypt objects by default
  58. // when the object's MatDesc which you want to decrypt is emtpy or same to the
  59. // master key's MatDesc you provided in ContentCipherBuilder, sdk try to
  60. // use ContentCipherBuilder to decrypt
  61. type CryptoBucket struct {
  62. oss.Bucket
  63. ContentCipherBuilder ContentCipherBuilder
  64. MasterCipherManager MasterCipherManager
  65. DecryptCipherManager DecryptCipherManager
  66. AliKmsClient *kms.Client
  67. }
  68. // GetCryptoBucket create a client encyrption bucket
  69. func GetCryptoBucket(client *oss.Client, bucketName string, builder ContentCipherBuilder,
  70. options ...CryptoBucketOption) (*CryptoBucket, error) {
  71. var cryptoBucket CryptoBucket
  72. cryptoBucket.Client = *client
  73. cryptoBucket.BucketName = bucketName
  74. cryptoBucket.ContentCipherBuilder = builder
  75. for _, option := range options {
  76. option(&cryptoBucket)
  77. }
  78. return &cryptoBucket, nil
  79. }
  80. // PutObject creates a new object and encyrpt it on client side when uploading to oss
  81. func (bucket CryptoBucket) PutObject(objectKey string, reader io.Reader, options ...oss.Option) error {
  82. cc, err := bucket.ContentCipherBuilder.ContentCipher()
  83. if err != nil {
  84. return err
  85. }
  86. cryptoReader, err := cc.EncryptContent(reader)
  87. if err != nil {
  88. return err
  89. }
  90. var request *oss.PutObjectRequest
  91. srcLen, err := oss.GetReaderLen(reader)
  92. if err != nil {
  93. request = &oss.PutObjectRequest{
  94. ObjectKey: objectKey,
  95. Reader: cryptoReader,
  96. }
  97. } else {
  98. encryptedLen := cc.GetEncryptedLen(srcLen)
  99. request = &oss.PutObjectRequest{
  100. ObjectKey: objectKey,
  101. Reader: oss.LimitReadCloser(cryptoReader, encryptedLen),
  102. }
  103. }
  104. opts := addCryptoHeaders(options, cc.GetCipherData())
  105. resp, err := bucket.DoPutObject(request, opts)
  106. if err != nil {
  107. return err
  108. }
  109. defer resp.Body.Close()
  110. return err
  111. }
  112. // GetObject downloads the object from oss
  113. // If the object is encrypted, sdk decrypt it automaticly
  114. func (bucket CryptoBucket) GetObject(objectKey string, options ...oss.Option) (io.ReadCloser, error) {
  115. result, err := bucket.DoGetObject(&oss.GetObjectRequest{ObjectKey: objectKey}, options)
  116. if err != nil {
  117. return nil, err
  118. }
  119. return result.Response, nil
  120. }
  121. // GetObjectToFile downloads the object from oss to local file
  122. // If the object is encrypted, sdk decrypt it automaticly
  123. func (bucket CryptoBucket) GetObjectToFile(objectKey, filePath string, options ...oss.Option) error {
  124. tempFilePath := filePath + oss.TempFileSuffix
  125. // Calls the API to actually download the object. Returns the result instance.
  126. result, err := bucket.DoGetObject(&oss.GetObjectRequest{ObjectKey: objectKey}, options)
  127. if err != nil {
  128. return err
  129. }
  130. defer result.Response.Close()
  131. // If the local file does not exist, create a new one. If it exists, overwrite it.
  132. fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, oss.FilePermMode)
  133. if err != nil {
  134. return err
  135. }
  136. // Copy the data to the local file path.
  137. _, err = io.Copy(fd, result.Response.Body)
  138. fd.Close()
  139. if err != nil {
  140. return err
  141. }
  142. // Compares the CRC value
  143. hasRange, _, _ := oss.IsOptionSet(options, oss.HTTPHeaderRange)
  144. encodeOpt, _ := oss.FindOption(options, oss.HTTPHeaderAcceptEncoding, nil)
  145. acceptEncoding := ""
  146. if encodeOpt != nil {
  147. acceptEncoding = encodeOpt.(string)
  148. }
  149. if bucket.GetConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" {
  150. result.Response.ClientCRC = result.ClientCRC.Sum64()
  151. err = oss.CheckCRC(result.Response, "GetObjectToFile")
  152. if err != nil {
  153. os.Remove(tempFilePath)
  154. return err
  155. }
  156. }
  157. return os.Rename(tempFilePath, filePath)
  158. }
  159. // DoGetObject is the actual API that gets the encrypted or not encrypted object.
  160. // It's the internal function called by other public APIs.
  161. func (bucket CryptoBucket) DoGetObject(request *oss.GetObjectRequest, options []oss.Option) (*oss.GetObjectResult, error) {
  162. // first,we must head object
  163. metaInfo, err := bucket.GetObjectDetailedMeta(request.ObjectKey)
  164. if err != nil {
  165. return nil, err
  166. }
  167. isEncryptedObj := isEncryptedObject(metaInfo)
  168. if !isEncryptedObj {
  169. return bucket.Bucket.DoGetObject(request, options)
  170. }
  171. envelope, err := getEnvelopeFromHeader(metaInfo)
  172. if err != nil {
  173. return nil, err
  174. }
  175. if !isValidContentAlg(envelope.CEKAlg) {
  176. return nil, fmt.Errorf("not supported content algorithm %s,object:%s", envelope.CEKAlg, request.ObjectKey)
  177. }
  178. if !envelope.IsValid() {
  179. return nil, fmt.Errorf("getEnvelopeFromHeader error,object:%s", request.ObjectKey)
  180. }
  181. // use ContentCipherBuilder to decrpt object by default
  182. defaultMatDesc := bucket.ContentCipherBuilder.GetMatDesc()
  183. var cc ContentCipher
  184. err = nil
  185. if envelope.MatDesc == defaultMatDesc {
  186. cc, err = bucket.ContentCipherBuilder.ContentCipherEnv(envelope)
  187. } else {
  188. cc, err = bucket.getDecryptCipher(envelope)
  189. }
  190. if err != nil {
  191. return nil, fmt.Errorf("%s,object:%s", err.Error(), request.ObjectKey)
  192. }
  193. discardFrontAlignLen := int64(0)
  194. uRange, err := oss.GetRangeConfig(options)
  195. if err != nil {
  196. return nil, err
  197. }
  198. if uRange != nil && uRange.HasStart {
  199. // process range to align key size
  200. adjustStart := adjustRangeStart(uRange.Start, cc)
  201. discardFrontAlignLen = uRange.Start - adjustStart
  202. if discardFrontAlignLen > 0 {
  203. uRange.Start = adjustStart
  204. options = oss.DeleteOption(options, oss.HTTPHeaderRange)
  205. options = append(options, oss.NormalizedRange(oss.GetRangeString(*uRange)))
  206. }
  207. // seek iv
  208. cipherData := cc.GetCipherData().Clone()
  209. cipherData.SeekIV(uint64(adjustStart))
  210. cc, _ = cc.Clone(cipherData)
  211. }
  212. params, _ := oss.GetRawParams(options)
  213. resp, err := bucket.Do("GET", request.ObjectKey, params, options, nil, nil)
  214. if err != nil {
  215. return nil, err
  216. }
  217. result := &oss.GetObjectResult{
  218. Response: resp,
  219. }
  220. // CRC
  221. var crcCalc hash.Hash64
  222. hasRange, _, _ := oss.IsOptionSet(options, oss.HTTPHeaderRange)
  223. if bucket.GetConfig().IsEnableCRC && !hasRange {
  224. crcCalc = crc64.New(oss.CrcTable())
  225. result.ServerCRC = resp.ServerCRC
  226. result.ClientCRC = crcCalc
  227. }
  228. // Progress
  229. listener := oss.GetProgressListener(options)
  230. contentLen, _ := strconv.ParseInt(resp.Headers.Get(oss.HTTPHeaderContentLength), 10, 64)
  231. resp.Body = oss.TeeReader(resp.Body, crcCalc, contentLen, listener, nil)
  232. resp.Body, err = cc.DecryptContent(resp.Body)
  233. if err == nil && discardFrontAlignLen > 0 {
  234. resp.Body = &oss.DiscardReadCloser{
  235. RC: resp.Body,
  236. Discard: int(discardFrontAlignLen)}
  237. }
  238. return result, err
  239. }
  240. // PutObjectFromFile creates a new object from the local file
  241. // the object will be encrypted automaticly on client side when uploaded to oss
  242. func (bucket CryptoBucket) PutObjectFromFile(objectKey, filePath string, options ...oss.Option) error {
  243. fd, err := os.Open(filePath)
  244. if err != nil {
  245. return err
  246. }
  247. defer fd.Close()
  248. opts := oss.AddContentType(options, filePath, objectKey)
  249. cc, err := bucket.ContentCipherBuilder.ContentCipher()
  250. if err != nil {
  251. return err
  252. }
  253. cryptoReader, err := cc.EncryptContent(fd)
  254. if err != nil {
  255. return err
  256. }
  257. var request *oss.PutObjectRequest
  258. srcLen, err := oss.GetReaderLen(fd)
  259. if err != nil {
  260. request = &oss.PutObjectRequest{
  261. ObjectKey: objectKey,
  262. Reader: cryptoReader,
  263. }
  264. } else {
  265. encryptedLen := cc.GetEncryptedLen(srcLen)
  266. request = &oss.PutObjectRequest{
  267. ObjectKey: objectKey,
  268. Reader: oss.LimitReadCloser(cryptoReader, encryptedLen),
  269. }
  270. }
  271. opts = addCryptoHeaders(opts, cc.GetCipherData())
  272. resp, err := bucket.DoPutObject(request, opts)
  273. if err != nil {
  274. return err
  275. }
  276. defer resp.Body.Close()
  277. return nil
  278. }
  279. // AppendObject please refer to Bucket.AppendObject
  280. func (bucket CryptoBucket) AppendObject(objectKey string, reader io.Reader, appendPosition int64, options ...oss.Option) (int64, error) {
  281. return 0, fmt.Errorf("CryptoBucket doesn't support AppendObject")
  282. }
  283. // DoAppendObject please refer to Bucket.DoAppendObject
  284. func (bucket CryptoBucket) DoAppendObject(request *oss.AppendObjectRequest, options []oss.Option) (*oss.AppendObjectResult, error) {
  285. return nil, fmt.Errorf("CryptoBucket doesn't support DoAppendObject")
  286. }
  287. // PutObjectWithURL please refer to Bucket.PutObjectWithURL
  288. func (bucket CryptoBucket) PutObjectWithURL(signedURL string, reader io.Reader, options ...oss.Option) error {
  289. return fmt.Errorf("CryptoBucket doesn't support PutObjectWithURL")
  290. }
  291. // PutObjectFromFileWithURL please refer to Bucket.PutObjectFromFileWithURL
  292. func (bucket CryptoBucket) PutObjectFromFileWithURL(signedURL, filePath string, options ...oss.Option) error {
  293. return fmt.Errorf("CryptoBucket doesn't support PutObjectFromFileWithURL")
  294. }
  295. // DoPutObjectWithURL please refer to Bucket.DoPutObjectWithURL
  296. func (bucket CryptoBucket) DoPutObjectWithURL(signedURL string, reader io.Reader, options []oss.Option) (*oss.Response, error) {
  297. return nil, fmt.Errorf("CryptoBucket doesn't support DoPutObjectWithURL")
  298. }
  299. // GetObjectWithURL please refer to Bucket.GetObjectWithURL
  300. func (bucket CryptoBucket) GetObjectWithURL(signedURL string, options ...oss.Option) (io.ReadCloser, error) {
  301. return nil, fmt.Errorf("CryptoBucket doesn't support GetObjectWithURL")
  302. }
  303. // GetObjectToFileWithURL please refer to Bucket.GetObjectToFileWithURL
  304. func (bucket CryptoBucket) GetObjectToFileWithURL(signedURL, filePath string, options ...oss.Option) error {
  305. return fmt.Errorf("CryptoBucket doesn't support GetObjectToFileWithURL")
  306. }
  307. // DoGetObjectWithURL please refer to Bucket.DoGetObjectWithURL
  308. func (bucket CryptoBucket) DoGetObjectWithURL(signedURL string, options []oss.Option) (*oss.GetObjectResult, error) {
  309. return nil, fmt.Errorf("CryptoBucket doesn't support DoGetObjectWithURL")
  310. }
  311. // ProcessObject please refer to Bucket.ProcessObject
  312. func (bucket CryptoBucket) ProcessObject(objectKey string, process string, options ...oss.Option) (oss.ProcessObjectResult, error) {
  313. var out oss.ProcessObjectResult
  314. return out, fmt.Errorf("CryptoBucket doesn't support ProcessObject")
  315. }
  316. // getDecryptCipher create a ContentCipher for decrypt object by envelope stored in object meta
  317. func (bucket CryptoBucket) getDecryptCipher(envelope Envelope) (ContentCipher, error) {
  318. if envelope.CEKAlg != AesCtrAlgorithm {
  319. return nil, fmt.Errorf("not supported content algorithm %s", envelope.CEKAlg)
  320. }
  321. matDesc := make(map[string]string)
  322. if envelope.MatDesc != "" {
  323. err := json.Unmarshal([]byte(envelope.MatDesc), &matDesc)
  324. if err != nil {
  325. return nil, err
  326. }
  327. }
  328. var masterKeys []string
  329. var err error
  330. if envelope.WrapAlg == RsaCryptoWrap || envelope.WrapAlg == KmsAliCryptoWrap {
  331. if bucket.MasterCipherManager == nil {
  332. return nil, fmt.Errorf("MasterCipherManager is nil")
  333. }
  334. masterKeys, err = bucket.MasterCipherManager.GetMasterKey(matDesc)
  335. }
  336. if err != nil {
  337. return nil, err
  338. }
  339. if envelope.WrapAlg == RsaCryptoWrap {
  340. if len(masterKeys) != 2 {
  341. return nil, fmt.Errorf("rsa keys count must be 2,now is %d", len(masterKeys))
  342. }
  343. rsaCipher, err := CreateMasterRsa(matDesc, masterKeys[0], masterKeys[1])
  344. if err != nil {
  345. return nil, err
  346. }
  347. aesCtrBuilder := CreateAesCtrCipher(rsaCipher)
  348. contentCipher, err := aesCtrBuilder.ContentCipherEnv(envelope)
  349. return contentCipher, err
  350. } else if envelope.WrapAlg == KmsAliCryptoWrap {
  351. if len(masterKeys) != 1 {
  352. return nil, fmt.Errorf("non-rsa keys count must be 1,now is %d", len(masterKeys))
  353. }
  354. if bucket.AliKmsClient == nil {
  355. return nil, fmt.Errorf("aliyun kms client is nil")
  356. }
  357. kmsCipher, err := CreateMasterAliKms(matDesc, masterKeys[0], bucket.AliKmsClient)
  358. if err != nil {
  359. return nil, err
  360. }
  361. aesCtrBuilder := CreateAesCtrCipher(kmsCipher)
  362. contentCipher, err := aesCtrBuilder.ContentCipherEnv(envelope)
  363. return contentCipher, err
  364. } else if bucket.DecryptCipherManager != nil {
  365. return bucket.DecryptCipherManager.GetDecryptCipher(envelope)
  366. }
  367. return nil, fmt.Errorf("not supported key wrap algorithm:%s", envelope.WrapAlg)
  368. }
  369. // isEncryptedObject judge the object is encrypted or not
  370. func isEncryptedObject(headers http.Header) bool {
  371. encrptedKey := headers.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionKey)
  372. return len(encrptedKey) > 0
  373. }
  374. // addCryptoHeaders save Envelope information in oss meta
  375. func addCryptoHeaders(options []oss.Option, cd *CipherData) []oss.Option {
  376. opts := []oss.Option{}
  377. // convert content-md5
  378. md5Option, _ := oss.FindOption(options, oss.HTTPHeaderContentMD5, nil)
  379. if md5Option != nil {
  380. opts = append(opts, oss.Meta(OssClientSideEncryptionUnencryptedContentMD5, md5Option.(string)))
  381. options = oss.DeleteOption(options, oss.HTTPHeaderContentMD5)
  382. }
  383. // convert content-length
  384. lenOption, _ := oss.FindOption(options, oss.HTTPHeaderContentLength, nil)
  385. if lenOption != nil {
  386. opts = append(opts, oss.Meta(OssClientSideEncryptionUnencryptedContentLength, lenOption.(string)))
  387. options = oss.DeleteOption(options, oss.HTTPHeaderContentLength)
  388. }
  389. opts = append(opts, options...)
  390. // matDesc
  391. if cd.MatDesc != "" {
  392. opts = append(opts, oss.Meta(OssClientSideEncryptionMatDesc, cd.MatDesc))
  393. }
  394. // encrypted key
  395. strEncryptedKey := base64.StdEncoding.EncodeToString(cd.EncryptedKey)
  396. opts = append(opts, oss.Meta(OssClientSideEncryptionKey, strEncryptedKey))
  397. // encrypted iv
  398. strEncryptedIV := base64.StdEncoding.EncodeToString(cd.EncryptedIV)
  399. opts = append(opts, oss.Meta(OssClientSideEncryptionStart, strEncryptedIV))
  400. // wrap alg
  401. opts = append(opts, oss.Meta(OssClientSideEncryptionWrapAlg, cd.WrapAlgorithm))
  402. // cek alg
  403. opts = append(opts, oss.Meta(OssClientSideEncryptionCekAlg, cd.CEKAlgorithm))
  404. return opts
  405. }
  406. func getEnvelopeFromHeader(header http.Header) (Envelope, error) {
  407. var envelope Envelope
  408. envelope.IV = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionStart)
  409. decodedIV, err := base64.StdEncoding.DecodeString(envelope.IV)
  410. if err != nil {
  411. return envelope, err
  412. }
  413. envelope.IV = string(decodedIV)
  414. envelope.CipherKey = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionKey)
  415. decodedKey, err := base64.StdEncoding.DecodeString(envelope.CipherKey)
  416. if err != nil {
  417. return envelope, err
  418. }
  419. envelope.CipherKey = string(decodedKey)
  420. envelope.MatDesc = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionMatDesc)
  421. envelope.WrapAlg = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionWrapAlg)
  422. envelope.CEKAlg = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionCekAlg)
  423. envelope.UnencryptedMD5 = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionUnencryptedContentMD5)
  424. envelope.UnencryptedContentLen = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionUnencryptedContentLength)
  425. return envelope, err
  426. }
  427. func isValidContentAlg(algName string) bool {
  428. // now content encyrption only support aec/ctr algorithm
  429. return algName == AesCtrAlgorithm
  430. }
  431. func adjustRangeStart(start int64, cc ContentCipher) int64 {
  432. alignLen := int64(cc.GetAlignLen())
  433. return (start / alignLen) * alignLen
  434. }