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