Browse Source

for client encyrption,modified according to review comments

taowei.wtw 6 năm trước cách đây
mục cha
commit
7b49b39f2b

+ 1 - 0
oss/client.go

@@ -1217,6 +1217,7 @@ func EnableCRC(isEnableCRC bool) ClientOption {
 func UserAgent(userAgent string) ClientOption {
 	return func(client *Client) {
 		client.Config.UserAgent = userAgent
+		client.Config.UserSetUa = true
 	}
 }
 

+ 1 - 0
oss/conf.go

@@ -99,6 +99,7 @@ type Config struct {
 	UploadLimiter       *OssLimiter         // Bandwidth limit reader for upload
 	CredentialsProvider CredentialsProvider // User provides interface to get AccessKeyID, AccessKeySecret, SecurityToken
 	LocalAddr           net.Addr            // local client host info
+	UserSetUa           bool                // UserAgent is set by user or not
 }
 
 // LimitUploadSpeed uploadSpeed:KB/s, 0 is unlimited,default is 0

+ 98 - 68
oss/crypto/crypto_bucket.go

@@ -27,13 +27,13 @@ type MasterCipherManager interface {
 	GetMasterKey(matDesc map[string]string) ([]string, error)
 }
 
-// DecryptCipherManager is interface for creating a decrypt ContentCipher with Envelope
+// ExtraCipherBuilder is interface for creating a decrypt ContentCipher with Envelope
 // If the objects you need to decrypt are neither encrypted with ContentCipherBuilder
 // you provided, nor encrypted with rsa and ali kms master keys, you must provide this interface
 //
 // ContentCipher  the interface used to decrypt objects
-type DecryptCipherManager interface {
-	GetDecryptCipher(envelope Envelope) (ContentCipher, error)
+type ExtraCipherBuilder interface {
+	GetDecryptCipher(envelope Envelope, cm MasterCipherManager) (ContentCipher, error)
 }
 
 // CryptoBucketOption CryptoBucket option such as SetAliKmsClient, SetMasterCipherManager, SetDecryptCipherManager.
@@ -55,13 +55,82 @@ func SetMasterCipherManager(manager MasterCipherManager) CryptoBucketOption {
 	}
 }
 
-// SetDecryptCipherManager set field DecryptCipherManager of CryptoBucket
-func SetDecryptCipherManager(manager DecryptCipherManager) CryptoBucketOption {
+// SetExtraCipherBuilder set field ExtraCipherBuilder of CryptoBucket
+func SetExtraCipherBuilder(extraBuilder ExtraCipherBuilder) CryptoBucketOption {
 	return func(bucket *CryptoBucket) {
-		bucket.DecryptCipherManager = manager
+		bucket.ExtraCipherBuilder = extraBuilder
 	}
 }
 
+// DefaultExtraCipherBuilder is Default implementation of the ExtraCipherBuilder for rsa and kms master keys
+type DefaultExtraCipherBuilder struct {
+	AliKmsClient *kms.Client
+}
+
+// GetDecryptCipher is used to get ContentCipher for decrypt object
+func (decb *DefaultExtraCipherBuilder) GetDecryptCipher(envelope Envelope, cm MasterCipherManager) (ContentCipher, error) {
+	if cm == nil {
+		return nil, fmt.Errorf("DefaultExtraCipherBuilder GetDecryptCipher error,MasterCipherManager is nil")
+	}
+
+	if envelope.CEKAlg != AesCtrAlgorithm {
+		return nil, fmt.Errorf("DefaultExtraCipherBuilder GetDecryptCipher error,not supported content algorithm %s", envelope.CEKAlg)
+	}
+
+	if envelope.WrapAlg != RsaCryptoWrap && envelope.WrapAlg != KmsAliCryptoWrap {
+		return nil, fmt.Errorf("DefaultExtraCipherBuilder GetDecryptCipher error,not supported envelope wrap algorithm %s", envelope.WrapAlg)
+	}
+
+	matDesc := make(map[string]string)
+	if envelope.MatDesc != "" {
+		err := json.Unmarshal([]byte(envelope.MatDesc), &matDesc)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	masterKeys, err := cm.GetMasterKey(matDesc)
+	if err != nil {
+		return nil, err
+	}
+
+	var contentCipher ContentCipher
+	if envelope.WrapAlg == RsaCryptoWrap {
+		// for rsa master key
+		if len(masterKeys) != 2 {
+			return nil, fmt.Errorf("rsa keys count must be 2,now is %d", len(masterKeys))
+		}
+		rsaCipher, err := CreateMasterRsa(matDesc, masterKeys[0], masterKeys[1])
+		if err != nil {
+			return nil, err
+		}
+		aesCtrBuilder := CreateAesCtrCipher(rsaCipher)
+		contentCipher, err = aesCtrBuilder.ContentCipherEnv(envelope)
+
+	} else if envelope.WrapAlg == KmsAliCryptoWrap {
+		// for kms master key
+		if len(masterKeys) != 1 {
+			return nil, fmt.Errorf("non-rsa keys count must be 1,now is %d", len(masterKeys))
+		}
+
+		if decb.AliKmsClient == nil {
+			return nil, fmt.Errorf("aliyun kms client is nil")
+		}
+
+		kmsCipher, err := CreateMasterAliKms(matDesc, masterKeys[0], decb.AliKmsClient)
+		if err != nil {
+			return nil, err
+		}
+		aesCtrBuilder := CreateAesCtrCipher(kmsCipher)
+		contentCipher, err = aesCtrBuilder.ContentCipherEnv(envelope)
+	} else {
+		// to do
+		// for master keys which are neither rsa nor kms
+	}
+
+	return contentCipher, err
+}
+
 // CryptoBucket implements the operations for encrypting and decrypting objects
 // ContentCipherBuilder is used to encrypt and decrypt objects by default
 // when the object's MatDesc which you want to decrypt is emtpy or same to the
@@ -70,8 +139,8 @@ func SetDecryptCipherManager(manager DecryptCipherManager) CryptoBucketOption {
 type CryptoBucket struct {
 	oss.Bucket
 	ContentCipherBuilder ContentCipherBuilder
+	ExtraCipherBuilder   ExtraCipherBuilder
 	MasterCipherManager  MasterCipherManager
-	DecryptCipherManager DecryptCipherManager
 	AliKmsClient         *kms.Client
 }
 
@@ -86,11 +155,17 @@ func GetCryptoBucket(client *oss.Client, bucketName string, builder ContentCiphe
 	for _, option := range options {
 		option(&cryptoBucket)
 	}
+
+	if cryptoBucket.ExtraCipherBuilder == nil {
+		cryptoBucket.ExtraCipherBuilder = &DefaultExtraCipherBuilder{AliKmsClient: cryptoBucket.AliKmsClient}
+	}
+
 	return &cryptoBucket, nil
 }
 
 // PutObject creates a new object and encyrpt it on client side when uploading to oss
 func (bucket CryptoBucket) PutObject(objectKey string, reader io.Reader, options ...oss.Option) error {
+	options = bucket.AddEncryptionUaSuffix(options)
 	cc, err := bucket.ContentCipherBuilder.ContentCipher()
 	if err != nil {
 		return err
@@ -129,6 +204,7 @@ func (bucket CryptoBucket) PutObject(objectKey string, reader io.Reader, options
 // GetObject downloads the object from oss
 // If the object is encrypted, sdk decrypt it automaticly
 func (bucket CryptoBucket) GetObject(objectKey string, options ...oss.Option) (io.ReadCloser, error) {
+	options = bucket.AddEncryptionUaSuffix(options)
 	result, err := bucket.DoGetObject(&oss.GetObjectRequest{ObjectKey: objectKey}, options)
 	if err != nil {
 		return nil, err
@@ -139,6 +215,7 @@ func (bucket CryptoBucket) GetObject(objectKey string, options ...oss.Option) (i
 // GetObjectToFile downloads the object from oss to local file
 // If the object is encrypted, sdk decrypt it automaticly
 func (bucket CryptoBucket) GetObjectToFile(objectKey, filePath string, options ...oss.Option) error {
+	options = bucket.AddEncryptionUaSuffix(options)
 	tempFilePath := filePath + oss.TempFileSuffix
 
 	// Calls the API to actually download the object. Returns the result instance.
@@ -183,6 +260,8 @@ func (bucket CryptoBucket) GetObjectToFile(objectKey, filePath string, options .
 // DoGetObject is the actual API that gets the encrypted or not encrypted object.
 // It's the internal function called by other public APIs.
 func (bucket CryptoBucket) DoGetObject(request *oss.GetObjectRequest, options []oss.Option) (*oss.GetObjectResult, error) {
+	options = bucket.AddEncryptionUaSuffix(options)
+
 	// first,we must head object
 	metaInfo, err := bucket.GetObjectDetailedMeta(request.ObjectKey)
 	if err != nil {
@@ -208,13 +287,13 @@ func (bucket CryptoBucket) DoGetObject(request *oss.GetObjectRequest, options []
 	}
 
 	// use ContentCipherBuilder to decrpt object by default
-	defaultMatDesc := bucket.ContentCipherBuilder.GetMatDesc()
+	encryptMatDesc := bucket.ContentCipherBuilder.GetMatDesc()
 	var cc ContentCipher
 	err = nil
-	if envelope.MatDesc == defaultMatDesc {
+	if envelope.MatDesc == encryptMatDesc {
 		cc, err = bucket.ContentCipherBuilder.ContentCipherEnv(envelope)
 	} else {
-		cc, err = bucket.getDecryptCipher(envelope)
+		cc, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
 	}
 
 	if err != nil {
@@ -278,6 +357,7 @@ func (bucket CryptoBucket) DoGetObject(request *oss.GetObjectRequest, options []
 // PutObjectFromFile creates a new object from the local file
 // the object will be encrypted automaticly on client side when uploaded to oss
 func (bucket CryptoBucket) PutObjectFromFile(objectKey, filePath string, options ...oss.Option) error {
+	options = bucket.AddEncryptionUaSuffix(options)
 	fd, err := os.Open(filePath)
 	if err != nil {
 		return err
@@ -365,65 +445,15 @@ func (bucket CryptoBucket) ProcessObject(objectKey string, process string, optio
 	return out, fmt.Errorf("CryptoBucket doesn't support ProcessObject")
 }
 
-// getDecryptCipher create a ContentCipher for decrypt object by envelope stored in object meta
-func (bucket CryptoBucket) getDecryptCipher(envelope Envelope) (ContentCipher, error) {
-	if envelope.CEKAlg != AesCtrAlgorithm {
-		return nil, fmt.Errorf("not supported content algorithm %s", envelope.CEKAlg)
-	}
-
-	matDesc := make(map[string]string)
-	if envelope.MatDesc != "" {
-		err := json.Unmarshal([]byte(envelope.MatDesc), &matDesc)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	var masterKeys []string
-	var err error
-	if envelope.WrapAlg == RsaCryptoWrap || envelope.WrapAlg == KmsAliCryptoWrap {
-		if bucket.MasterCipherManager == nil {
-			return nil, fmt.Errorf("MasterCipherManager is nil")
-		}
-		masterKeys, err = bucket.MasterCipherManager.GetMasterKey(matDesc)
-	}
-
-	if err != nil {
-		return nil, err
-	}
-
-	if envelope.WrapAlg == RsaCryptoWrap {
-		if len(masterKeys) != 2 {
-			return nil, fmt.Errorf("rsa keys count must be 2,now is %d", len(masterKeys))
-		}
-		rsaCipher, err := CreateMasterRsa(matDesc, masterKeys[0], masterKeys[1])
-		if err != nil {
-			return nil, err
-		}
-		aesCtrBuilder := CreateAesCtrCipher(rsaCipher)
-		contentCipher, err := aesCtrBuilder.ContentCipherEnv(envelope)
-		return contentCipher, err
-	} else if envelope.WrapAlg == KmsAliCryptoWrap {
-		if len(masterKeys) != 1 {
-			return nil, fmt.Errorf("non-rsa keys count must be 1,now is %d", len(masterKeys))
-		}
-
-		if bucket.AliKmsClient == nil {
-			return nil, fmt.Errorf("aliyun kms client is nil")
-		}
-
-		kmsCipher, err := CreateMasterAliKms(matDesc, masterKeys[0], bucket.AliKmsClient)
-		if err != nil {
-			return nil, err
-		}
-
-		aesCtrBuilder := CreateAesCtrCipher(kmsCipher)
-		contentCipher, err := aesCtrBuilder.ContentCipherEnv(envelope)
-		return contentCipher, err
-	} else if bucket.DecryptCipherManager != nil {
-		return bucket.DecryptCipherManager.GetDecryptCipher(envelope)
+func (bucket CryptoBucket) AddEncryptionUaSuffix(options []oss.Option) []oss.Option {
+	var outOption []oss.Option
+	bSet, _, _ := oss.IsOptionSet(options, oss.HTTPHeaderUserAgent)
+	if bSet || bucket.Client.Config.UserSetUa {
+		outOption = options
+		return outOption
 	}
-	return nil, fmt.Errorf("not supported key wrap algorithm:%s", envelope.WrapAlg)
+	outOption = append(options, oss.UserAgentHeader(bucket.Client.Config.UserAgent+"/"+EncryptionUaSuffix))
+	return outOption
 }
 
 // isEncryptedObject judge the object is encrypted or not

+ 78 - 5
oss/crypto/crypto_bucket_test.go

@@ -1000,28 +1000,28 @@ func (s *OssCryptoBucketSuite) TestGetDecryptCipher(c *C) {
 	// test for getDecryptCipher
 	CEKAlg := envelope.CEKAlg
 	envelope.CEKAlg = ""
-	_, err = bucket.getDecryptCipher(envelope)
+	_, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
 	c.Assert(err, NotNil)
 	envelope.CEKAlg = CEKAlg
 
 	// matDesc is emtpy
 	bucket.MasterCipherManager = &MockRsaManager{}
-	_, err = bucket.getDecryptCipher(envelope)
+	_, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
 	c.Assert(err, NotNil)
 
 	// MasterCipherManager is nil
 	bucket.MasterCipherManager = nil
-	_, err = bucket.getDecryptCipher(envelope)
+	_, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
 	c.Assert(err, NotNil)
 
 	WrapAlg := envelope.WrapAlg
 	envelope.WrapAlg = "test"
-	_, err = bucket.getDecryptCipher(envelope)
+	_, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
 	c.Assert(err, NotNil)
 	envelope.WrapAlg = WrapAlg
 
 	envelope.WrapAlg = KmsAliCryptoWrap
-	_, err = bucket.getDecryptCipher(envelope)
+	_, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
 	c.Assert(err, NotNil)
 	ForceDeleteBucket(client, bucketName, c)
 }
@@ -1151,3 +1151,76 @@ func (s *OssCryptoBucketSuite) TestRepeatedPutObjectFromFile(c *C) {
 	os.Remove(downFileName)
 	ForceDeleteBucket(client, bucketName, c)
 }
+
+func (s *OssCryptoBucketSuite) TestPutObjectEncryptionUserAgent(c *C) {
+	logName := "." + string(os.PathSeparator) + "test-go-sdk.log" + RandStr(5)
+	f, err := os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
+	c.Assert(err, IsNil)
+
+	// create a bucket with default proprety
+	client, err := oss.New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+	client.Config.LogLevel = oss.Debug
+	client.Config.Logger = log.New(f, "", log.LstdFlags)
+
+	bucketName := bucketNamePrefix + RandLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	objectName := objectNamePrefix + RandStr(8)
+	srcJpgFile := "../../sample/test-client-encryption-src.jpg"
+
+	// put object from file
+	masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+	contentProvider := CreateAesCtrCipher(masterRsaCipher)
+	cryptoBucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+	err = cryptoBucket.PutObjectFromFile(objectName, srcJpgFile)
+	c.Assert(err, IsNil)
+
+	// read log file,get http info
+	contents, err := ioutil.ReadFile(logName)
+	c.Assert(err, IsNil)
+
+	httpContent := string(contents)
+	c.Assert(strings.Contains(httpContent, EncryptionUaSuffix), Equals, true)
+
+    f.Close()
+	os.Remove(logName)
+	ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestPutObjectNormalUserAgent(c *C) {
+	logName := "." + string(os.PathSeparator) + "test-go-sdk.log" + RandStr(5)
+	f, err := os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
+	c.Assert(err, IsNil)
+
+	// create a bucket with default proprety
+	client, err := oss.New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+	client.Config.LogLevel = oss.Debug
+	client.Config.Logger = log.New(f, "", log.LstdFlags)
+
+	bucketName := bucketNamePrefix + RandLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	objectName := objectNamePrefix + RandStr(8)
+	srcJpgFile := "../../sample/test-client-encryption-src.jpg"
+
+	bucket, err := client.Bucket(bucketName)
+
+	err = bucket.PutObjectFromFile(objectName, srcJpgFile)
+	c.Assert(err, IsNil)
+
+	// read log file,get http info
+	contents, err := ioutil.ReadFile(logName)
+	c.Assert(err, IsNil)
+
+	httpContent := string(contents)
+	c.Assert(strings.Contains(httpContent, EncryptionUaSuffix), Equals, false)
+
+    f.Close()
+	os.Remove(logName)
+	ForceDeleteBucket(client, bucketName, c)
+}

+ 5 - 0
oss/crypto/crypto_const.go

@@ -19,3 +19,8 @@ const (
 	KmsAliCryptoWrap string = "KMS/ALICLOUD"
 	AesCtrAlgorithm  string = "AES/CTR/NoPadding"
 )
+
+// user agent tag for client encryption
+const (
+	EncryptionUaSuffix string = "OssEncryptionClient"
+)

+ 5 - 2
oss/crypto/crypto_multipart.go

@@ -30,6 +30,7 @@ func (pcc PartCryptoContext) Valid() bool {
 // cryptoContext.ContentCipher is output parameter
 // cryptoContext will be used in next API
 func (bucket CryptoBucket) InitiateMultipartUpload(objectKey string, cryptoContext *PartCryptoContext, options ...oss.Option) (oss.InitiateMultipartUploadResult, error) {
+	options = bucket.AddEncryptionUaSuffix(options)
 	var imur oss.InitiateMultipartUploadResult
 	if cryptoContext == nil {
 		return imur, fmt.Errorf("error,cryptoContext is nil")
@@ -65,6 +66,7 @@ func (bucket CryptoBucket) InitiateMultipartUpload(objectKey string, cryptoConte
 // cryptoContext is the input parameter
 func (bucket CryptoBucket) UploadPart(imur oss.InitiateMultipartUploadResult, reader io.Reader,
 	partSize int64, partNumber int, cryptoContext PartCryptoContext, options ...oss.Option) (oss.UploadPart, error) {
+	options = bucket.AddEncryptionUaSuffix(options)
 	var uploadPart oss.UploadPart
 	if cryptoContext.ContentCipher == nil {
 		return uploadPart, fmt.Errorf("error,cryptoContext is nil or cryptoContext.ContentCipher is nil")
@@ -100,7 +102,7 @@ func (bucket CryptoBucket) UploadPart(imur oss.InitiateMultipartUploadResult, re
 	}
 
 	opts := addCryptoHeaders(options, partCC.GetCipherData())
-	if cryptoContext.DataSize >= 0 {
+	if cryptoContext.DataSize > 0 {
 		opts = append(opts, oss.Meta(OssClientSideEncryptionDataSize, strconv.FormatInt(cryptoContext.DataSize, 10)))
 	}
 	opts = append(opts, oss.Meta(OssClientSideEncryptionPartSize, strconv.FormatInt(cryptoContext.PartSize, 10)))
@@ -113,6 +115,7 @@ func (bucket CryptoBucket) UploadPart(imur oss.InitiateMultipartUploadResult, re
 // cryptoContext is the input parameter
 func (bucket CryptoBucket) UploadPartFromFile(imur oss.InitiateMultipartUploadResult, filePath string,
 	startPosition, partSize int64, partNumber int, cryptoContext PartCryptoContext, options ...oss.Option) (oss.UploadPart, error) {
+	options = bucket.AddEncryptionUaSuffix(options)
 	var uploadPart = oss.UploadPart{}
 	if cryptoContext.ContentCipher == nil {
 		return uploadPart, fmt.Errorf("error,cryptoContext is nil or cryptoContext.ContentCipher is nil")
@@ -148,7 +151,7 @@ func (bucket CryptoBucket) UploadPartFromFile(imur oss.InitiateMultipartUploadRe
 
 	encryptedLen := partCC.GetEncryptedLen(partSize)
 	opts := addCryptoHeaders(options, partCC.GetCipherData())
-	if cryptoContext.DataSize >= 0 {
+	if cryptoContext.DataSize > 0 {
 		opts = append(opts, oss.Meta(OssClientSideEncryptionDataSize, strconv.FormatInt(cryptoContext.DataSize, 10)))
 	}
 	opts = append(opts, oss.Meta(OssClientSideEncryptionPartSize, strconv.FormatInt(cryptoContext.PartSize, 10)))

+ 5 - 0
oss/option.go

@@ -245,6 +245,11 @@ func TrafficLimitHeader(value int64) Option {
 	return setHeader(HTTPHeaderOssTrafficLimit, strconv.FormatInt(value, 10))
 }
 
+// UserAgentHeader is an option to set HTTPHeaderUserAgent
+func UserAgentHeader(ua string) Option {
+	return setHeader(HTTPHeaderUserAgent, ua)
+}
+
 // Delimiter is an option to set delimiler parameter
 func Delimiter(value string) Option {
 	return addParam("delimiter", value)