v3/materials/kms_keyring.go (159 lines of code) (raw):

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package materials import ( "context" "fmt" "github.com/aws/aws-sdk-go-v2/service/kms" "github.com/aws/aws-sdk-go-v2/service/kms/types" ) const ( GcmTagSizeBits = "128" // KMSKeyring is a constant used during decryption to build a KMS key handler. KMSKeyring = "kms" // KMSContextKeyring is a constant used during decryption to build a kms+context keyring KMSContextKeyring = "kms+context" // GrantToken is the key used to store the grant tokens in the context. They are used to avoid eventual consistency authorization issues when calling KMS APIs GrantTokens = "GrantTokens" kmsDefaultEncryptionContextKey = "AES/GCM/NoPadding" kmsAWSCEKContextKey = "aws:x-amz-cek-alg" kmsMismatchCEKAlg = "the content encryption algorithm used at encryption time does not match the algorithm stored for decryption time. The object may be altered or corrupted" kmsReservedKeyConflictErrMsg = "conflict in reserved KMS Encryption Context key %s. This value is reserved for the S3 Encryption client and cannot be set by the user" ) // KmsAPIClient is a client that implements the GenerateDataKey and Decrypt operations type KmsAPIClient interface { GenerateDataKey(context.Context, *kms.GenerateDataKeyInput, ...func(*kms.Options)) (*kms.GenerateDataKeyOutput, error) Decrypt(context.Context, *kms.DecryptInput, ...func(*kms.Options)) (*kms.DecryptOutput, error) } // KeyringOptions is for additional configuration on Keyring types to perform additional behaviors. // When EnableLegacyWrappingAlgorithms is set to true, the Keyring MAY decrypt objects encrypted // using legacy wrapping algorithms such as KMS v1. type KeyringOptions struct { EnableLegacyWrappingAlgorithms bool } // KmsKeyring encrypts with encryption context and on decrypt it checks for the algorithm // in the material description and makes the call to commonDecrypt with the correct parameters type KmsKeyring struct { kmsClient KmsAPIClient KmsKeyId string legacyWrappingAlgorithms bool } // KmsAnyKeyKeyring is decrypt-only. type KmsAnyKeyKeyring struct { kmsClient KmsAPIClient legacyWrappingAlgorithms bool } // NewKmsKeyring creates a new KmsKeyring which calls KMS to encrypt/decrypt the data key used to encrypt the S3 // object. The KmsKeyring will always use the kmsKeyId provided to encrypt and decrypt messages. func NewKmsKeyring(apiClient KmsAPIClient, kmsKeyId string, optFns ...func(options *KeyringOptions)) *KmsKeyring { options := KeyringOptions{ EnableLegacyWrappingAlgorithms: false, } for _, fn := range optFns { fn(&options) } return &KmsKeyring{ kmsClient: apiClient, KmsKeyId: kmsKeyId, legacyWrappingAlgorithms: options.EnableLegacyWrappingAlgorithms, } } // NewKmsDecryptOnlyAnyKeyKeyring creates a new KmsAnyKeyKeyring. This Keyring uses the KMS identifier // persisted in the data key's ciphertext to decrypt the data key. func NewKmsDecryptOnlyAnyKeyKeyring(apiClient KmsAPIClient, optFns ...func(options *KeyringOptions)) *KmsAnyKeyKeyring { options := KeyringOptions{ EnableLegacyWrappingAlgorithms: false, } for _, fn := range optFns { fn(&options) } return &KmsAnyKeyKeyring{ kmsClient: apiClient, legacyWrappingAlgorithms: options.EnableLegacyWrappingAlgorithms, } } // OnEncrypt generates/encrypts a data key for use with content encryption. func (k *KmsKeyring) OnEncrypt(ctx context.Context, materials *EncryptionMaterials) (*CryptographicMaterials, error) { var matDesc MaterialDescription = materials.encryptionContext if _, ok := matDesc[kmsAWSCEKContextKey]; ok { return nil, fmt.Errorf(kmsReservedKeyConflictErrMsg, kmsAWSCEKContextKey) } if matDesc == nil { matDesc = map[string]string{} } requestMatDesc := matDesc.Clone() requestMatDesc[kmsAWSCEKContextKey] = kmsDefaultEncryptionContextKey in := kms.GenerateDataKeyInput{ EncryptionContext: requestMatDesc, KeyId: &k.KmsKeyId, KeySpec: types.DataKeySpecAes256, } grantTokens := ctx.Value(GrantTokens) if grantTokens != nil { in.GrantTokens = grantTokens.([]string) } out, err := k.kmsClient.GenerateDataKey(ctx, &in) if err != nil { return &CryptographicMaterials{}, err } iv, err := generateBytes(materials.gcmNonceSize) if err != nil { return &CryptographicMaterials{}, err } cryptoMaterials := &CryptographicMaterials{ Key: out.Plaintext, IV: iv, KeyringAlgorithm: KMSContextKeyring, CEKAlgorithm: materials.algorithm, TagLength: GcmTagSizeBits, MaterialDescription: requestMatDesc, EncryptedKey: out.CiphertextBlob, } return cryptoMaterials, nil } // OnDecrypt decrypts the encryptedDataKeys and returns them in materials // for use with content decryption, or an error if the object cannot be decrypted // by the Keyring as its configured. func (k *KmsKeyring) OnDecrypt(ctx context.Context, materials *DecryptionMaterials, encryptedDataKey DataKey) (*CryptographicMaterials, error) { if materials.DataKey.DataKeyAlgorithm == KMSKeyring && !k.legacyWrappingAlgorithms { return nil, fmt.Errorf("to decrypt x-amz-cek-alg value `%s` you must enable legacyWrappingAlgorithms on the keyring", materials.DataKey.DataKeyAlgorithm) } if materials.DataKey.DataKeyAlgorithm == KMSKeyring && k.legacyWrappingAlgorithms { return commonDecrypt(ctx, materials, encryptedDataKey, &k.KmsKeyId, nil, k.kmsClient) } else if materials.DataKey.DataKeyAlgorithm == KMSContextKeyring { return commonDecrypt(ctx, materials, encryptedDataKey, &k.KmsKeyId, materials.MaterialDescription, k.kmsClient) } else { return nil, fmt.Errorf("x-amz-cek-alg value `%s` did not match an expected algorithm", materials.DataKey.DataKeyAlgorithm) } } func (k *KmsKeyring) isAWSFixture() bool { return true } // OnEncrypt generates/encrypts a data key for use with content encryption // The KmsAnyKeyKeyring does not support OnEncrypt, so an error is returned. func (k *KmsAnyKeyKeyring) OnEncrypt(ctx context.Context, materials *EncryptionMaterials) (*CryptographicMaterials, error) { return nil, fmt.Errorf("KmsAnyKeyKeyring MUST NOT be used to encrypt new data") } // OnDecrypt decrypts the encryptedDataKeys and returns them in materials // for use with content decryption, or an error if the object cannot be decrypted // by the Keyring as its configured. func (k *KmsAnyKeyKeyring) OnDecrypt(ctx context.Context, materials *DecryptionMaterials, encryptedDataKey DataKey) (*CryptographicMaterials, error) { if materials.DataKey.DataKeyAlgorithm == KMSKeyring && k.legacyWrappingAlgorithms { return commonDecrypt(ctx, materials, encryptedDataKey, nil, nil, k.kmsClient) } else if materials.DataKey.DataKeyAlgorithm == KMSContextKeyring { return commonDecrypt(ctx, materials, encryptedDataKey, nil, materials.MaterialDescription, k.kmsClient) } else { return nil, fmt.Errorf("x-amz-cek-alg value `%s` did not match an expected algorithm", materials.DataKey.DataKeyAlgorithm) } } func (k *KmsAnyKeyKeyring) isAWSFixture() bool { return true } func commonDecrypt(ctx context.Context, materials *DecryptionMaterials, encryptedDataKey DataKey, kmsKeyId *string, matDesc MaterialDescription, kmsClient KmsAPIClient) (*CryptographicMaterials, error) { if matDesc != nil { if v, ok := matDesc[kmsAWSCEKContextKey]; !ok { return nil, fmt.Errorf("required key %v is missing from encryption context", kmsAWSCEKContextKey) } else if v != materials.ContentAlgorithm { return nil, fmt.Errorf(kmsMismatchCEKAlg) } } in := &kms.DecryptInput{ EncryptionContext: materials.MaterialDescription, CiphertextBlob: encryptedDataKey.EncryptedDataKey, KeyId: kmsKeyId, } grantTokens := ctx.Value(GrantTokens) if grantTokens != nil { in.GrantTokens = grantTokens.([]string) } out, err := kmsClient.Decrypt(ctx, in) if err != nil { return nil, err } materials.DataKey.KeyMaterial = out.Plaintext cryptoMaterials := &CryptographicMaterials{ Key: out.Plaintext, IV: materials.ContentIV, KeyringAlgorithm: materials.DataKey.DataKeyAlgorithm, CEKAlgorithm: materials.ContentAlgorithm, TagLength: materials.TagLength, MaterialDescription: materials.MaterialDescription, EncryptedKey: materials.DataKey.EncryptedDataKey, } return cryptoMaterials, nil }