class Mongo::Crypt::Handle
A handle to the libmongocrypt library that wraps a mongocrypt_t object, allowing clients to set options on that object or perform operations such as encryption and decryption
@api private
Public Class Methods
Creates a new Handle
object and initializes it with options
@param [ Hash ] kms_providers A hash of KMS settings. The only supported
key is currently :local. Local KMS options must be passed in the format { local: { key: <master key> } } where the master key is a 96-byte, base64 encoded string.
@param [ Hash ] options A hash of options
@option options [ Hash | nil ] :schema_map A hash representing the JSON schema
of the collection that stores auto encrypted documents.
@option options [ Logger
] :logger A Logger
object to which libmongocrypt logs
will be sent
# File lib/mongo/crypt/handle.rb, line 39 def initialize(kms_providers, options={}) # FFI::AutoPointer uses a custom release strategy to automatically free # the pointer once this object goes out of scope @mongocrypt = FFI::AutoPointer.new( Binding.mongocrypt_new, Binding.method(:mongocrypt_destroy) ) @schema_map = options[:schema_map] set_schema_map if @schema_map @logger = options[:logger] set_logger_callback if @logger set_crypto_hooks set_kms_providers(kms_providers) initialize_mongocrypt end
Public Instance Methods
Return the reference to the underlying @mongocrypt object
@return [ FFI::Pointer ]
# File lib/mongo/crypt/handle.rb, line 62 def ref @mongocrypt end
Private Instance Methods
Perform AES encryption or decryption and write the output to the provided mongocrypt_binary_t object.
# File lib/mongo/crypt/handle.rb, line 135 def do_aes(key_binary_p, iv_binary_p, input_binary_p, output_binary_p, response_length_p, status_p, decrypt: false) key = Binary.from_pointer(key_binary_p).to_s iv = Binary.from_pointer(iv_binary_p).to_s input = Binary.from_pointer(input_binary_p).to_s write_binary_string_and_set_status(output_binary_p, status_p) do output = Hooks.aes(key, iv, input, decrypt: decrypt) response_length_p.write_int(output.bytesize) output end end
Perform HMAC SHA encryption and write the output to the provided mongocrypt_binary_t object.
# File lib/mongo/crypt/handle.rb, line 151 def do_hmac_sha(digest_name, key_binary_p, input_binary_p, output_binary_p, status_p) key = Binary.from_pointer(key_binary_p).to_s input = Binary.from_pointer(input_binary_p).to_s write_binary_string_and_set_status(output_binary_p, status_p) do Hooks.hmac_sha(digest_name, key, input) end end
Yields to the provided block and rescues exceptions raised by the block. If an exception was raised, sets the specified status to the exception message and returns false. If no exceptions were raised, does not modify the status and returns true.
This method is meant to be used with libmongocrypt callbacks and follows the API defined by libmongocrypt.
@param [ FFI::Pointer ] status_p A pointer to libmongocrypt status object
@return [ true | false ] Whether block executed without raising
exceptions.
# File lib/mongo/crypt/handle.rb, line 100 def handle_error(status_p) begin yield true rescue => e status = Status.from_pointer(status_p) status.update(:error_client, 1, "#{e.class}: #{e}") false end end
Initialize the underlying mongocrypt_t object and raise an error if the operation fails
# File lib/mongo/crypt/handle.rb, line 309 def initialize_mongocrypt Binding.init(self) # There is currently no test for the error(?) code path end
We are buildling libmongocrypt without crypto functions to remove the external dependency on OpenSSL. This method binds native Ruby crypto methods to the underlying mongocrypt_t object so that libmongocrypt can still perform cryptography.
Every crypto binding ignores its first argument, which is an option mongocrypt_ctx_t object and is not required to use crypto hooks.
# File lib/mongo/crypt/handle.rb, line 168 def set_crypto_hooks @aes_encrypt = Proc.new do |_, key_binary_p, iv_binary_p, input_binary_p, output_binary_p, response_length_p, status_p| do_aes( key_binary_p, iv_binary_p, input_binary_p, output_binary_p, response_length_p, status_p ) end @aes_decrypt = Proc.new do |_, key_binary_p, iv_binary_p, input_binary_p, output_binary_p, response_length_p, status_p| do_aes( key_binary_p, iv_binary_p, input_binary_p, output_binary_p, response_length_p, status_p, decrypt: true ) end @random = Proc.new do |_, output_binary_p, num_bytes, status_p| write_binary_string_and_set_status(output_binary_p, status_p) do Hooks.random(num_bytes) end end @hmac_sha_512 = Proc.new do |_, key_binary_p, input_binary_p, output_binary_p, status_p| do_hmac_sha('SHA512', key_binary_p, input_binary_p, output_binary_p, status_p) end @hmac_sha_256 = Proc.new do |_, key_binary_p, input_binary_p, output_binary_p, status_p| do_hmac_sha('SHA256', key_binary_p, input_binary_p, output_binary_p, status_p) end @hmac_hash = Proc.new do |_, input_binary_p, output_binary_p, status_p| input = Binary.from_pointer(input_binary_p).to_s write_binary_string_and_set_status(output_binary_p, status_p) do Hooks.hash_sha256(input) end end Binding.setopt_crypto_hooks( self, @aes_encrypt, @aes_decrypt, @random, @hmac_sha_512, @hmac_sha_256, @hmac_hash, ) end
Validate the kms_providers option and use it to set the KMS provider information on the underlying mongocrypt_t object
# File lib/mongo/crypt/handle.rb, line 231 def set_kms_providers(kms_providers) unless kms_providers raise ArgumentError.new("The kms_providers option must not be nil") end unless kms_providers.key?(:local) || kms_providers.key?(:aws) raise ArgumentError.new( 'The kms_providers option must have one of the following keys: ' + ':aws, :local' ) end set_kms_providers_local(kms_providers) if kms_providers.key?(:local) set_kms_providers_aws(kms_providers) if kms_providers.key?(:aws) end
Validate and set the aws KMS provider information on the underlying mongocrypt_t object and raise an exception if the operation fails
# File lib/mongo/crypt/handle.rb, line 264 def set_kms_providers_aws(kms_providers) unless kms_providers[:aws] raise ArgumentError.new('The :aws KMS provider must not be nil') end access_key_id = kms_providers[:aws][:access_key_id] secret_access_key = kms_providers[:aws][:secret_access_key] unless kms_providers[:aws].key?(:access_key_id) && kms_providers[:aws].key?(:secret_access_key) raise ArgumentError.new( "The specified aws kms_providers option is invalid: #{kms_providers[:aws]}. " + "kms_providers with :aws key must be in the format: " + "{ aws: { access_key_id: 'YOUR-ACCESS-KEY-ID', secret_access_key: 'SECRET-ACCESS-KEY' } }" ) end %i(access_key_id secret_access_key).each do |key| value = kms_providers[:aws][key] if value.nil? raise ArgumentError.new( "The aws #{key} option must be a String with at least one character; " \ "currently have nil" ) end unless value.is_a?(String) raise ArgumentError.new( "The aws #{key} option must be a String with at least one character; " \ "currently have #{value}" ) end if value.empty? raise ArgumentError.new( "The aws #{key} option must be a String with at least one character; " \ "it is currently an empty string" ) end end Binding.setopt_kms_provider_aws(self, access_key_id, secret_access_key) end
Validate and set the local KMS provider information on the underlying mongocrypt_t object and raise an exception if the operation fails
# File lib/mongo/crypt/handle.rb, line 249 def set_kms_providers_local(kms_providers) unless kms_providers[:local][:key] && kms_providers[:local][:key].is_a?(String) raise ArgumentError.new( "The specified local kms_providers option is invalid: " + "#{kms_providers[:local]}. kms_providers with :local key must be " + "in the format: { local: { key: 'MASTER-KEY' } }" ) end master_key = kms_providers[:local][:key] Binding.setopt_kms_provider_local(self, master_key) end
Send the logs from libmongocrypt to the Mongo::Logger
# File lib/mongo/crypt/handle.rb, line 80 def set_logger_callback @log_callback = Proc.new do |level, msg| @logger.send(level, msg) end Binding.setopt_log_handler(@mongocrypt, @log_callback) end
Set the schema map option on the underlying mongocrypt_t object
# File lib/mongo/crypt/handle.rb, line 69 def set_schema_map unless @schema_map.is_a?(Hash) raise ArgumentError.new( "#{@schema_map} is an invalid schema_map; schema_map must be a Hash or nil" ) end Binding.setopt_schema_map(self, @schema_map) end
Yields to the provided block and writes the return value of block to the specified mongocrypt_binary_t object. If an exception is raised during execution of the block, writes the exception message to the specified status object and returns false. If no exception is raised, does not modify status and returns true. message to the mongocrypt_status_t object.
@param [ FFI::Pointer ] output_binary_p A pointer to libmongocrypt
Binary object to receive the result of block's execution
@param [ FFI::Pointer ] status_p A pointer to libmongocrypt status object
@return [ true | false ] Whether block executed without raising
exceptions.
# File lib/mongo/crypt/handle.rb, line 125 def write_binary_string_and_set_status(output_binary_p, status_p) handle_error(status_p) do output = yield Binary.from_pointer(output_binary_p).write(output) end end