# 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.

require 'securerandom'
require 'digest'

module Mongo
  module Crypt

    # A helper module that implements cryptography methods required
    # for native Ruby crypto hooks. These methods are passed into FFI
    # as C callbacks and called from the libmongocrypt library.
    #
    # @api private
    module Hooks

      # An AES encrypt or decrypt method.
      #
      # @param [ String ] key The 32-byte AES encryption key
      # @param [ String ] iv The 16-byte AES IV
      # @param [ String ] input The data to be encrypted/decrypted
      # @param [ true | false ] decrypt Whether this method is decrypting. Default is
      #   false, which means the method will create an encryption cipher by default
      #
      # @return [ String ] Output
      # @raise [ Exception ] Exceptions raised during encryption are propagated
      #   to caller.
      def aes(key, iv, input, decrypt: false)
        cipher = OpenSSL::Cipher::AES.new(256, :CBC)

        decrypt ? cipher.decrypt : cipher.encrypt
        cipher.key = key
        cipher.iv = iv
        cipher.padding = 0

        encrypted = cipher.update(input)
      end
      module_function :aes

      # Crypto secure random function
      #
      # @param [ Integer ] num_bytes The number of random bytes requested
      #
      # @return [ String ]
      # @raise [ Exception ] Exceptions raised during encryption are propagated
      #   to caller.
      def random(num_bytes)
        SecureRandom.random_bytes(num_bytes)
      end
      module_function :random

      # An HMAC SHA-512 or SHA-256 function
      #
      # @param [ String ] digest_name The name of the digest, either "SHA256" or "SHA512"
      # @param [ String ] key The 32-byte AES encryption key
      # @param [ String ] input The data to be tagged
      #
      # @return [ String ]
      # @raise [ Exception ] Exceptions raised during encryption are propagated
      #   to caller.
      def hmac_sha(digest_name, key, input)
        OpenSSL::HMAC.digest(digest_name, key, input)
      end
      module_function :hmac_sha

      # A crypto hash (SHA-256) function
      #
      # @param [ String ] input The data to be hashed
      #
      # @return [ String ]
      # @raise [ Exception ] Exceptions raised during encryption are propagated
      #   to caller.
      def hash_sha256(input)
        Digest::SHA2.new(256).digest(input)
      end
      module_function :hash_sha256
    end
  end
end