Class: ActiveCipherStorage::Providers::AwsKmsProvider

Inherits:
Base
  • Object
show all
Includes:
KeyUtils
Defined in:
lib/active_cipher_storage/providers/aws_kms_provider.rb

Constant Summary collapse

PROVIDER_ID =
"aws_kms"

Instance Method Summary collapse

Constructor Details

#initialize(key_id: nil, region: nil, encryption_context: {}, client: nil) ⇒ AwsKmsProvider

Returns a new instance of AwsKmsProvider.



8
9
10
11
12
13
14
15
16
# File 'lib/active_cipher_storage/providers/aws_kms_provider.rb', line 8

def initialize(key_id: nil, region: nil, encryption_context: {}, client: nil)
  @key_id             = key_id || ENV.fetch("AWS_KMS_KEY_ID") {
    raise Errors::ProviderError,
          "AwsKmsProvider requires :key_id or AWS_KMS_KEY_ID env var"
  }
  @region             = region
  @encryption_context = encryption_context || {}
  @client_override    = client
end

Instance Method Details

#decrypt_data_key(encrypted_key) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/active_cipher_storage/providers/aws_kms_provider.rb', line 35

def decrypt_data_key(encrypted_key)
  resp = kms_client.decrypt(
    ciphertext_blob:    encrypted_key,
    encryption_context: @encryption_context
  )
  resp.plaintext.dup
rescue Aws::KMS::Errors::InvalidCiphertextException,
       Aws::KMS::Errors::IncorrectKeyException => e
  raise Errors::KeyManagementError,
        "KMS Decrypt failed — wrong key or tampered DEK: #{e.message}"
rescue Aws::KMS::Errors::ServiceError => e
  raise Errors::KeyManagementError, "KMS Decrypt failed: #{e.message}"
ensure
  resp&.plaintext&.then { |k| zero_bytes!(k) }
end

#generate_data_keyObject



21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/active_cipher_storage/providers/aws_kms_provider.rb', line 21

def generate_data_key
  resp = kms_client.generate_data_key(
    key_id:             @key_id,
    key_spec:           "AES_256",
    encryption_context: @encryption_context
  )
  { plaintext_key: resp.plaintext.dup, encrypted_key: resp.ciphertext_blob.dup }
rescue Aws::KMS::Errors::ServiceError => e
  raise Errors::KeyManagementError, "KMS GenerateDataKey failed: #{e.message}"
ensure
  # AWS SDK may retain a reference to resp.plaintext; zero our copy too.
  resp&.plaintext&.then { |k| zero_bytes!(k) }
end

#key_idObject



19
# File 'lib/active_cipher_storage/providers/aws_kms_provider.rb', line 19

def key_id      = @key_id

#provider_idObject



18
# File 'lib/active_cipher_storage/providers/aws_kms_provider.rb', line 18

def provider_id = PROVIDER_ID

#rotate_data_key(encrypted_key, destination_key_id: nil) ⇒ Object

Uses KMS ReEncrypt — the plaintext DEK never leaves KMS.



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/active_cipher_storage/providers/aws_kms_provider.rb', line 66

def rotate_data_key(encrypted_key, destination_key_id: nil)
  resp = kms_client.re_encrypt(
    ciphertext_blob:                encrypted_key,
    source_encryption_context:      @encryption_context,
    destination_key_id:             destination_key_id || @key_id,
    destination_encryption_context: @encryption_context
  )
  resp.ciphertext_blob.dup
rescue Aws::KMS::Errors::ServiceError => e
  raise Errors::KeyManagementError, "KMS ReEncrypt failed: #{e.message}"
end

#wrap_data_key(plaintext_dek) ⇒ Object

Encrypts an existing plaintext DEK using KMS Encrypt. Prefer rotate_data_key (ReEncrypt) when both old and new providers are AWS KMS, because ReEncrypt keeps the plaintext DEK entirely within KMS.



54
55
56
57
58
59
60
61
62
63
# File 'lib/active_cipher_storage/providers/aws_kms_provider.rb', line 54

def wrap_data_key(plaintext_dek)
  resp = kms_client.encrypt(
    key_id:             @key_id,
    plaintext:          plaintext_dek,
    encryption_context: @encryption_context
  )
  resp.ciphertext_blob.dup
rescue Aws::KMS::Errors::ServiceError => e
  raise Errors::KeyManagementError, "KMS Encrypt failed: #{e.message}"
end