# Copyright (C) 2019 MongoDB, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. unless ENV['LIBMONGOCRYPT_PATH'] # It seems that MRI maintains autoload configuration for a module until # that module is defined, but JRuby removes autoload configuration as soon # as the referenced file is attempted to be loaded, even if the module # never ends up being defined. if BSON::Environment.jruby? module Mongo module Crypt autoload :Binding, 'mongo/crypt/binding' end end end raise LoadError, "Cannot load Mongo::Crypt::Binding because there is no path " + "to libmongocrypt specified in the LIBMONGOCRYPT_PATH environment variable." end require 'ffi' module Mongo module Crypt # @api private def reset_autoload remove_const(:Binding) autoload(:Binding, 'mongo/crypt/binding') end module_function :reset_autoload # A Ruby binding for the libmongocrypt C library # # @api private class Binding extend FFI::Library begin ffi_lib ENV['LIBMONGOCRYPT_PATH'] rescue LoadError => e Crypt.reset_autoload raise LoadError, "Cannot load Mongo::Crypt::Binding because the path to " + "libmongocrypt specified in the LIBMONGOCRYPT_PATH environment variable " + "is invalid: #{ENV['LIBMONGOCRYPT_PATH']}\n\n#{e.class}: #{e.message}" end # Returns the version string of the libmongocrypt library # # @param [ FFI::Pointer | nil ] len (out param) An optional pointer to a # uint8 that will reference the length of the returned string. # # @return [ String ] A version string for libmongocrypt attach_function :mongocrypt_version, [:pointer], :string # Create a new mongocrypt_binary_t object (a non-owning view of a byte # array) # # @return [ FFI::Pointer ] A pointer to the newly-created # mongocrypt_binary_t object attach_function :mongocrypt_binary_new, [], :pointer # Create a new mongocrypt_binary_t object that maintains a pointer to # the specified byte array. # # @param [ FFI::Pointer ] data A pointer to an array of bytes; the data # is not copied and must outlive the mongocrypt_binary_t object # @param [ Integer ] len The length of the array argument # # @return [ FFI::Pointer ] A pointer to the newly-created # mongocrypt_binary_t object attach_function( :mongocrypt_binary_new_from_data, [:pointer, :int], :pointer ) # Get the pointer to the underlying data for the mongocrypt_binary_t # # @param [ FFI::Pointer ] binary A pointer to a mongocrypt_binary_t object # # @return [ FFI::Pointer ] A pointer to the data array attach_function :mongocrypt_binary_data, [:pointer], :pointer # Get the length of the underlying data array # # @param [ FFI::Pointer ] binary A pointer to a mongocrypt_binary_t object # # @return [ Integer ] The length of the data array attach_function :mongocrypt_binary_len, [:pointer], :int # Destroy the mongocrypt_binary_t object # # @param [ FFI::Pointer ] A pointer to a mongocrypt_binary_t object # # @return [ nil ] Always nil attach_function :mongocrypt_binary_destroy, [:pointer], :void # Enum labeling different status types enum :status_type, [ :ok, 0, :error_client, 1, :error_kms, 2, ] # Create a new mongocrypt_status_t object # # @return [ FFI::Pointer ] A pointer to the new mongocrypt_status_ts attach_function :mongocrypt_status_new, [], :pointer # Set a message, type, and code on an existing status # # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # @param [ Symbol ] type The status type; possible values are defined # by the status_type enum # @param [ Integer ] code The status code # @param [ String ] message The status message # @param [ Integer ] len The length of the message argument (or -1 for a # null-terminated string) # # @return [ nil ] Always nil attach_function( :mongocrypt_status_set, [:pointer, :status_type, :int, :string, :int], :void ) # Indicates the status type # # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # # @return [ Symbol ] The status type (as defined by the status_type enum) attach_function :mongocrypt_status_type, [:pointer], :status_type # Return the status error code # # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # # @return [ Integer ] The status code attach_function :mongocrypt_status_code, [:pointer], :int # Returns the status message # # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # @param [ FFI::Pointer | nil ] len (out param) An optional pointer to a # uint32, where the length of the retun string will be written # # @return [ String ] The status message attach_function :mongocrypt_status_message, [:pointer, :pointer], :string # Returns whether the status is ok or an error # # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # # @return [ Boolean ] Whether the status is ok attach_function :mongocrypt_status_ok, [:pointer], :bool # Destroys the reference to the mongocrypt_status_t object # # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # # @return [ nil ] Always nil attach_function :mongocrypt_status_destroy, [:pointer], :void # Enum labeling the various log levels enum :log_level, [ :fatal, 0, :error, 1, :warn, 2, :info, 3, :debug, 4, ] # A callback to the mongocrypt log function # Set a custom log callback with the mongocrypt_setopt_log_handler method # # @param [ Symbol ] level The log level; possible values defined by the # log_level enum # @param [ String ] message The log message # @param [ Integer ] len The length of the message param, or -1 if the # string is null terminated # @param [ FFI::Pointer | nil ] ctx An optional pointer to a context # object when this callback was set # # @return [ nil ] Always nil. callback :mongocrypt_log_fn_t, [:log_level, :string, :int, :pointer], :void # Creates a new mongocrypt_t object # # @return [ FFI::Pointer ] A pointer to a new mongocrypt_t object attach_function :mongocrypt_new, [], :pointer # Set the handler on the mongocrypt_t object to be called every time # libmongocrypt logs a message # # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object # @param [ Method ] log_fn A logging callback method # @param [ FFI::Pointer | nil ] log_ctx An optional pointer to a context # to be passed into the log callback on every invocation. # # @return [ Boolean ] Whether setting the callback was successful attach_function( :mongocrypt_setopt_log_handler, [:pointer, :mongocrypt_log_fn_t, :pointer], :bool ) # Set the logger callback function on the Mongo::Crypt::Handle object # # @param [ Mongo::Crypt::Handle ] handle # @param [ Method ] log_callback # # @raise [ Mongo::Error::CryptError ] If the callback is not set successfully def self.setopt_log_handler(handle, log_callback) check_status(handle) do mongocrypt_setopt_log_handler(handle, log_callback, nil) end end # Configure mongocrypt_t object with AWS KMS provider options # # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object # @param [ String ] aws_access_key_id The AWS access key id # @param [ Integer ] aws_access_key_id_len The length of the AWS access # key string (or -1 for a null-terminated string) # @param [ String ] aws_secret_access_key The AWS secret access key # @param [ Integer ] aws_secret_access_key_len The length of the AWS # secret access key (or -1 for a null-terminated string) # # @return [ Boolean ] Returns whether the option was set successfully attach_function( :mongocrypt_setopt_kms_provider_aws, [:pointer, :string, :int, :string, :int], :bool ) # Configure the Handle object with AWS KMS provider options # # @param [ Mongo::Crypt::Handle ] handle # @param [ String ] aws_access_key The AWS access key # @param [ String ] aws_secret_access_key The AWS secret access key # # @raise [ Mongo::Error::CryptError ] If the option is not set successfully def self.setopt_kms_provider_aws(handle, aws_access_key, aws_secret_access_key ) check_status(handle) do mongocrypt_setopt_kms_provider_aws( handle.ref, aws_access_key, -1, aws_secret_access_key, -1 ) end end # Configure mongocrypt_t object to take local KSM provider options # # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object # @param [ FFI::Pointer ] key A pointer to a mongocrypt_binary_t object # that references the 96-byte local master key # # @return [ Boolean ] Returns whether the option was set successfully attach_function( :mongocrypt_setopt_kms_provider_local, [:pointer, :pointer], :bool ) # Set local KMS provider options on the Mongo::Crypt::Handle object # # @param [ Mongo::Crypt::Handle ] handle # @param [ String ] master_key The 96-byte local KMS master key # # @raise [ Mongo::Error::CryptError ] If the option is not set successfully def self.setopt_kms_provider_local(handle, master_key) Binary.wrap_string(master_key) do |master_key_p| check_status(handle) do mongocrypt_setopt_kms_provider_local(handle.ref, master_key_p) end end end # Sets a local schema map for encryption # # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object # @param [ FFI::Pointer ] schema_map A pointer to a mongocrypt_binary_t # object that references the schema map as a BSON binary string # # @return [ Boolean ] Returns whether the option was set successfully attach_function :mongocrypt_setopt_schema_map, [:pointer, :pointer], :bool # Set schema map on the Mongo::Crypt::Handle object # # @param [ Mongo::Crypt::Handle ] handle # @param [ BSON::Document ] schema_map_doc The schema map as a # BSON::Document object # # @raise [ Mongo::Error::CryptError ] If the schema map is not set successfully def self.setopt_schema_map(handle, schema_map_doc) validate_document(schema_map_doc) data = schema_map_doc.to_bson.to_s Binary.wrap_string(data) do |data_p| check_status(handle) do mongocrypt_setopt_schema_map(handle.ref, data_p) end end end # Initialize the mongocrypt_t object # # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object # # @return [ Boolean ] Returns whether the crypt was initialized successfully attach_function :mongocrypt_init, [:pointer], :bool # Initialize the Mongo::Crypt::Handle object # # @param [ Mongo::Crypt::Handle ] handle # # @raise [ Mongo::Error::CryptError ] If initialization fails def self.init(handle) check_status(handle) do mongocrypt_init(handle.ref) end end # Set the status information from the mongocrypt_t object on the # mongocrypt_status_t object # # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t object # # @return [ Boolean ] Whether the status was successfully set attach_function :mongocrypt_status, [:pointer, :pointer], :bool # Destroy the reference the mongocrypt_t object # # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object # # @return [ nil ] Always nil attach_function :mongocrypt_destroy, [:pointer], :void # Create a new mongocrypt_ctx_t object (a wrapper for the libmongocrypt # state machine) # # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object # # @return [ FFI::Pointer ] A new mongocrypt_ctx_t object attach_function :mongocrypt_ctx_new, [:pointer], :pointer # Set the status information from the mongocrypt_ctx_t object on the # mongocrypt_status_t object # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t object # # @return [ Boolean ] Whether the status was successfully set attach_function :mongocrypt_ctx_status, [:pointer, :pointer], :bool # Set the key id used for explicit encryption # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ FFI::Pointer ] key_id A pointer to a mongocrypt_binary_t object # that references the 16-byte key-id # # @note Do not initialize ctx before calling this method # @return [ Boolean ] Whether the option was successfully set attach_function :mongocrypt_ctx_setopt_key_id, [:pointer, :pointer], :bool # Sets the key id option on an explicit encryption context. # # @param [ Mongo::Crypt::Context ] context Explicit encryption context # @param [ String ] key_id The key id # # @raise [ Mongo::Error::CryptError ] If the operation failed def self.ctx_setopt_key_id(context, key_id) Binary.wrap_string(key_id) do |key_id_p| check_ctx_status(context) do mongocrypt_ctx_setopt_key_id(context.ctx_p, key_id_p) end end end # When creating a data key, set an alternate name on that key. When # performing explicit encryption, specifying which data key to use for # encryption based on its keyAltName field. # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ FFI::Pointer ] binary A pointer to a mongocrypt_binary_t # object that references a BSON document in the format # { "keyAltName": } # # @return [ Boolean ] Whether the alternative name was successfully set # # @note Do not initialize ctx before calling this method attach_function( :mongocrypt_ctx_setopt_key_alt_name, [:pointer, :pointer], :bool ) # Set multiple alternate key names on data key creation # # @param [ Mongo::Crypt::Context ] context A DataKeyContext # @param [ Array ] key_alt_names An array of alternate key names as strings # # @raise [ Mongo::Error::CryptError ] If any of the alternate names are # not valid UTF8 strings def self.ctx_setopt_key_alt_names(context, key_alt_names) key_alt_names.each do |key_alt_name| key_alt_name_bson = { :keyAltName => key_alt_name }.to_bson.to_s Binary.wrap_string(key_alt_name_bson) do |key_alt_name_p| check_ctx_status(context) do mongocrypt_ctx_setopt_key_alt_name(context.ctx_p, key_alt_name_p) end end end end # Set the algorithm used for explicit encryption # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ String ] algorithm The algorithm name. Valid values are: # - "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" # - "AEAD_AES_256_CBC_HMAC_SHA_512-Random" # @param [ Integer ] len The length of the algorithm string # # @note Do not initialize ctx before calling this method # @return [ Boolean ] Whether the option was successfully set attach_function( :mongocrypt_ctx_setopt_algorithm, [:pointer, :string, :int], :bool ) # Set the algorithm on the context # # @param [ Mongo::Crypt::Context ] context # @param [ String ] name The algorithm name. Valid values are: # - "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" # - "AEAD_AES_256_CBC_HMAC_SHA_512-Random" # # @raise [ Mongo::Error::CryptError ] If the operation failed def self.ctx_setopt_algorithm(context, name) check_ctx_status(context) do mongocrypt_ctx_setopt_algorithm(context.ctx_p, name, -1) end end # Configure the ctx to take a master key from AWS # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_object # @param [ String ] region The AWS region # @param [ Integer ] region_len The length of the region string (or -1 # for a null-terminated string) # @param [ String ] arn The Amazon Resource Name (ARN) of the mater key # @param [ Integer ] arn_len The length of the ARN (or -1 for a # null-terminated string) # # @return [ Boolean ] Returns whether the option was set successfully attach_function( :mongocrypt_ctx_setopt_masterkey_aws, [:pointer, :string, :int, :string, :int], :bool ) # Configure the Context object to take a master key from AWS # # @param [ Mongo::Crypt::Context ] context # @param [ String ] region The AWS region (e.g. "us-east-2") # @param [ String ] arn The master key Amazon Resource Name # # @raise [ Mongo::Error::CryptError ] If the operation failed def self.ctx_setopt_master_key_aws(context, region, arn) check_ctx_status(context) do mongocrypt_ctx_setopt_masterkey_aws( context.ctx_p, region, -1, arn, -1 ) end end # Set a custom endpoint at which to fetch the AWS master key # # @param [ FFI::Pointer ] ctx # @param [ String ] endpoint The custom endpoint # @param [ Integer ] endpoint_len The length of the endpoint string (or # -1 for a null-terminated string) # # @return [ Boolean ] Returns whether the option was set successfully attach_function( :mongocrypt_ctx_setopt_masterkey_aws_endpoint, [:pointer, :string, :int], :bool ) # Configure the Context object to take a masterk ey from AWS # # @param [ Mongo::Crypt::Context ] context # @param [ String ] endpoint The custom AWS master key endpoint # # @raise [ Mongo::Error::CryptError ] If the operation failed def self.ctx_setopt_master_key_aws_endpoint(context, endpoint) check_ctx_status(context) do mongocrypt_ctx_setopt_masterkey_aws_endpoint( context.ctx_p, endpoint, -1, ) end end # Set the ctx to take a local master key # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # # @note Do not initialize ctx before calling this method # @return [ Boolean ] Whether the option was successfully set attach_function( :mongocrypt_ctx_setopt_masterkey_local, [:pointer], :bool ) # Tell the Context object to read the master key from local KMS options # # @param [ Mongo::Crypt::Context ] context # # @raise [ Mongo::Error::CryptError ] If the operation failed def self.ctx_setopt_master_key_local(context) check_ctx_status(context) do mongocrypt_ctx_setopt_masterkey_local(context.ctx_p) end end # Initializes the ctx to create a data key # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # # @note Before calling this method, master key options must be set. # Set AWS master key by calling mongocrypt_ctx_setopt_masterkey_aws # and mongocrypt_ctx_setopt_masterkey_aws_endpoint. Set local master # key by calling mongocrypt_ctx_setopt_masterkey_local. # # @return [ Boolean ] Whether the initialization was successful attach_function :mongocrypt_ctx_datakey_init, [:pointer], :bool # Initialize the Context to create a data key # # @param [ Mongo::Crypt::Context ] context # # @raise [ Mongo::Error::CryptError ] If initialization fails def self.ctx_datakey_init(context) check_ctx_status(context) do mongocrypt_ctx_datakey_init(context.ctx_p) end end # Initializes the ctx for auto-encryption # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ String ] db The database name # @param [ Integer ] db_len The length of the database name argument (or # -1 for a null-terminated string) # @param [ FFI::Pointer ] cmd A pointer to a mongocrypt_binary_t object # that references the database command as a binary string # # @note This method expects the passed-in BSON to be in the format: # { "v": BSON value to decrypt } # # @return [ Boolean ] Whether the initialization was successful attach_function( :mongocrypt_ctx_encrypt_init, [:pointer, :string, :int, :pointer], :bool ) # Initialize the Context for auto-encryption # # @param [ Mongo::Crypt::Context ] context # @param [ String ] db_name The name of the database against which the # encrypted command is being performed # @param [ Hash ] command The command to be encrypted # # @raise [ Mongo::Error::CryptError ] If initialization fails def self.ctx_encrypt_init(context, db_name, command) validate_document(command) data = command.to_bson.to_s Binary.wrap_string(data) do |data_p| check_ctx_status(context) do mongocrypt_ctx_encrypt_init(context.ctx_p, db_name, -1, data_p) end end end # Initializes the ctx for explicit encryption # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ FFI::Pointer ] msg A pointer to a mongocrypt_binary_t object # that references the message to be encrypted as a binary string # # @note Before calling this method, set a key_id, key_alt_name (optional), # and encryption algorithm using the following methods: # mongocrypt_ctx_setopt_key_id, mongocrypt_ctx_setopt_key_alt_name, # and mongocrypt_ctx_setopt_algorithm # # @return [ Boolean ] Whether the initialization was successful attach_function( :mongocrypt_ctx_explicit_encrypt_init, [:pointer, :pointer], :bool ) # Initialize the Context for explicit encryption # # @param [ Mongo::Crypt::Context ] context # @param [ Hash ] A BSON document to encrypt # # @raise [ Mongo::Error::CryptError ] If initialization fails def self.ctx_explicit_encrypt_init(context, doc) validate_document(doc) data = doc.to_bson.to_s Binary.wrap_string(data) do |data_p| check_ctx_status(context) do mongocrypt_ctx_explicit_encrypt_init(context.ctx_p, data_p) end end end # Initializes the ctx for auto-decryption # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ FFI::Pointer ] doc A pointer to a mongocrypt_binary_t object # that references the document to be decrypted as a BSON binary string # # @return [ Boolean ] Whether the initialization was successful attach_function :mongocrypt_ctx_decrypt_init, [:pointer, :pointer], :bool # Initialize the Context for auto-decryption # # @param [ Mongo::Crypt::Context ] context # @param [ BSON::Document ] A BSON document to decrypt # # @raise [ Mongo::Error::CryptError ] If initialization fails def self.ctx_decrypt_init(context, command) validate_document(command) data = command.to_bson.to_s Binary.wrap_string(data) do |data_p| check_ctx_status(context) do mongocrypt_ctx_decrypt_init(context.ctx_p, data_p) end end end # Initializes the ctx for explicit decryption # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ FFI::Pointer ] msg A pointer to a mongocrypt_binary_t object # that references the message to be decrypted as a BSON binary string # # @return [ Boolean ] Whether the initialization was successful attach_function( :mongocrypt_ctx_explicit_decrypt_init, [:pointer, :pointer], :bool ) # Initialize the Context for explicit decryption # # @param [ Mongo::Crypt::Context ] context # @param [ Hash ] A BSON document to decrypt # # @raise [ Mongo::Error::CryptError ] If initialization fails def self.ctx_explicit_decrypt_init(context, doc) validate_document(doc) data = doc.to_bson.to_s Binary.wrap_string(data) do |data_p| check_ctx_status(context) do mongocrypt_ctx_explicit_decrypt_init(context.ctx_p, data_p) end end end # An enum labeling different libmognocrypt state machine states enum :mongocrypt_ctx_state, [ :error, 0, :need_mongo_collinfo, 1, :need_mongo_markings, 2, :need_mongo_keys, 3, :need_kms, 4, :ready, 5, :done, 6, ] # Get the current state of the ctx # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # # @return [ Symbol ] The current state, will be one of the values defined # by the mongocrypt_ctx_state enum attach_function :mongocrypt_ctx_state, [:pointer], :mongocrypt_ctx_state # Get a BSON operation for the driver to run against the MongoDB # collection, the key vault database, or mongocryptd. # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ FFI::Pointer ] op_bson (out param) A pointer to a # mongocrypt_binary_t object that will have a reference to the # BSON operation written to it by libmongocrypt # # @return [ Boolean ] A boolean indicating the success of the operation attach_function :mongocrypt_ctx_mongo_op, [:pointer, :pointer], :bool # Returns a BSON::Document representing an operation that the # driver must perform on behalf of libmongocrypt to get the # information it needs in order to continue with # encryption/decryption (for example, a filter for a key vault query). # # @param [ Mongo::Crypt::Context ] context # # @raise [ Mongo::Crypt ] If there is an error getting the operation # @return [ BSON::Document ] The operation that the driver must perform def self.ctx_mongo_op(context) binary = Binary.new check_ctx_status(context) do mongocrypt_ctx_mongo_op(context.ctx_p, binary.ref) end # TODO since the binary references a C pointer, and ByteBuffer is # written in C in MRI, we could omit a copy of the data by making # ByteBuffer reference the string that is owned by libmongocrypt. BSON::Document.from_bson(BSON::ByteBuffer.new(binary.to_s), mode: :bson) end # Feed a BSON reply to libmongocrypt # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ FFI::Pointer ] reply A mongocrypt_binary_t object that # references the BSON reply to feed to libmongocrypt # # @return [ Boolean ] A boolean indicating the success of the operation attach_function :mongocrypt_ctx_mongo_feed, [:pointer, :pointer], :bool # Feed a response from the driver back to libmongocrypt # # @param [ Mongo::Crypt::Context ] context # @param [ BSON::Document ] doc The document representing the response # # @raise [ Mongo::Error::CryptError ] If the response is not fed successfully def self.ctx_mongo_feed(context, doc) validate_document(doc) data = doc.to_bson.to_s Binary.wrap_string(data) do |data_p| check_ctx_status(context) do mongocrypt_ctx_mongo_feed(context.ctx_p, data_p) end end end # Indicate to libmongocrypt that the driver is done feeding replies # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # # @return [ Boolean ] A boolean indicating the success of the operation attach_function :mongocrypt_ctx_mongo_done, [:pointer], :bool # Return a pointer to a mongocrypt_kms_ctx_t object or NULL. # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # # @return [ FFI::Pointer ] A pointer to a mongocrypt_kms_ctx_t object attach_function :mongocrypt_ctx_next_kms_ctx, [:pointer], :pointer # Return a new KmsContext object needed by a Context object. # # @param [ Mongo::Crypt::Context ] context # # @return [ Mongo::Crypt::KmsContext | nil ] The KmsContext needed to # fetch an AWS master key or nil, if no KmsContext is needed def self.ctx_next_kms_ctx(context) kms_ctx_p = mongocrypt_ctx_next_kms_ctx(context.ctx_p) if kms_ctx_p.null? nil else KmsContext.new(kms_ctx_p) end end # Get the message needed to fetch the AWS KMS master key. # # @param [ FFI::Pointer ] kms Pointer to the mongocrypt_kms_ctx_t object # @param [ FFI::Pointer ] msg (outparam) Pointer to a mongocrypt_binary_t # object that will have the location of the message written to it by # libmongocrypt # # @return [ Boolean ] Whether the operation is successful attach_function :mongocrypt_kms_ctx_message, [:pointer, :pointer], :bool # Get the HTTP message needed to fetch the AWS KMS master key from a # KmsContext object. # # @param [ Mongo::Crypt::KmsContext ] kms_context # # @raise [ Mongo::Error::CryptError ] If the response is not fed successfully # # @return [ String ] The HTTP message def self.kms_ctx_message(kms_context) binary = Binary.new check_kms_ctx_status(kms_context) do mongocrypt_kms_ctx_message(kms_context.kms_ctx_p, binary.ref) end return binary.to_s end # Get the hostname with which to connect over TLS to get information about # the AWS master key. # # @param [ FFI::Pointer ] kms A pointer to a mongocrypt_kms_ctx_t object # @param [ FFI::Pointer ] endpoint (out param) A pointer to which the # endpoint string will be written by libmongocrypt # # @return [ Boolean ] Whether the operation was successful attach_function :mongocrypt_kms_ctx_endpoint, [:pointer, :pointer], :bool # Get the hostname with which to connect over TLS to get information # about the AWS master key. # # @param [ Mongo::Crypt::KmsContext ] kms_context # # @raise [ Mongo::Error::CryptError ] If the response is not fed successfully # # @return [ String | nil ] The hostname, or nil if none exists def self.kms_ctx_endpoint(kms_context) ptr = FFI::MemoryPointer.new(:pointer, 1) check_kms_ctx_status(kms_context) do mongocrypt_kms_ctx_endpoint(kms_context.kms_ctx_p, ptr) end str_ptr = ptr.read_pointer str_ptr.null? ? nil : str_ptr.read_string.force_encoding('UTF-8') end # Get the number of bytes needed by the KMS context. # # @param [ FFI::Pointer ] kms The mongocrypt_kms_ctx_t object # # @return [ Integer ] The number of bytes needed attach_function :mongocrypt_kms_ctx_bytes_needed, [:pointer], :int # Get the number of bytes needed by the KmsContext. # # @param [ Mongo::Crypt::KmsContext ] kms_context # # @return [ Integer ] The number of bytes needed def self.kms_ctx_bytes_needed(kms_context) mongocrypt_kms_ctx_bytes_needed(kms_context.kms_ctx_p) end # Feed replies from the KMS back to libmongocrypt. # # @param [ FFI::Pointer ] kms A pointer to the mongocrypt_kms_ctx_t object # @param [ FFI::Pointer ] bytes A pointer to a mongocrypt_binary_t # object that references the response from the KMS # # @return [ Boolean ] Whether the operation was successful attach_function :mongocrypt_kms_ctx_feed, [:pointer, :pointer], :bool # Feed replies from the KMS back to libmongocrypt. # # @param [ Mongo::Crypt::KmsContext ] kms_context # @oaram [ String ] data The data to feed to libmongocrypt # # @raise [ Mongo::Error::CryptError ] If the response is not fed successfully def self.kms_ctx_feed(kms_context, bytes) check_kms_ctx_status(kms_context) do Binary.wrap_string(bytes) do |bytes_p| mongocrypt_kms_ctx_feed(kms_context.kms_ctx_p, bytes_p) end end end # Write status information about the mongocrypt_kms_ctx_t object # to the mongocrypt_status_t object. # # @param [ FFI::Pointer ] kms A pointer to the mongocrypt_kms_ctx_t object # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t object # # @return [ Boolean ] Whether the operation was successful attach_function :mongocrypt_kms_ctx_status, [:pointer, :pointer], :bool # If the provided block returns false, raise a CryptError with the # status information from the provided KmsContext object. # # @param [ Mongo::Crypt::KmsContext ] kms_context # # @raise [ Mongo::Error::CryptError ] If the provided block returns false def self.check_kms_ctx_status(kms_context) unless yield status = Status.new mongocrypt_kms_ctx_status(kms_context.kms_ctx_p, status.ref) status.raise_crypt_error end end # Indicate to libmongocrypt that it will receive no more replies from # mongocrypt_kms_ctx_t objects. # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # # @return [ Boolean ] Whether the operation was successful attach_function :mongocrypt_ctx_kms_done, [:pointer], :bool # Indicate to libmongocrypt that it will receive no more KMS replies. # # @param [ Mongo::Crypt::Context ] context # # @raise [ Mongo::Error::CryptError ] If the operation is unsuccessful def self.ctx_kms_done(context) check_ctx_status(context) do mongocrypt_ctx_kms_done(context.ctx_p) end end # Perform the final encryption or decryption and return a BSON document # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # @param [ FFI::Pointer ] op_bson (out param) A pointer to a # mongocrypt_binary_t object that will have a reference to the # final encrypted BSON document # # @return [ Boolean ] A boolean indicating the success of the operation attach_function :mongocrypt_ctx_finalize, [:pointer, :pointer], :void # Finalize the state machine represented by the Context # # @param [ Mongo::Crypt::Context ] context # # @raise [ Mongo::Error::CryptError ] If the state machine is not successfully # finalized def self.ctx_finalize(context) binary = Binary.new check_ctx_status(context) do mongocrypt_ctx_finalize(context.ctx_p, binary.ref) end # TODO since the binary references a C pointer, and ByteBuffer is # written in C in MRI, we could omit a copy of the data by making # ByteBuffer reference the string that is owned by libmongocrypt. BSON::Document.from_bson(BSON::ByteBuffer.new(binary.to_s), mode: :bson) end # Destroy the reference to the mongocrypt_ctx_t object # # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object # # @return [ nil ] Always nil attach_function :mongocrypt_ctx_destroy, [:pointer], :void # A callback to a function that performs AES encryption or decryption # # @param [ FFI::Pointer | nil] ctx An optional pointer to a context object # that may have been set when hooks were enabled. # @param [ FFI::Pointer ] key A pointer to a mongocrypt_binary_t object # that references the 32-byte AES encryption key # @param [ FFI::Pointer ] iv A pointer to a mongocrypt_binary_t object # that references the 16-byte AES IV # @param [ FFI::Pointer ] in A pointer to a mongocrypt_binary_t object # that references the value to be encrypted/decrypted # @param [ FFI::Pointer ] out (out param) A pointer to a # mongocrypt_binary_t object will have a reference to the encrypted/ # decrypted value written to it by libmongocrypt # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # object to which an error message will be written if encryption fails # # @return [ Bool ] Whether encryption/decryption was successful callback( :mongocrypt_crypto_fn, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :bool ) # A callback to a function that performs HMAC SHA-512 or SHA-256 # # @param [ FFI::Pointer | nil ] ctx An optional pointer to a context object # that may have been set when hooks were enabled. # @param [ FFI::Pointer ] key A pointer to a mongocrypt_binary_t object # that references the 32-byte HMAC SHA encryption key # @param [ FFI::Pointer ] in A pointer to a mongocrypt_binary_t object # that references the input value # @param [ FFI::Pointer ] out (out param) A pointer to a # mongocrypt_binary_t object will have a reference to the output value # written to it by libmongocrypt # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # object to which an error message will be written if encryption fails # # @return [ Bool ] Whether HMAC-SHA was successful callback( :mongocrypt_hmac_fn, [:pointer, :pointer, :pointer, :pointer, :pointer], :bool ) # A callback to a SHA-256 hash function # # @param [ FFI::Pointer | nil ] ctx An optional pointer to a context object # that may have been set when hooks were enabled. # @param [ FFI::Pointer ] in A pointer to a mongocrypt_binary_t object # that references the value to be hashed # @param [ FFI::Pointer ] out (out param) A pointer to a # mongocrypt_binary_t object will have a reference to the output value # written to it by libmongocrypt # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # object to which an error message will be written if encryption fails # # @return [ Bool ] Whether hashing was successful callback :mongocrypt_hash_fn, [:pointer, :pointer, :pointer, :pointer], :bool # A callback to a crypto secure random function # # @param [ FFI::Pointer | nil ] ctx An optional pointer to a context object # that may have been set when hooks were enabled. # @param [ FFI::Pointer ] out (out param) A pointer to a # mongocrypt_binary_t object will have a reference to the output value # written to it by libmongocrypt # @param [ Integer ] count The number of random bytes to return # @param [ FFI::Pointer ] status A pointer to a mongocrypt_status_t # object to which an error message will be written if encryption fails # # @return [ Bool ] Whether hashing was successful callback :mongocrypt_random_fn, [:pointer, :pointer, :int, :pointer], :bool # Set crypto hooks on the provided mongocrypt object # # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object # @param [ Proc ] An AES encryption method # @param [ Proc ] An AES decryption method # @param [ Proc ] A random method # @param [ Proc ] A HMAC SHA-512 method # @param [ Proc ] A HMAC SHA-256 method # @param [ Proc ] A SHA-256 hash method # @param [ FFI::Pointer | nil ] ctx An optional pointer to a context object # that may have been set when hooks were enabled. # # @return [ Boolean ] Whether setting this option succeeded attach_function( :mongocrypt_setopt_crypto_hooks, [ :pointer, :mongocrypt_crypto_fn, :mongocrypt_crypto_fn, :mongocrypt_random_fn, :mongocrypt_hmac_fn, :mongocrypt_hmac_fn, :mongocrypt_hash_fn, :pointer ], :bool ) # Set crypto callbacks on the Handle # # @param [ Mongo::Crypt::Handle ] handle # @param [ Method ] aes_encrypt_cb An AES encryption method # @param [ Method ] aes_decrypt_cb A AES decryption method # @param [ Method ] random_cb A method that returns a string of random bytes # @param [ Method ] hmac_sha_512_cb A HMAC SHA-512 method # @param [ Method ] hmac_sha_256_cb A HMAC SHA-256 method # @param [ Method ] hmac_hash_cb A SHA-256 hash method # # @raise [ Mongo::Error::CryptError ] If the callbacks aren't set successfully def self.setopt_crypto_hooks(handle, aes_encrypt_cb, aes_decrypt_cb, random_cb, hmac_sha_512_cb, hmac_sha_256_cb, hmac_hash_cb ) check_status(handle) do mongocrypt_setopt_crypto_hooks(handle.ref, aes_encrypt_cb, aes_decrypt_cb, random_cb, hmac_sha_512_cb, hmac_sha_256_cb, hmac_hash_cb, nil ) end end # Raise a Mongo::Error::CryptError based on the status of the underlying # mongocrypt_t object. # # @return [ nil ] Always nil. def self.check_status(handle) unless yield status = Status.new mongocrypt_status(handle.ref, status.ref) status.raise_crypt_error end end # Raise a Mongo::Error::CryptError based on the status of the underlying # mongocrypt_ctx_t object. # # @return [ nil ] Always nil. def self.check_ctx_status(context) if block_given? do_raise = !yield else do_raise = true end if do_raise status = Status.new mongocrypt_ctx_status(context.ctx_p, status.ref) status.raise_crypt_error end end # Checks that the specified data is a Hash before serializing # it to BSON to prevent errors from libmongocrypt # # @note All BSON::Document instances are also Hash instances # # @param [ Object ] data The data to be passed to libmongocrypt # # @raise [ Mongo::Error::CryptError ] If the data is not a Hash def self.validate_document(data) return if data.is_a?(Hash) if data.nil? message = "Attempted to pass nil data to libmongocrypt. " + "Data must be a Hash" else message = "Attempted to pass invalid data to libmongocrypt: #{data} " + "Data must be a Hash" end raise Error::CryptError.new(message) end end end end