Skip to content

Commit 5d34d4a

Browse files
RUBY-2405 Add GCP KMS support (#2374)
1 parent b69d85a commit 5d34d4a

36 files changed

+1083
-18
lines changed

.evergreen/config-atlas.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,20 @@ functions:
166166
MONGO_RUBY_DRIVER_AWS_SECRET="${fle_aws_secret}"
167167
MONGO_RUBY_DRIVER_AWS_REGION="${fle_aws_region}"
168168
MONGO_RUBY_DRIVER_AWS_ARN="${fle_aws_arn}"
169+
169170
MONGO_RUBY_DRIVER_AZURE_TENANT_ID="${fle_azure_tenant_id}"
170171
MONGO_RUBY_DRIVER_AZURE_CLIENT_ID="${fle_azure_client_id}"
171172
MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET="${fle_azure_client_secret}"
172173
MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT="${fle_azure_identity_platform_endpoint}"
173174
MONGO_RUBY_DRIVER_AZURE_KEY_VAULT_ENDPOINT="${fle_azure_key_vault_endpoint}"
174175
MONGO_RUBY_DRIVER_AZURE_KEY_NAME="${fle_azure_key_name}"
176+
175177
MONGO_RUBY_DRIVER_GCP_EMAIL="${fle_gcp_email}"
176178
MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY="${fle_gcp_private_key}"
179+
MONGO_RUBY_DRIVER_GCP_PROJECT_ID="${fle_gcp_project_id}"
180+
MONGO_RUBY_DRIVER_GCP_LOCATION="${fle_gcp_location}"
181+
MONGO_RUBY_DRIVER_GCP_KEY_RING="${fle_gcp_key_ring}"
182+
MONGO_RUBY_DRIVER_GCP_KEY_NAME="${fle_gcp_key_name}"
177183
MONGO_RUBY_DRIVER_MONGOCRYPTD_PORT="${fle_mongocryptd_port}"
178184
EOT
179185

.evergreen/config.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,20 @@ functions:
183183
MONGO_RUBY_DRIVER_AWS_SECRET="${fle_aws_secret}"
184184
MONGO_RUBY_DRIVER_AWS_REGION="${fle_aws_region}"
185185
MONGO_RUBY_DRIVER_AWS_ARN="${fle_aws_arn}"
186+
186187
MONGO_RUBY_DRIVER_AZURE_TENANT_ID="${fle_azure_tenant_id}"
187188
MONGO_RUBY_DRIVER_AZURE_CLIENT_ID="${fle_azure_client_id}"
188189
MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET="${fle_azure_client_secret}"
189190
MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT="${fle_azure_identity_platform_endpoint}"
190191
MONGO_RUBY_DRIVER_AZURE_KEY_VAULT_ENDPOINT="${fle_azure_key_vault_endpoint}"
191192
MONGO_RUBY_DRIVER_AZURE_KEY_NAME="${fle_azure_key_name}"
193+
192194
MONGO_RUBY_DRIVER_GCP_EMAIL="${fle_gcp_email}"
193195
MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY="${fle_gcp_private_key}"
196+
MONGO_RUBY_DRIVER_GCP_PROJECT_ID="${fle_gcp_project_id}"
197+
MONGO_RUBY_DRIVER_GCP_LOCATION="${fle_gcp_location}"
198+
MONGO_RUBY_DRIVER_GCP_KEY_RING="${fle_gcp_key_ring}"
199+
MONGO_RUBY_DRIVER_GCP_KEY_NAME="${fle_gcp_key_name}"
194200
MONGO_RUBY_DRIVER_MONGOCRYPTD_PORT="${fle_mongocryptd_port}"
195201
EOT
196202

.evergreen/config/common.yml.erb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,14 +248,20 @@ functions:
248248
MONGO_RUBY_DRIVER_AWS_SECRET="${fle_aws_secret}"
249249
MONGO_RUBY_DRIVER_AWS_REGION="${fle_aws_region}"
250250
MONGO_RUBY_DRIVER_AWS_ARN="${fle_aws_arn}"
251+
251252
MONGO_RUBY_DRIVER_AZURE_TENANT_ID="${fle_azure_tenant_id}"
252253
MONGO_RUBY_DRIVER_AZURE_CLIENT_ID="${fle_azure_client_id}"
253254
MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET="${fle_azure_client_secret}"
254255
MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT="${fle_azure_identity_platform_endpoint}"
255256
MONGO_RUBY_DRIVER_AZURE_KEY_VAULT_ENDPOINT="${fle_azure_key_vault_endpoint}"
256257
MONGO_RUBY_DRIVER_AZURE_KEY_NAME="${fle_azure_key_name}"
258+
257259
MONGO_RUBY_DRIVER_GCP_EMAIL="${fle_gcp_email}"
258260
MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY="${fle_gcp_private_key}"
261+
MONGO_RUBY_DRIVER_GCP_PROJECT_ID="${fle_gcp_project_id}"
262+
MONGO_RUBY_DRIVER_GCP_LOCATION="${fle_gcp_location}"
263+
MONGO_RUBY_DRIVER_GCP_KEY_RING="${fle_gcp_key_ring}"
264+
MONGO_RUBY_DRIVER_GCP_KEY_NAME="${fle_gcp_key_name}"
259265
MONGO_RUBY_DRIVER_MONGOCRYPTD_PORT="${fle_mongocryptd_port}"
260266
EOT
261267

docs/reference/client-side-encryption.txt

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,8 @@ Both automatic encryption and explicit encryption require an encryption master k
305305
This master key is used to encrypt data keys, which are in turn used to encrypt
306306
user data. The master key can be generated in one of two ways: by creating a
307307
local key, or by creating a key in a key management service. Currently
308-
Ruby driver supports AWS Key Management Service (KMS) and Azure Key Vault.
308+
Ruby driver supports AWS Key Management Service (KMS), Azure Key Vault, and
309+
Google Cloud Key Management (GCP KMS).
309310

310311
Local Master Key
311312
~~~~~~~~~~~~~~~~
@@ -380,7 +381,7 @@ See the `Local Master Key`_ section for more information about generating a new
380381
local master key.
381382

382383
Create a Data Key Using a Remote Master Key
383-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
384+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
384385

385386
If you have created an AWS KMS master key, note the access key ID and the secret access
386387
key of the IAM user that has permissions to use the key. Additionally, note
@@ -389,9 +390,20 @@ use that information to generate a data key.
389390

390391
If you have created an Azure master key, note the tenant id, the client id, and
391392
the client secret of the application that has permissions to use the key.
392-
Additionally, note the key name, key version (id any), and key vault endpoint
393+
Additionally, note the key name, key version (if any), and key vault endpoint
393394
for your master key. You will use that information to generate a data key.
394395

396+
If you have created a GCP KMS master key, note the email and the private key,
397+
and the client secret of the application that has permissions to use the key.
398+
Additionally, note the project id, location, key ring, key name, and
399+
key version (if any) for your master key. You will use that information to
400+
generate a data key.
401+
402+
Please note that GCP private key can be in different formats. Ruby driver
403+
supports DER encoded RSA private key as base64 encoded string. For MRI Ruby
404+
the driver additionally support PEM encoded RSA private key.
405+
406+
395407
.. code-block:: ruby
396408

397409
# A Mongo::Client instance that will be used to connect to the key vault
@@ -412,6 +424,12 @@ for your master key. You will use that information to generate a data key.
412424
tenant_id: 'AZURE-TENANT-ID',
413425
client_id: 'AZURE-CLIENT-ID',
414426
client_secret: 'AZURE-CLIENT-SECRET'
427+
},
428+
gcp: {
429+
email: 'GCP-EMAIL',
430+
# :private_key value should be GCP private key as base64 encoded
431+
# DER RSA private key, or PEM RSA private key, if you are using MRI Ruby.
432+
private_key: 'GCP-PRIVATE-KEY',
415433
}
416434
}
417435
)
@@ -423,7 +441,6 @@ for your master key. You will use that information to generate a data key.
423441
region: 'REGION-OF-YOUR-MASTER-KEY',
424442
key: 'ARN-OF-YOUR-MASTER-KEY'
425443
}
426-
427444
}
428445
)
429446
# => <BSON::Binary... type=ciphertext...>
@@ -435,7 +452,19 @@ for your master key. You will use that information to generate a data key.
435452
key_vault_endpoint: 'AZURE-KEY-VAULT-ENDPOINT',
436453
key_name: 'AZURE-KEY-NAME'
437454
}
455+
}
456+
)
457+
# => <BSON::Binary... type=ciphertext...>
438458

459+
gcp_data_key_id = client_encryption.create_data_key(
460+
'gcp',
461+
{
462+
master_key: {
463+
project_id: 'GCP-PROJECT-ID',
464+
location: 'GCP-LOCATION',
465+
key_ring: 'GCP-KEY-RING',
466+
key_name: 'GCP-KEY-NAME',
467+
}
439468
}
440469
)
441470
# => <BSON::Binary... type=ciphertext...>

lib/mongo/crypt/binding.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,45 @@ def self.setopt_crypto_hooks(handle,
10821082
end
10831083
end
10841084

1085+
# @!method self.mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5(crypt, sign_rsaes_pkcs1_v1_5, ctx=nil)
1086+
# @api private
1087+
#
1088+
# Set a crypto hook for the RSASSA-PKCS1-v1_5 algorithm with a SHA-256 hash.
1089+
# @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
1090+
# @param [ Proc ] sign_rsaes_pkcs1_v1_5 A RSASSA-PKCS1-v1_5 signing method.
1091+
# @param [ FFI::Pointer | nil ] ctx An optional pointer to a context object
1092+
# that may have been set when hooks were enabled.
1093+
# @return [ Boolean ] Whether setting this option succeeded.
1094+
attach_function(
1095+
:mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5,
1096+
[
1097+
:pointer,
1098+
:mongocrypt_hmac_fn,
1099+
:pointer
1100+
],
1101+
:bool
1102+
)
1103+
1104+
# Set a crypto hook for the RSASSA-PKCS1-v1_5 algorithm with
1105+
# a SHA-256 hash oh the Handle.
1106+
#
1107+
# @param [ Mongo::Crypt::Handle ] handle
1108+
# @param [ Method ] rsaes_pkcs_signature_cb A RSASSA-PKCS1-v1_5 signing method.
1109+
#
1110+
# @raise [ Mongo::Error::CryptError ] If the callbacks aren't set successfully
1111+
def self.setopt_crypto_hook_sign_rsaes_pkcs1_v1_5(
1112+
handle,
1113+
rsaes_pkcs_signature_cb
1114+
)
1115+
check_status(handle) do
1116+
mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5(
1117+
handle.ref,
1118+
rsaes_pkcs_signature_cb,
1119+
nil
1120+
)
1121+
end
1122+
end
1123+
10851124
# Raise a Mongo::Error::CryptError based on the status of the underlying
10861125
# mongocrypt_t object.
10871126
#

lib/mongo/crypt/encryption_io.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ def mark_command(cmd)
124124
return response.first
125125
end
126126

127-
# Get information about the AWS encryption key and feed it to the the
127+
# Get information about the remote KMS encryption key and feed it to the the
128128
# KmsContext object
129129
#
130130
# @param [ Mongo::Crypt::KmsContext ] kms_context A KmsContext object
131-
# corresponding to one AWS KMS data key. Contains information about
131+
# corresponding to one remote KMS data key. Contains information about
132132
# the endpoint at which to establish a TLS connection and the message
133133
# to send on that connection.
134134
def feed_kms(kms_context)

lib/mongo/crypt/handle.rb

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,19 @@ def do_hmac_sha(digest_name, key_binary_p, input_binary_p,
159159
end
160160
end
161161

162-
# We are buildling libmongocrypt without crypto functions to remove the
162+
# Perform signing using RSASSA-PKCS1-v1_5 with SHA256 hash and write
163+
# the output to the provided mongocrypt_binary_t object.
164+
def do_rsaes_pkcs_signature(key_binary_p, input_binary_p,
165+
output_binary_p, status_p)
166+
key = Binary.from_pointer(key_binary_p).to_s
167+
input = Binary.from_pointer(input_binary_p).to_s
168+
169+
write_binary_string_and_set_status(output_binary_p, status_p) do
170+
Hooks.rsaes_pkcs_signature(key, input)
171+
end
172+
end
173+
174+
# We are building libmongocrypt without crypto functions to remove the
163175
# external dependency on OpenSSL. This method binds native Ruby crypto
164176
# methods to the underlying mongocrypt_t object so that libmongocrypt can
165177
# still perform cryptography.
@@ -225,6 +237,16 @@ def set_crypto_hooks
225237
@hmac_sha_256,
226238
@hmac_hash,
227239
)
240+
241+
@rsaes_pkcs_signature_cb = Proc.new do |_, key_binary_p, input_binary_p,
242+
output_binary_p, status_p|
243+
do_rsaes_pkcs_signature(key_binary_p, input_binary_p, output_binary_p, status_p)
244+
end
245+
246+
Binding.setopt_crypto_hook_sign_rsaes_pkcs1_v1_5(
247+
self,
248+
@rsaes_pkcs_signature_cb
249+
)
228250
end
229251

230252
# Initialize the underlying mongocrypt_t object and raise an error if the operation fails

lib/mongo/crypt/hooks.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,28 @@ def hash_sha256(input)
8888
Digest::SHA2.new(256).digest(input)
8989
end
9090
module_function :hash_sha256
91+
92+
# An RSASSA-PKCS1-v1_5 with SHA-256 signature function.
93+
#
94+
# @param [ String ] key The PKCS#8 private key in DER format, base64 encoded.
95+
# @param [ String ] input The data to be signed.
96+
#
97+
# @return [ String ] The signature.
98+
def rsaes_pkcs_signature(key, input)
99+
private_key = if BSON::Environment.jruby?
100+
# JRuby cannot read DER format, we need to convert key into PEM first.
101+
key_pem = [
102+
"-----BEGIN PRIVATE KEY-----",
103+
Base64.strict_encode64(Base64.decode64(key)).scan(/.{1,64}/),
104+
"-----END PRIVATE KEY-----",
105+
].join("\n")
106+
OpenSSL::PKey::RSA.new(key_pem)
107+
else
108+
OpenSSL::PKey.read(Base64.decode64(key))
109+
end
110+
private_key.sign(OpenSSL::Digest::SHA256.new, input)
111+
end
112+
module_function :rsaes_pkcs_signature
91113
end
92114
end
93115
end

lib/mongo/crypt/kms.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,5 @@ def validate_param(key, opts, format_hint, required: true)
7575
require "mongo/crypt/kms/master_key_document"
7676
require 'mongo/crypt/kms/aws'
7777
require 'mongo/crypt/kms/azure'
78+
require 'mongo/crypt/kms/gcp'
7879
require 'mongo/crypt/kms/local'

lib/mongo/crypt/kms/azure.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class MasterKeyDocument
105105
#
106106
# @raise [ ArgumentError ] If required options are missing or incorrectly.
107107
def initialize(opts)
108-
if opts.is_a?(Hash)
108+
unless opts.is_a?(Hash)
109109
raise ArgumentError.new(
110110
'Key document options must contain a key named :master_key with a Hash value'
111111
)

lib/mongo/crypt/kms/credentials.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,14 @@ def initialize(kms_providers)
4646
if kms_providers.key?(:azure)
4747
@azure = Azure::Credentials.new(kms_providers[:azure])
4848
end
49+
if kms_providers.key?(:gcp)
50+
@gcp = GCP::Credentials.new(kms_providers[:gcp])
51+
end
4952
if kms_providers.key?(:local)
5053
@local = Local::Credentials.new(kms_providers[:local])
5154
end
52-
if @aws.nil? && @azure.nil? && @local.nil?
53-
raise ArgumentError.new("KMS providers options must have one of the following keys: :aws, :azure, :local")
55+
if @aws.nil? && @azure.nil? && @gcp.nil? && @local.nil?
56+
raise ArgumentError.new("KMS providers options must have one of the following keys: :aws, :azure, :gcp, :local")
5457
end
5558
end
5659

@@ -61,6 +64,7 @@ def to_document
6164
BSON::Document.new({}).tap do |bson|
6265
bson[:aws] = @aws.to_document if @aws
6366
bson[:azure] = @azure.to_document if @azure
67+
bson[:gcp] = @gcp.to_document if @gcp
6468
bson[:local] = @local.to_document if @local
6569
end
6670
end

0 commit comments

Comments
 (0)