From 3f89d37a02567fe378b229534b4aa58521f682d4 Mon Sep 17 00:00:00 2001 From: geemus Date: Mon, 9 Dec 2024 15:51:36 +0000 Subject: [PATCH 01/23] add kms test lint fixes --- tests/requests/kms/helper.rb | 28 ++++++++++++++-------------- tests/requests/kms/key_tests.rb | 11 +++++------ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/tests/requests/kms/helper.rb b/tests/requests/kms/helper.rb index 9c3d97c733..d9aeff0b24 100644 --- a/tests/requests/kms/helper.rb +++ b/tests/requests/kms/helper.rb @@ -3,25 +3,25 @@ module KMS module Formats BASIC = { 'ResponseMetadata' => { 'RequestId' => String } - } + }.freeze DESCRIBE_KEY = { - "KeyMetadata" => { - "KeyUsage" => String, - "AWSAccountId" => String, - "KeyId" => String, - "Description" => Fog::Nullable::String, - "CreationDate" => Time, - "Arn" => String, - "Enabled" => Fog::Boolean + 'KeyMetadata' => { + 'KeyUsage' => String, + 'AWSAccountId' => String, + 'KeyId' => String, + 'Description' => Fog::Nullable::String, + 'CreationDate' => Time, + 'Arn' => String, + 'Enabled' => Fog::Boolean } - } + }.freeze LIST_KEYS = { - "Keys" => [{ "KeyId" => String, "KeyArn" => String }], - "Truncated" => Fog::Boolean, - "Marker" => Fog::Nullable::String - } + 'Keys' => [{ 'KeyId' => String, 'KeyArn' => String }], + 'Truncated' => Fog::Boolean, + 'Marker' => Fog::Nullable::String + }.freeze end end end diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index b65f162cba..f347e68d66 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -1,23 +1,22 @@ -Shindo.tests('AWS::KMS | key requests', ['aws', 'kms']) do +Shindo.tests('AWS::KMS | key requests', %w[aws kms]) do key_id = nil tests('success') do - tests("#create_key").data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) do + tests('#create_key').data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) do result = Fog::AWS[:kms].create_key.body - key_id = result["KeyMetadata"]["KeyId"] + key_id = result['KeyMetadata']['KeyId'] result end end - tests("#describe_key").data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) do + tests('#describe_key').data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) do result = Fog::AWS[:kms].describe_key(key_id).body returns(key_id) { result['KeyMetadata']['KeyId'] } result end - tests("#list_keys").data_matches_schema(AWS::KMS::Formats::LIST_KEYS) do + tests('#list_keys').data_matches_schema(AWS::KMS::Formats::LIST_KEYS) do Fog::AWS[:kms].list_keys.body end - end From db1faeb133456d9d925d190d7a7877b631d1a538 Mon Sep 17 00:00:00 2001 From: geemus Date: Mon, 9 Dec 2024 19:53:35 +0000 Subject: [PATCH 02/23] remove unused default key policy constant from KMS create key request --- lib/fog/aws/requests/kms/create_key.rb | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index eb712c53b3..8e39dc2f08 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -2,24 +2,6 @@ module Fog module AWS class KMS class Real - DEFAULT_KEY_POLICY = <<-JSON -{ - "Version": "2012-10-17", - "Id": "key-default-1", - "Statement": [ - { - "Sid": "Enable IAM User Permissions", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::915445820265:root" - }, - "Action": "kms:*", - "Resource": "*" - } - ] -} - JSON - require 'fog/aws/parsers/kms/describe_key' def create_key(policy = nil, description = nil, usage = "ENCRYPT_DECRYPT") From 010b62125f2ec1c6597c437e3d0fb383a4fa63a1 Mon Sep 17 00:00:00 2001 From: geemus Date: Mon, 9 Dec 2024 21:37:11 +0000 Subject: [PATCH 03/23] minor cleanup and add first pass at schedule_key_deletion method and mock --- lib/fog/aws/kms.rb | 1 + lib/fog/aws/parsers/kms/describe_key.rb | 4 +- .../aws/parsers/kms/schedule_key_deletion.rb | 28 ++++++++++++++ lib/fog/aws/requests/kms/create_key.rb | 30 ++++++++------- lib/fog/aws/requests/kms/list_keys.rb | 6 +-- .../aws/requests/kms/schedule_key_deletion.rb | 37 +++++++++++++++++++ tests/requests/kms/helper.rb | 25 +++++++++---- tests/requests/kms/key_tests.rb | 4 ++ 8 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 lib/fog/aws/parsers/kms/schedule_key_deletion.rb create mode 100644 lib/fog/aws/requests/kms/schedule_key_deletion.rb diff --git a/lib/fog/aws/kms.rb b/lib/fog/aws/kms.rb index 3d176cbe61..7c2aa300fb 100644 --- a/lib/fog/aws/kms.rb +++ b/lib/fog/aws/kms.rb @@ -20,6 +20,7 @@ class KMS < Fog::Service request :list_keys request :create_key request :describe_key + request :schedule_key_deletion model_path 'fog/aws/models/kms' model :key diff --git a/lib/fog/aws/parsers/kms/describe_key.rb b/lib/fog/aws/parsers/kms/describe_key.rb index d5597bc483..cf8dbc8491 100644 --- a/lib/fog/aws/parsers/kms/describe_key.rb +++ b/lib/fog/aws/parsers/kms/describe_key.rb @@ -17,9 +17,9 @@ def start_element(name, attrs = []) def end_element(name) case name - when 'KeyUsage', 'AWSAccountId', 'Description', 'KeyId', 'Arn' + when 'Arn', 'AWSAccountId', 'Description', 'KeyId', 'KeyState', 'KeyUsage' @key[name] = value - when 'CreationDate' + when 'CreationDate', 'DeletionDate' @key[name] = Time.parse(value) when 'Enabled' @key[name] = (value == 'true') diff --git a/lib/fog/aws/parsers/kms/schedule_key_deletion.rb b/lib/fog/aws/parsers/kms/schedule_key_deletion.rb new file mode 100644 index 0000000000..947c0a392f --- /dev/null +++ b/lib/fog/aws/parsers/kms/schedule_key_deletion.rb @@ -0,0 +1,28 @@ +module Fog + module Parsers + module AWS + module KMS + class ScheduleKeyDeletion < Fog::Parsers::Base + def reset + @response = {} + end + + def start_element(name, attrs = []) + super + end + + def end_element(name) + case name + when 'DeletionDate' + @response[name] = Time.parse(value) + when 'KeyId', 'KeyState' + @response[name] = value + when 'PendingWindowInDays' + @response[name] = value.to_i + end + end + end + end + end + end +end diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index 8e39dc2f08..35bf4b8f7d 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -4,38 +4,40 @@ class KMS class Real require 'fog/aws/parsers/kms/describe_key' - def create_key(policy = nil, description = nil, usage = "ENCRYPT_DECRYPT") + def create_key(policy = nil, description = nil, usage = 'ENCRYPT_DECRYPT') request( - 'Action' => 'CreateKey', + 'Action' => 'CreateKey', 'Description' => description, - 'KeyUsage' => usage, - 'Policy' => policy, - :parser => Fog::Parsers::AWS::KMS::DescribeKey.new + 'KeyUsage' => usage, + 'Policy' => policy, + :parser => Fog::Parsers::AWS::KMS::DescribeKey.new ) end end class Mock - def create_key(policy = nil, description = nil, usage = "ENCRYPT_DECRYPT") + def create_key(_policy = nil, description = nil, usage = 'ENCRYPT_DECRYPT') response = Excon::Response.new key_id = UUID.uuid key_arn = Fog::AWS::Mock.arn("kms", self.account_id, "key/#{key_id}", @region) key = { - "KeyUsage" => usage, - "AWSAccountId" => self.account_id, - "KeyId" => key_id, - "Description" => description, - "CreationDate" => Time.now, - "Arn" => key_arn, - "Enabled" => true, + 'Arn' => key_arn, + 'AWSAccountId' => self.account_id, + 'CreationDate' => Time.now.utc, + 'DeletionDate' => nil, + 'Description' => description, + 'Enabled' => true, + 'KeyId' => key_id, + 'KeyState' => 'Enabled', + 'KeyUsage' => usage } # @todo use default policy self.data[:keys][key_id] = key - response.body = { "KeyMetadata" => key } + response.body = { 'KeyMetadata' => key } response end end diff --git a/lib/fog/aws/requests/kms/list_keys.rb b/lib/fog/aws/requests/kms/list_keys.rb index d2415ad534..bd72b0edfb 100644 --- a/lib/fog/aws/requests/kms/list_keys.rb +++ b/lib/fog/aws/requests/kms/list_keys.rb @@ -43,9 +43,9 @@ def list_keys(options={}) key_set = if marker self.data[:markers][marker] || [] else - self.data[:keys].inject([]) { |r,(k,v)| - r << { "KeyId" => k, "KeyArn" => v["Arn"] } - } + self.data[:keys].inject([]) do |r, (k, v)| + r << { 'KeyArn' => v['Arn'], 'KeyId' => k } + end end keys = if limit diff --git a/lib/fog/aws/requests/kms/schedule_key_deletion.rb b/lib/fog/aws/requests/kms/schedule_key_deletion.rb new file mode 100644 index 0000000000..f03fa7803f --- /dev/null +++ b/lib/fog/aws/requests/kms/schedule_key_deletion.rb @@ -0,0 +1,37 @@ +module Fog + module AWS + class KMS + class Real + require 'fog/aws/parsers/kms/schedule_key_deletion' + + def schedule_key_deletion(identifier, pending_window_in_days) + request( + 'Action' => 'ScheduleKeyDeletion', + 'KeyId' => identifier, + 'PendingWindowInDays' => pending_window_in_days, + :parser => Fog::Parsers::AWS::KMS::ScheduleKeyDeletion.new + ) + end + end + + class Mock + def schedule_key_deletion(identifier, pending_window_in_days) + response = Excon::Response.new + key = self.data[:keys][identifier] + + key['DeletionDate'] = Time.now + (60 * 60 * 24 * pending_window_in_days) + key['Enabled'] = false + key['KeyState'] = 'PendingDeletion' + + response.body = { + 'DeletionDate' => key['DeletionDate'], + 'KeyId' => key['KeyId'], + 'KeyState' => key['KeyState'], + 'PendingWindowInDays' => pending_window_in_days + } + response + end + end + end + end +end diff --git a/tests/requests/kms/helper.rb b/tests/requests/kms/helper.rb index d9aeff0b24..628e2d420d 100644 --- a/tests/requests/kms/helper.rb +++ b/tests/requests/kms/helper.rb @@ -7,20 +7,29 @@ module Formats DESCRIBE_KEY = { 'KeyMetadata' => { - 'KeyUsage' => String, + 'Arn' => String, 'AWSAccountId' => String, - 'KeyId' => String, - 'Description' => Fog::Nullable::String, 'CreationDate' => Time, - 'Arn' => String, - 'Enabled' => Fog::Boolean + 'DeletionDate' => Fog::Nullable::Time, + 'Description' => Fog::Nullable::String, + 'Enabled' => Fog::Boolean, + 'KeyId' => String, + 'KeyState' => String, + 'KeyUsage' => String } }.freeze LIST_KEYS = { - 'Keys' => [{ 'KeyId' => String, 'KeyArn' => String }], - 'Truncated' => Fog::Boolean, - 'Marker' => Fog::Nullable::String + 'Keys' => [{ 'KeyArn' => String, 'KeyId' => String }], + 'Marker' => Fog::Nullable::String, + 'Truncated' => Fog::Boolean + }.freeze + + SCHEDULE_KEY_DELETION = { + 'DeletionDate' => Time, + 'KeyId' => String, + 'KeyState' => String, + 'PendingWindowInDays' => Integer }.freeze end end diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index f347e68d66..81adbe93ce 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -19,4 +19,8 @@ tests('#list_keys').data_matches_schema(AWS::KMS::Formats::LIST_KEYS) do Fog::AWS[:kms].list_keys.body end + + tests('#schedule_key_deletion').data_matches_schema(AWS::KMS::Formats::SCHEDULE_KEY_DELETION) do + Fog::AWS[:kms].schedule_key_deletion(key_id, 7) + end end From c6c4a25a8466ea532a97201ee66c0a4410180288 Mon Sep 17 00:00:00 2001 From: geemus Date: Mon, 9 Dec 2024 21:47:29 +0000 Subject: [PATCH 04/23] fix for schedule_key_deletion test --- tests/requests/kms/key_tests.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index 81adbe93ce..1304c2e1bf 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -21,6 +21,6 @@ end tests('#schedule_key_deletion').data_matches_schema(AWS::KMS::Formats::SCHEDULE_KEY_DELETION) do - Fog::AWS[:kms].schedule_key_deletion(key_id, 7) + Fog::AWS[:kms].schedule_key_deletion(key_id, 7).body end end From 13094ba91fa7963d7e7d0ce866926c5016a24398 Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 15:53:38 +0000 Subject: [PATCH 05/23] [kms] convert create_key to options hash instead of positional arguments --- lib/fog/aws/requests/kms/create_key.rb | 55 +++++++++++++++++++++----- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index 35bf4b8f7d..a724892e1b 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -4,19 +4,32 @@ class KMS class Real require 'fog/aws/parsers/kms/describe_key' - def create_key(policy = nil, description = nil, usage = 'ENCRYPT_DECRYPT') + # Create Key + # + # ==== Parameters + # * options<~Hash>: + # * 'Description'<~String>: + # * 'KeyUsage'<~String>: + # * 'Policy'<~String>: + # * ... (see docs from see also) + # + # === Returns + # + # ==== See Also + # https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateKey.html + def create_key(*args) + options = Fog::AWS::KMS.parse_create_key_args(args) request( 'Action' => 'CreateKey', - 'Description' => description, - 'KeyUsage' => usage, - 'Policy' => policy, :parser => Fog::Parsers::AWS::KMS::DescribeKey.new - ) + ).merge!(options) end end class Mock - def create_key(_policy = nil, description = nil, usage = 'ENCRYPT_DECRYPT') + def create_key(*args) + options = Fog::AWS::KMS.parse_create_key_args(args) + response = Excon::Response.new key_id = UUID.uuid key_arn = Fog::AWS::Mock.arn("kms", self.account_id, "key/#{key_id}", @region) @@ -26,12 +39,10 @@ def create_key(_policy = nil, description = nil, usage = 'ENCRYPT_DECRYPT') 'AWSAccountId' => self.account_id, 'CreationDate' => Time.now.utc, 'DeletionDate' => nil, - 'Description' => description, 'Enabled' => true, 'KeyId' => key_id, 'KeyState' => 'Enabled', - 'KeyUsage' => usage - } + }.merge!(options) # @todo use default policy @@ -41,6 +52,32 @@ def create_key(_policy = nil, description = nil, usage = 'ENCRYPT_DECRYPT') response end end + + # previous args (policy, description, usage) was deprecated in favor of a hash of options + def self.parse_create_key_args(args) + case args.size + when 0 + {} + when 1 + if args[0].is_a?(Hash) + args[0] + else + Fog::Logger.deprecation("create_key with distinct arguments is deprecated, use options hash instead [light_black](#{caller.first})[/]") + { + 'Policy' => args[0] + } + end + when 2, 3 + Fog::Logger.deprecation("create_key with distinct arguments is deprecated, use options hash instead [light_black](#{caller.first})[/]") + { + 'Policy' => args[0], + 'Description' => args[1], + 'KeyUsage' => args[2] || 'ENCRYPT_DECRYPT' + } + else + raise "Unknown argument style: #{args.inspect}, use options hash instead." + end + end end end end From 48e1d830b56a86aab8784ca42896e94ffd6ace08 Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 16:04:28 +0000 Subject: [PATCH 06/23] fix mock data after options hash change --- lib/fog/aws/requests/kms/create_key.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index a724892e1b..484771b71b 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -39,9 +39,12 @@ def create_key(*args) 'AWSAccountId' => self.account_id, 'CreationDate' => Time.now.utc, 'DeletionDate' => nil, + 'Description' => nil, 'Enabled' => true, 'KeyId' => key_id, 'KeyState' => 'Enabled', + 'KeyUsage' => 'ENCRYPT_DECRYPT', + 'Policy' => nil }.merge!(options) # @todo use default policy From 4b9178343a7d393ef1e55a8ab6d0081f68fa02e2 Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 16:31:52 +0000 Subject: [PATCH 07/23] add KeySpec to create_key mock and expected format in tests --- lib/fog/aws/requests/kms/create_key.rb | 1 + tests/requests/kms/helper.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index 484771b71b..9c704ba715 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -42,6 +42,7 @@ def create_key(*args) 'Description' => nil, 'Enabled' => true, 'KeyId' => key_id, + 'KeySpec' => 'SYMMETRIC_DEFAULT', 'KeyState' => 'Enabled', 'KeyUsage' => 'ENCRYPT_DECRYPT', 'Policy' => nil diff --git a/tests/requests/kms/helper.rb b/tests/requests/kms/helper.rb index 628e2d420d..da10bd2041 100644 --- a/tests/requests/kms/helper.rb +++ b/tests/requests/kms/helper.rb @@ -14,6 +14,7 @@ module Formats 'Description' => Fog::Nullable::String, 'Enabled' => Fog::Boolean, 'KeyId' => String, + 'KeySpec' => String, 'KeyState' => String, 'KeyUsage' => String } From 351512fe7c5803b9c1e129d0b4c18b2fa6c8845d Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 18:04:17 +0000 Subject: [PATCH 08/23] first pass at get_public_key requests and mocks --- lib/fog/aws/kms.rb | 4 ++- lib/fog/aws/parsers/kms/get_public_key.rb | 30 +++++++++++++++++++ lib/fog/aws/requests/kms/create_key.rb | 12 ++++++-- lib/fog/aws/requests/kms/get_public_key.rb | 35 ++++++++++++++++++++++ lib/fog/aws/requests/kms/list_keys.rb | 1 - tests/requests/kms/helper.rb | 10 +++++++ tests/requests/kms/key_tests.rb | 6 ++++ 7 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 lib/fog/aws/parsers/kms/get_public_key.rb create mode 100644 lib/fog/aws/requests/kms/get_public_key.rb diff --git a/lib/fog/aws/kms.rb b/lib/fog/aws/kms.rb index 7c2aa300fb..3b65dd2303 100644 --- a/lib/fog/aws/kms.rb +++ b/lib/fog/aws/kms.rb @@ -20,6 +20,7 @@ class KMS < Fog::Service request :list_keys request :create_key request :describe_key + request :get_public_key request :schedule_key_deletion model_path 'fog/aws/models/kms' @@ -31,7 +32,8 @@ def self.data @data ||= Hash.new do |hash, region| hash[region] = Hash.new do |region_hash, access_key| region_hash[access_key] = { - :keys => {}, + keys: {}, + pkeys: {} } end end diff --git a/lib/fog/aws/parsers/kms/get_public_key.rb b/lib/fog/aws/parsers/kms/get_public_key.rb new file mode 100644 index 0000000000..365f5fccea --- /dev/null +++ b/lib/fog/aws/parsers/kms/get_public_key.rb @@ -0,0 +1,30 @@ +module Fog + module Parsers + module AWS + module KMS + class GetPublicKey < Fog::Parsers::Base + def reset + @response = {} + end + + def start_element(name, attrs = []) + super + case name + when 'EncryptionAlgorithms', 'KeyAgreementAlgorithms', 'SigningAlgorithms' + @response[name] = [] + end + end + + def end_element(name) + case name + when 'KeyId', 'KeySpec', 'KeyUsage', 'PublicKey' + @response[name] = value + when 'EncryptionAlgorithms', 'KeyAgreementAlgorithms', 'SigningAlgorithms' + @response[name] << value + end + end + end + end + end + end +end diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index 9c704ba715..a97dbcfbf0 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -19,10 +19,10 @@ class Real # https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateKey.html def create_key(*args) options = Fog::AWS::KMS.parse_create_key_args(args) - request( + request({ 'Action' => 'CreateKey', :parser => Fog::Parsers::AWS::KMS::DescribeKey.new - ).merge!(options) + }.merge!(options)) end end @@ -52,6 +52,14 @@ def create_key(*args) self.data[:keys][key_id] = key + spec = key['KeySpec'] + size = spec.split('_').last + spec = spec.split("_#{size}").first + case spec + when 'RSA' + self.data[:pkeys][key_id] = OpenSSL::PKey::RSA.generate(size) + end + response.body = { 'KeyMetadata' => key } response end diff --git a/lib/fog/aws/requests/kms/get_public_key.rb b/lib/fog/aws/requests/kms/get_public_key.rb new file mode 100644 index 0000000000..f84ee8b315 --- /dev/null +++ b/lib/fog/aws/requests/kms/get_public_key.rb @@ -0,0 +1,35 @@ +module Fog + module AWS + class KMS + class Real + require 'fog/aws/parsers/kms/get_public_key' + + def get_public_key(identifier, grant_tokens = nil) + request( + 'Action' => 'GetPublicKey', + 'GrantTokens' => grant_tokens, + 'KeyId' => identifier, + :parser => Fog::Parsers::AWS::KMS::GetPublicKey.new + ) + end + end + + class Mock + def get_public_key(identifier, grant_tokens = []) + response = Excon::Response.new + key = self.data[:keys][identifier] + pkey = self.data[:pkeys][identifier] + + response.body = { + 'KeyId' => key['KeyId'], + 'KeyUsage' => key['KeyUsage'], + 'KeySpec' => key['KeySpec'], + 'PublicKey' => Base64.strict_encode64(pkey.public_to_der), + 'SigningAlgorithms' => key['SigningAlgorithms'] + } + response + end + end + end + end +end diff --git a/lib/fog/aws/requests/kms/list_keys.rb b/lib/fog/aws/requests/kms/list_keys.rb index bd72b0edfb..20cc10e6ee 100644 --- a/lib/fog/aws/requests/kms/list_keys.rb +++ b/lib/fog/aws/requests/kms/list_keys.rb @@ -2,7 +2,6 @@ module Fog module AWS class KMS class Real - require 'fog/aws/parsers/kms/list_keys' def list_keys(options={}) diff --git a/tests/requests/kms/helper.rb b/tests/requests/kms/helper.rb index da10bd2041..14a3abe567 100644 --- a/tests/requests/kms/helper.rb +++ b/tests/requests/kms/helper.rb @@ -20,6 +20,16 @@ module Formats } }.freeze + GET_PUBLIC_KEY = { + 'EncryptionAlgorithms' => Fog::Nullable::Array, + 'KeyAgreementAlgorithms' => Fog::Nullable::Array, + 'KeyId' => String, + 'KeySpec' => String, + 'KeyUsage' => String, + 'PublicKey' => String, + 'SigningAlgorithms' => Fog::Nullable::Array + }.freeze + LIST_KEYS = { 'Keys' => [{ 'KeyArn' => String, 'KeyId' => String }], 'Marker' => Fog::Nullable::String, diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index 1304c2e1bf..0ae20064ec 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -16,6 +16,12 @@ result end + tests('#get_public_key').data_matches_schema(AWS::KMS::Formats::GET_PUBLIC_KEY) do + result = Fog::AWS[:kms].get_public_key(key_id).body + returns(key_id) { result['KeyId'] } + result + end + tests('#list_keys').data_matches_schema(AWS::KMS::Formats::LIST_KEYS) do Fog::AWS[:kms].list_keys.body end From 6e78c022f602c990ceefc3e19fb4b3ae76075339 Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 18:11:16 +0000 Subject: [PATCH 09/23] fix params for create_key in tests so that public_key/signing mocks will match --- tests/requests/kms/key_tests.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index 0ae20064ec..caba3c5a95 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -3,7 +3,10 @@ tests('success') do tests('#create_key').data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) do - result = Fog::AWS[:kms].create_key.body + result = Fog::AWS[:kms].create_key( + 'KeySpec' => 'RSA_2048', + 'KeyUsage' => 'SIGN_VERIFY' + ).body key_id = result['KeyMetadata']['KeyId'] result From d8cf0a21f84b44889e1bbbe5c040f7379d5c02d8 Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 19:51:10 +0000 Subject: [PATCH 10/23] size for rsa key should be cast to integer --- lib/fog/aws/requests/kms/create_key.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index a97dbcfbf0..4b73e33035 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -57,7 +57,7 @@ def create_key(*args) spec = spec.split("_#{size}").first case spec when 'RSA' - self.data[:pkeys][key_id] = OpenSSL::PKey::RSA.generate(size) + self.data[:pkeys][key_id] = OpenSSL::PKey::RSA.generate(size.to_i) end response.body = { 'KeyMetadata' => key } From feeb15b274498c251eb1ba9eb87de6048e3e884b Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 21:40:45 +0000 Subject: [PATCH 11/23] add mocks for ECC pkeys --- lib/fog/aws/requests/kms/create_key.rb | 15 +++++++++++---- lib/fog/aws/requests/kms/get_public_key.rb | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index 4b73e33035..dac17d4fef 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -52,10 +52,17 @@ def create_key(*args) self.data[:keys][key_id] = key - spec = key['KeySpec'] - size = spec.split('_').last - spec = spec.split("_#{size}").first - case spec + size = key['KeySpec'].split('_').last + type = key['KeySpec'].split('_').first + case type + when 'ECC' + curve = { + 'ECC_NIST_P256' => 'secp256k1', + 'ECC_NIST_P384' => 'secp384r1', + 'ECC_NIST_P521' => 'secp521r1', + 'ECC_SECG_P256K1' => 'prime256v1' + }[key['KeySpec']] + self.data[:pkeys][key_id] = OpenSSL::PKey::EC.generate(curve) when 'RSA' self.data[:pkeys][key_id] = OpenSSL::PKey::RSA.generate(size.to_i) end diff --git a/lib/fog/aws/requests/kms/get_public_key.rb b/lib/fog/aws/requests/kms/get_public_key.rb index f84ee8b315..db4e66df6b 100644 --- a/lib/fog/aws/requests/kms/get_public_key.rb +++ b/lib/fog/aws/requests/kms/get_public_key.rb @@ -15,7 +15,7 @@ def get_public_key(identifier, grant_tokens = nil) end class Mock - def get_public_key(identifier, grant_tokens = []) + def get_public_key(identifier, _grant_tokens = []) response = Excon::Response.new key = self.data[:keys][identifier] pkey = self.data[:pkeys][identifier] From 68e717e4fba47a79b673b46630a07c8006698164 Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 22:13:34 +0000 Subject: [PATCH 12/23] first pass at kms sign request and mocks --- lib/fog/aws/kms.rb | 1 + lib/fog/aws/parsers/kms/sign.rb | 24 ++++++++++++ lib/fog/aws/requests/kms/create_key.rb | 26 ++++++------- lib/fog/aws/requests/kms/sign.rb | 54 ++++++++++++++++++++++++++ tests/requests/kms/helper.rb | 6 +++ tests/requests/kms/key_tests.rb | 4 ++ 6 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 lib/fog/aws/parsers/kms/sign.rb create mode 100644 lib/fog/aws/requests/kms/sign.rb diff --git a/lib/fog/aws/kms.rb b/lib/fog/aws/kms.rb index 3b65dd2303..8cc0106419 100644 --- a/lib/fog/aws/kms.rb +++ b/lib/fog/aws/kms.rb @@ -22,6 +22,7 @@ class KMS < Fog::Service request :describe_key request :get_public_key request :schedule_key_deletion + request :sign model_path 'fog/aws/models/kms' model :key diff --git a/lib/fog/aws/parsers/kms/sign.rb b/lib/fog/aws/parsers/kms/sign.rb new file mode 100644 index 0000000000..3a52e8b76e --- /dev/null +++ b/lib/fog/aws/parsers/kms/sign.rb @@ -0,0 +1,24 @@ +module Fog + module Parsers + module AWS + module KMS + class Sign < Fog::Parsers::Base + def reset + @response = {} + end + + def start_element(name, attrs = []) + super + end + + def end_element(name) + case name + when 'KeyId', 'Signature', 'SigningAlgorithm' + @response[name] = value + end + end + end + end + end + end +end diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index dac17d4fef..6912a6ec7f 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -52,20 +52,18 @@ def create_key(*args) self.data[:keys][key_id] = key - size = key['KeySpec'].split('_').last - type = key['KeySpec'].split('_').first - case type - when 'ECC' - curve = { - 'ECC_NIST_P256' => 'secp256k1', - 'ECC_NIST_P384' => 'secp384r1', - 'ECC_NIST_P521' => 'secp521r1', - 'ECC_SECG_P256K1' => 'prime256v1' - }[key['KeySpec']] - self.data[:pkeys][key_id] = OpenSSL::PKey::EC.generate(curve) - when 'RSA' - self.data[:pkeys][key_id] = OpenSSL::PKey::RSA.generate(size.to_i) - end + klass, arg = { + 'ECC_NIST_P256' => [OpenSSL::PKey::EC, 'secp256k1'], + 'ECC_NIST_P384' => [OpenSSL::PKey::EC, 'secp384r1'], + 'ECC_NIST_P521' => [OpenSSL::PKey::EC, 'secp521r1'], + 'ECC_SECG_P256K1' => [OpenSSL::PKey::EC, 'prime256v1'], + 'RSA_2048' => [OpenSSL::PKey::RSA, 2048], + 'RSA_3072' => [OpenSSL::PKey::RSA, 3072], + 'RSA_4096' => [OpenSSL::PKey::RSA, 4096] + }[key['KeySpec']] + raise "Unknown or not-yet-implemented #{key['KeySpec']} KeySpec for kms create_key mocks" unless klass + + self.data[:pkeys][key_id] = klass.generate(arg) response.body = { 'KeyMetadata' => key } response diff --git a/lib/fog/aws/requests/kms/sign.rb b/lib/fog/aws/requests/kms/sign.rb new file mode 100644 index 0000000000..0519046b2a --- /dev/null +++ b/lib/fog/aws/requests/kms/sign.rb @@ -0,0 +1,54 @@ +module Fog + module AWS + class KMS + class Real + require 'fog/aws/parsers/kms/sign' + + # Sign + # + # ==== Parameters + # + # === Returns + # + # ==== See Also + # https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html + def sign(identifier, message, algorithm, options = {}) + request({ + 'Action' => 'Sign', + 'KeyId' => identifier, + 'Message' => message, + 'SigningAlgorithm' => algorithm, + :parser => Fog::Parsers::AWS::KMS::Sign.new + }.merge!(options)) + end + end + + class Mock + def sign(identifier, message, algorithm, _options = {}) + response = Excon::Response.new + pkey = self.data[:pkeys][identifier] + unless pkey + response.status = 404 + raise(Excon::Errors.status_error({ expects: 200 }, response)) + end + + # FIXME: SM2 support? + sha = "SHA#{algorithm.split('_SHA_').last}" + hash = OpenSSL::Digest.digest(sha, message) + + signopts = {} + signopts[:rsa_padding_mode] = 'pss' if algorithm.start_with?('RSASSA_PSS') + + signature = pkey.sign_raw(sha, hash, signopts) + + response.body = { + 'KeyId' => identifier, + 'Signature' => Base64.strict_encode64(signature), + 'SigningAlgorithm' => algorithm + } + response + end + end + end + end +end diff --git a/tests/requests/kms/helper.rb b/tests/requests/kms/helper.rb index 14a3abe567..5e27a6b418 100644 --- a/tests/requests/kms/helper.rb +++ b/tests/requests/kms/helper.rb @@ -42,6 +42,12 @@ module Formats 'KeyState' => String, 'PendingWindowInDays' => Integer }.freeze + + SIGN = { + 'KeyId' => String, + 'Signature' => String, + 'SigningAlgorithm' => String + }.freeze end end end diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index caba3c5a95..5fd71e4b38 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -29,6 +29,10 @@ Fog::AWS[:kms].list_keys.body end + tests('#sign').data_matches_schema(AWS::KMS::Formats::SIGN) do + Fog::AWS[:kms].sign(key_id, 'sign me', 'RSASSA_PSS_SHA_256').body + end + tests('#schedule_key_deletion').data_matches_schema(AWS::KMS::Formats::SCHEDULE_KEY_DELETION) do Fog::AWS[:kms].schedule_key_deletion(key_id, 7).body end From a6e2dab9ddfd322cc9d7d518344908913fbc8329 Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 22:20:03 +0000 Subject: [PATCH 13/23] simplify by using sign instead of sign_raw --- lib/fog/aws/requests/kms/sign.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/fog/aws/requests/kms/sign.rb b/lib/fog/aws/requests/kms/sign.rb index 0519046b2a..0d08885050 100644 --- a/lib/fog/aws/requests/kms/sign.rb +++ b/lib/fog/aws/requests/kms/sign.rb @@ -34,12 +34,11 @@ def sign(identifier, message, algorithm, _options = {}) # FIXME: SM2 support? sha = "SHA#{algorithm.split('_SHA_').last}" - hash = OpenSSL::Digest.digest(sha, message) signopts = {} signopts[:rsa_padding_mode] = 'pss' if algorithm.start_with?('RSASSA_PSS') - signature = pkey.sign_raw(sha, hash, signopts) + signature = pkey.sign(sha, message, signopts) response.body = { 'KeyId' => identifier, From 281213804f722289671d589c45cda6b5816b6635 Mon Sep 17 00:00:00 2001 From: geemus Date: Tue, 10 Dec 2024 22:38:07 +0000 Subject: [PATCH 14/23] switch to sign_pss, hopefully compatible with 3.0 --- lib/fog/aws/requests/kms/sign.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/fog/aws/requests/kms/sign.rb b/lib/fog/aws/requests/kms/sign.rb index 0d08885050..1e8b42f5d0 100644 --- a/lib/fog/aws/requests/kms/sign.rb +++ b/lib/fog/aws/requests/kms/sign.rb @@ -35,10 +35,11 @@ def sign(identifier, message, algorithm, _options = {}) # FIXME: SM2 support? sha = "SHA#{algorithm.split('_SHA_').last}" - signopts = {} - signopts[:rsa_padding_mode] = 'pss' if algorithm.start_with?('RSASSA_PSS') - - signature = pkey.sign(sha, message, signopts) + signature = if algorithm.start_with?('RSASSA_PSS') + pkey.sign_pss(sha, message, salt_length: :max, mgf1_hash: sha) + else + pkey.sign(sha, message) + end response.body = { 'KeyId' => identifier, From 37f60b69deaa50125e80c71122ee11398f9dd829 Mon Sep 17 00:00:00 2001 From: geemus Date: Wed, 11 Dec 2024 16:26:44 +0000 Subject: [PATCH 15/23] fix ec curve mapping --- lib/fog/aws/requests/kms/create_key.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fog/aws/requests/kms/create_key.rb b/lib/fog/aws/requests/kms/create_key.rb index 6912a6ec7f..2d7f16f999 100644 --- a/lib/fog/aws/requests/kms/create_key.rb +++ b/lib/fog/aws/requests/kms/create_key.rb @@ -53,10 +53,10 @@ def create_key(*args) self.data[:keys][key_id] = key klass, arg = { - 'ECC_NIST_P256' => [OpenSSL::PKey::EC, 'secp256k1'], + 'ECC_NIST_P256' => [OpenSSL::PKey::EC, 'prime256v1'], 'ECC_NIST_P384' => [OpenSSL::PKey::EC, 'secp384r1'], 'ECC_NIST_P521' => [OpenSSL::PKey::EC, 'secp521r1'], - 'ECC_SECG_P256K1' => [OpenSSL::PKey::EC, 'prime256v1'], + 'ECC_SECG_P256K1' => [OpenSSL::PKey::EC, 'secp256k1'], 'RSA_2048' => [OpenSSL::PKey::RSA, 2048], 'RSA_3072' => [OpenSSL::PKey::RSA, 3072], 'RSA_4096' => [OpenSSL::PKey::RSA, 4096] From 3e67ab05edbaa52dde034d3c346eb9056b725182 Mon Sep 17 00:00:00 2001 From: geemus Date: Wed, 11 Dec 2024 16:47:39 +0000 Subject: [PATCH 16/23] kms: mocks - cleanup signopts, support both raw and digest signing --- lib/fog/aws/requests/kms/sign.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/fog/aws/requests/kms/sign.rb b/lib/fog/aws/requests/kms/sign.rb index 1e8b42f5d0..1c249c04e4 100644 --- a/lib/fog/aws/requests/kms/sign.rb +++ b/lib/fog/aws/requests/kms/sign.rb @@ -24,7 +24,7 @@ def sign(identifier, message, algorithm, options = {}) end class Mock - def sign(identifier, message, algorithm, _options = {}) + def sign(identifier, message, algorithm, options = {}) response = Excon::Response.new pkey = self.data[:pkeys][identifier] unless pkey @@ -34,11 +34,13 @@ def sign(identifier, message, algorithm, _options = {}) # FIXME: SM2 support? sha = "SHA#{algorithm.split('_SHA_').last}" + signopts = {} + signopts[:rsa_padding_mode] = 'pss' if algorithm.start_with?('RSASSA_PSS') - signature = if algorithm.start_with?('RSASSA_PSS') - pkey.sign_pss(sha, message, salt_length: :max, mgf1_hash: sha) + signature = if options['MessageType'] == 'RAW' + pkey.sign_raw(sha, message, signopts) else - pkey.sign(sha, message) + pkey.sign(sha, message, signopts) end response.body = { From 13ab68eaee31739f23939567080b4d807bd3ab54 Mon Sep 17 00:00:00 2001 From: geemus Date: Thu, 12 Dec 2024 17:15:33 +0000 Subject: [PATCH 17/23] starting to flesh out mock tests around signing/verification --- lib/fog/aws/requests/kms/sign.rb | 2 +- tests/requests/kms/key_tests.rb | 38 +++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/lib/fog/aws/requests/kms/sign.rb b/lib/fog/aws/requests/kms/sign.rb index 1c249c04e4..2d32995c23 100644 --- a/lib/fog/aws/requests/kms/sign.rb +++ b/lib/fog/aws/requests/kms/sign.rb @@ -37,7 +37,7 @@ def sign(identifier, message, algorithm, options = {}) signopts = {} signopts[:rsa_padding_mode] = 'pss' if algorithm.start_with?('RSASSA_PSS') - signature = if options['MessageType'] == 'RAW' + signature = if options['MessageType'] == 'DIGEST' pkey.sign_raw(sha, message, signopts) else pkey.sign(sha, message, signopts) diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index 5fd71e4b38..15daf5bfc2 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -1,16 +1,17 @@ Shindo.tests('AWS::KMS | key requests', %w[aws kms]) do key_id = nil + public_key = nil + pkey = nil + data = 'sign me' - tests('success') do - tests('#create_key').data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) do - result = Fog::AWS[:kms].create_key( - 'KeySpec' => 'RSA_2048', - 'KeyUsage' => 'SIGN_VERIFY' - ).body - key_id = result['KeyMetadata']['KeyId'] + tests('#create_key').data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) do + result = Fog::AWS[:kms].create_key( + 'KeySpec' => 'RSA_2048', + 'KeyUsage' => 'SIGN_VERIFY' + ).body + key_id = result['KeyMetadata']['KeyId'] - result - end + result end tests('#describe_key').data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) do @@ -21,6 +22,8 @@ tests('#get_public_key').data_matches_schema(AWS::KMS::Formats::GET_PUBLIC_KEY) do result = Fog::AWS[:kms].get_public_key(key_id).body + public_key = Base64.decode64(result['PublicKey']) + pkey = OpenSSL::PKey::RSA.new(public_key) returns(key_id) { result['KeyId'] } result end @@ -30,7 +33,22 @@ end tests('#sign').data_matches_schema(AWS::KMS::Formats::SIGN) do - Fog::AWS[:kms].sign(key_id, 'sign me', 'RSASSA_PSS_SHA_256').body + Fog::AWS[:kms].sign(key_id, data, 'RSASSA_PSS_SHA_256', 'MessageType' => 'RAW').body + end + + tests('#sign RAW') do + sign_response = Fog::AWS[:kms].sign(key_id, data, 'RSASSA_PSS_SHA_256', 'MessageType' => 'RAW').body + signature = Base64.decode64(sign_response['Signature']) + + pkey.verify('SHA256', signature, data, { rsa_padding_mode: 'pss' }) + end + + tests('#sign DIGEST') do + hash = OpenSSL::Digest.digest('SHA256', data) + sign_response = Fog::AWS[:kms].sign(key_id, hash, 'RSASSA_PSS_SHA_256', 'MessageType' => 'DIGEST').body + signature = Base64.decode64(sign_response['Signature']) + + pkey.verify_raw('SHA256', signature, hash, { rsa_padding_mode: 'pss' }) end tests('#schedule_key_deletion').data_matches_schema(AWS::KMS::Formats::SCHEDULE_KEY_DELETION) do From 61d340a6435ba6ce0f41e24067a335dffb1dca4c Mon Sep 17 00:00:00 2001 From: geemus Date: Thu, 12 Dec 2024 17:46:43 +0000 Subject: [PATCH 18/23] further fleshing out/refining tests --- tests/requests/kms/key_tests.rb | 39 +++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index 15daf5bfc2..926207fa96 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -14,36 +14,51 @@ result end - tests('#describe_key').data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) do + tests('#describe_key') do result = Fog::AWS[:kms].describe_key(key_id).body - returns(key_id) { result['KeyMetadata']['KeyId'] } - result + + tests('format').data_matches_schema(AWS::KMS::Formats::DESCRIBE_KEY) { result } + + tests('result_contains correct key_id').returns(key_id) { result['KeyMetadata']['KeyId'] } end - tests('#get_public_key').data_matches_schema(AWS::KMS::Formats::GET_PUBLIC_KEY) do + tests('#get_public_key') do result = Fog::AWS[:kms].get_public_key(key_id).body public_key = Base64.decode64(result['PublicKey']) pkey = OpenSSL::PKey::RSA.new(public_key) - returns(key_id) { result['KeyId'] } - result + + tests('format').data_matches_schema(AWS::KMS::Formats::GET_PUBLIC_KEY) { result } + + tests('result contains correct key_id').returns(key_id) { result['KeyId'] } end - tests('#list_keys').data_matches_schema(AWS::KMS::Formats::LIST_KEYS) do - Fog::AWS[:kms].list_keys.body + tests('#list_keys') do + result = Fog::AWS[:kms].list_keys.body + + tests('format').data_matches_schema(AWS::KMS::Formats::LIST_KEYS) { result } + + tests('result contains correct key_id').returns(true) { result['Keys'].map { |k| k['KeyId'] }.include?(key_id) } end - tests('#sign').data_matches_schema(AWS::KMS::Formats::SIGN) do - Fog::AWS[:kms].sign(key_id, data, 'RSASSA_PSS_SHA_256', 'MessageType' => 'RAW').body + tests('#sign') do + sign_response = Fog::AWS[:kms].sign(key_id, data, 'RSASSA_PSS_SHA_256', 'MessageType' => 'RAW').body + + tests('format').data_matches_schema(AWS::KMS::Formats::SIGN) { sign_response } + + tests('#verify').returns(true) do + signature = Base64.decode64(sign_response['Signature']) + pkey.verify('SHA256', signature, data, { rsa_padding_mode: 'pss' }) + end end - tests('#sign RAW') do + tests('#sign RAW').returns(true) do sign_response = Fog::AWS[:kms].sign(key_id, data, 'RSASSA_PSS_SHA_256', 'MessageType' => 'RAW').body signature = Base64.decode64(sign_response['Signature']) pkey.verify('SHA256', signature, data, { rsa_padding_mode: 'pss' }) end - tests('#sign DIGEST') do + tests('#sign DIGEST').returns(true) do hash = OpenSSL::Digest.digest('SHA256', data) sign_response = Fog::AWS[:kms].sign(key_id, hash, 'RSASSA_PSS_SHA_256', 'MessageType' => 'DIGEST').body signature = Base64.decode64(sign_response['Signature']) From fe614bc6abc8853fef1eecd910d16988682a4261 Mon Sep 17 00:00:00 2001 From: geemus Date: Thu, 12 Dec 2024 22:04:00 +0000 Subject: [PATCH 19/23] add mock table tests for signing --- tests/requests/kms/key_tests.rb | 65 ++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index 926207fa96..22ae482a66 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -1,6 +1,8 @@ +KEY_SPECS = %w[RSA_2048 RSA_3072 RSA_4096 ECC_NIST_P256 ECC_NIST_P384 ECC_NIST_P521 ECC_SECG_P256K1].freeze +SIGNING_ALGORITHMS = %w[RSASSA_PSS_SHA_256 RSASSA_PSS_SHA_384 RSASSA_PSS_SHA_512 RSASSA_PKCS1_V1_5_SHA_256 RSASSA_PKCS1_V1_5_SHA_384 RSASSA_PKCS1_V1_5_SHA_512 ECDSA_SHA_256 ECDSA_SHA_384 ECDSA_SHA_512] + Shindo.tests('AWS::KMS | key requests', %w[aws kms]) do key_id = nil - public_key = nil pkey = nil data = 'sign me' @@ -41,32 +43,59 @@ end tests('#sign') do - sign_response = Fog::AWS[:kms].sign(key_id, data, 'RSASSA_PSS_SHA_256', 'MessageType' => 'RAW').body + sign_response = Fog::AWS[:kms].sign(key_id, data, 'RSASSA_PKCS1_V1_5_SHA_256', 'MessageType' => 'RAW').body tests('format').data_matches_schema(AWS::KMS::Formats::SIGN) { sign_response } tests('#verify').returns(true) do signature = Base64.decode64(sign_response['Signature']) - pkey.verify('SHA256', signature, data, { rsa_padding_mode: 'pss' }) + pkey.verify('SHA256', signature, data) end end - tests('#sign RAW').returns(true) do - sign_response = Fog::AWS[:kms].sign(key_id, data, 'RSASSA_PSS_SHA_256', 'MessageType' => 'RAW').body - signature = Base64.decode64(sign_response['Signature']) - - pkey.verify('SHA256', signature, data, { rsa_padding_mode: 'pss' }) - end - - tests('#sign DIGEST').returns(true) do - hash = OpenSSL::Digest.digest('SHA256', data) - sign_response = Fog::AWS[:kms].sign(key_id, hash, 'RSASSA_PSS_SHA_256', 'MessageType' => 'DIGEST').body - signature = Base64.decode64(sign_response['Signature']) - - pkey.verify_raw('SHA256', signature, hash, { rsa_padding_mode: 'pss' }) - end - tests('#schedule_key_deletion').data_matches_schema(AWS::KMS::Formats::SCHEDULE_KEY_DELETION) do Fog::AWS[:kms].schedule_key_deletion(key_id, 7).body end + + tests('mock sign') do + pending unless Fog.mock? + + KEY_SPECS.each do |key_spec| + SIGNING_ALGORITHMS.select { |sa| sa.start_with?(key_spec[0...2]) }.each do |signing_algorithm| + key_id = Fog::AWS[:kms].create_key( + 'KeySpec' => key_spec, + 'KeyUsage' => 'SIGN_VERIFY' + ).body['KeyMetadata']['KeyId'] + + result = Fog::AWS[:kms].get_public_key(key_id).body + public_key = Base64.decode64(result['PublicKey']) + pkey = if key_spec.start_with?('RSA') + OpenSSL::PKey::RSA.new(public_key) + elsif key_spec.start_with?('EC') + OpenSSL::PKey::EC.new(public_key) + end + sha = "SHA#{signing_algorithm.split('_SHA_').last}" + sign_opts = if signing_algorithm.include?('_PSS_') + { rsa_padding_mode: 'pss' } + else + {} + end + + tests("#sign #{key_spec} #{signing_algorithm} DIGEST").returns(true) do + hash = OpenSSL::Digest.digest(sha, data) + sign_response = Fog::AWS[:kms].sign(key_id, hash, signing_algorithm, 'MessageType' => 'DIGEST').body + signature = Base64.decode64(sign_response['Signature']) + + pkey.verify_raw(sha, signature, hash, sign_opts) + end + + tests("#sign #{key_spec} #{signing_algorithm} RAW").returns(true) do + sign_response = Fog::AWS[:kms].sign(key_id, data, signing_algorithm, 'MessageType' => 'RAW').body + signature = Base64.decode64(sign_response['Signature']) + + pkey.verify(sha, signature, data, sign_opts) + end + end + end + end end From 828d761b21b8caece264c8866b723a230d428df9 Mon Sep 17 00:00:00 2001 From: geemus Date: Fri, 13 Dec 2024 21:53:02 +0000 Subject: [PATCH 20/23] get_public_key KeyId is actually ARN, also delete after sign table test, just in case --- lib/fog/aws/requests/kms/get_public_key.rb | 2 +- tests/requests/kms/key_tests.rb | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/fog/aws/requests/kms/get_public_key.rb b/lib/fog/aws/requests/kms/get_public_key.rb index db4e66df6b..2259206344 100644 --- a/lib/fog/aws/requests/kms/get_public_key.rb +++ b/lib/fog/aws/requests/kms/get_public_key.rb @@ -21,7 +21,7 @@ def get_public_key(identifier, _grant_tokens = []) pkey = self.data[:pkeys][identifier] response.body = { - 'KeyId' => key['KeyId'], + 'KeyId' => key['Arn'], 'KeyUsage' => key['KeyUsage'], 'KeySpec' => key['KeySpec'], 'PublicKey' => Base64.strict_encode64(pkey.public_to_der), diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index 22ae482a66..ed084cc53e 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -3,6 +3,7 @@ Shindo.tests('AWS::KMS | key requests', %w[aws kms]) do key_id = nil + key_arn = nil pkey = nil data = 'sign me' @@ -12,6 +13,7 @@ 'KeyUsage' => 'SIGN_VERIFY' ).body key_id = result['KeyMetadata']['KeyId'] + key_arn = result['KeyMetadata']['Arn'] result end @@ -31,7 +33,7 @@ tests('format').data_matches_schema(AWS::KMS::Formats::GET_PUBLIC_KEY) { result } - tests('result contains correct key_id').returns(key_id) { result['KeyId'] } + tests('result contains correct key_id (arn)').returns(key_arn) { result['KeyId'] } end tests('#list_keys') do @@ -95,6 +97,8 @@ pkey.verify(sha, signature, data, sign_opts) end + + Fog::AWS[:kms].schedule_key_deletion(key_id, 7) end end end From 1dfc9e61a3cec5d8584e1e2022b2238bfddba4f6 Mon Sep 17 00:00:00 2001 From: geemus Date: Fri, 13 Dec 2024 21:56:22 +0000 Subject: [PATCH 21/23] add missing keyspec to describe_key parser --- lib/fog/aws/parsers/kms/describe_key.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fog/aws/parsers/kms/describe_key.rb b/lib/fog/aws/parsers/kms/describe_key.rb index cf8dbc8491..808a4e765a 100644 --- a/lib/fog/aws/parsers/kms/describe_key.rb +++ b/lib/fog/aws/parsers/kms/describe_key.rb @@ -17,7 +17,7 @@ def start_element(name, attrs = []) def end_element(name) case name - when 'Arn', 'AWSAccountId', 'Description', 'KeyId', 'KeyState', 'KeyUsage' + when 'Arn', 'AWSAccountId', 'Description', 'KeyId', 'KeySpec', 'KeyState', 'KeyUsage' @key[name] = value when 'CreationDate', 'DeletionDate' @key[name] = Time.parse(value) From f21b07ab5a8080d9f0209967e5f374393525225d Mon Sep 17 00:00:00 2001 From: geemus Date: Mon, 16 Dec 2024 16:13:21 +0000 Subject: [PATCH 22/23] add overlooked base64 encode to sign request calls --- lib/fog/aws/requests/kms/sign.rb | 10 ++++++++-- tests/requests/kms/key_tests.rb | 21 ++++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/fog/aws/requests/kms/sign.rb b/lib/fog/aws/requests/kms/sign.rb index 2d32995c23..7d7826d231 100644 --- a/lib/fog/aws/requests/kms/sign.rb +++ b/lib/fog/aws/requests/kms/sign.rb @@ -7,11 +7,15 @@ class Real # Sign # # ==== Parameters + # * identifier<~String>: id, arn, alias name, or alias arn for key to sign with + # * message<~String>: base64 encoded message to sign # # === Returns + # * response<~Excon::Response>: # # ==== See Also # https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html + # def sign(identifier, message, algorithm, options = {}) request({ 'Action' => 'Sign', @@ -32,15 +36,17 @@ def sign(identifier, message, algorithm, options = {}) raise(Excon::Errors.status_error({ expects: 200 }, response)) end + data = Base64.decode64(message) + # FIXME: SM2 support? sha = "SHA#{algorithm.split('_SHA_').last}" signopts = {} signopts[:rsa_padding_mode] = 'pss' if algorithm.start_with?('RSASSA_PSS') signature = if options['MessageType'] == 'DIGEST' - pkey.sign_raw(sha, message, signopts) + pkey.sign_raw(sha, data, signopts) else - pkey.sign(sha, message, signopts) + pkey.sign(sha, data, signopts) end response.body = { diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index ed084cc53e..258b32fa56 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -45,7 +45,12 @@ end tests('#sign') do - sign_response = Fog::AWS[:kms].sign(key_id, data, 'RSASSA_PKCS1_V1_5_SHA_256', 'MessageType' => 'RAW').body + sign_response = Fog::AWS[:kms].sign( + key_id, + Base64.encode64(data), + 'RSASSA_PKCS1_V1_5_SHA_256', + 'MessageType' => 'RAW' + ).body tests('format').data_matches_schema(AWS::KMS::Formats::SIGN) { sign_response } @@ -85,14 +90,24 @@ tests("#sign #{key_spec} #{signing_algorithm} DIGEST").returns(true) do hash = OpenSSL::Digest.digest(sha, data) - sign_response = Fog::AWS[:kms].sign(key_id, hash, signing_algorithm, 'MessageType' => 'DIGEST').body + sign_response = Fog::AWS[:kms].sign( + key_id, + Base64.encode64(hash), + signing_algorithm, + 'MessageType' => 'DIGEST' + ).body signature = Base64.decode64(sign_response['Signature']) pkey.verify_raw(sha, signature, hash, sign_opts) end tests("#sign #{key_spec} #{signing_algorithm} RAW").returns(true) do - sign_response = Fog::AWS[:kms].sign(key_id, data, signing_algorithm, 'MessageType' => 'RAW').body + sign_response = Fog::AWS[:kms].sign( + key_id, + Base64.encode64(data), + signing_algorithm, + 'MessageType' => 'RAW' + ).body signature = Base64.decode64(sign_response['Signature']) pkey.verify(sha, signature, data, sign_opts) From 3040bcde05011dfac86f9d1af406fc70d7f1308d Mon Sep 17 00:00:00 2001 From: geemus Date: Mon, 16 Dec 2024 17:04:17 +0000 Subject: [PATCH 23/23] add a digest test to live+mock key tests as well --- tests/requests/kms/key_tests.rb | 39 ++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/tests/requests/kms/key_tests.rb b/tests/requests/kms/key_tests.rb index 258b32fa56..a89b7f6d6d 100644 --- a/tests/requests/kms/key_tests.rb +++ b/tests/requests/kms/key_tests.rb @@ -45,18 +45,37 @@ end tests('#sign') do - sign_response = Fog::AWS[:kms].sign( - key_id, - Base64.encode64(data), - 'RSASSA_PKCS1_V1_5_SHA_256', - 'MessageType' => 'RAW' - ).body + tests('DIGEST') do + hash = OpenSSL::Digest.digest('SHA256', data) + sign_response = Fog::AWS[:kms].sign( + key_id, + Base64.encode64(hash), + 'RSASSA_PKCS1_V1_5_SHA_256', + 'MessageType' => 'DIGEST' + ).body + + tests('format').data_matches_schema(AWS::KMS::Formats::SIGN) { sign_response } + + tests('#verify').returns(true) do + signature = Base64.decode64(sign_response['Signature']) + pkey.verify_raw('SHA256', signature, hash) + end + end - tests('format').data_matches_schema(AWS::KMS::Formats::SIGN) { sign_response } + tests('RAW') do + sign_response = Fog::AWS[:kms].sign( + key_id, + Base64.encode64(data), + 'RSASSA_PKCS1_V1_5_SHA_256', + 'MessageType' => 'RAW' + ).body - tests('#verify').returns(true) do - signature = Base64.decode64(sign_response['Signature']) - pkey.verify('SHA256', signature, data) + tests('format').data_matches_schema(AWS::KMS::Formats::SIGN) { sign_response } + + tests('#verify').returns(true) do + signature = Base64.decode64(sign_response['Signature']) + pkey.verify('SHA256', signature, data) + end end end