text: /*
   * Copyright 2019-present 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.
   */
  
  /*
   * Comments in this implementation refer to:
   * [MCGREW] https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05
   */
  
  #include "../mongocrypt-crypto-private.h"
  #include "../mongocrypt-private.h"
  #include "../mongocrypt-log-private.h"
  
  #ifdef MONGOCRYPT_ENABLE_CRYPTO_LIBCRYPTO
  
  #include <bson/bson.h>
  
  #include <openssl/crypto.h>
  #include <openssl/evp.h>
  #include <openssl/err.h>
  #include <openssl/hmac.h>
  #include <openssl/rand.h>
  
  #if OPENSSL_VERSION_NUMBER < 0x10100000L || \
     (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
  
  static HMAC_CTX *
  HMAC_CTX_new (void)
  {
     return bson_malloc0 (sizeof (HMAC_CTX));
  }
  
  static void
  HMAC_CTX_free (HMAC_CTX *ctx)
  {
     HMAC_CTX_cleanup (ctx);
     bson_free (ctx);
  }
  #endif
  
  bool _native_crypto_initialized = false;
  
  void
  _native_crypto_init ()
  {
     _native_crypto_initialized = true;
  }
  
  /* _encrypt_with_cipher encrypts @in with the OpenSSL cipher specified by
   * @cipher.
   * @key is the input key. @iv is the input IV.
   * @out is the output ciphertext. @out must be allocated by the caller with
   * enough room for the ciphertext.
   * @bytes_written is the number of bytes that were written to @out.
   * Returns false and sets @status on error. @status is required. */
  static bool
  _encrypt_with_cipher (const EVP_CIPHER *cipher, aes_256_args_t args)
  {
     EVP_CIPHER_CTX *ctx;
     bool ret = false;
     int intermediate_bytes_written;
     mongocrypt_status_t *status = args.status;
  
     ctx = EVP_CIPHER_CTX_new ();
  
     BSON_ASSERT (ctx);
     BSON_ASSERT (cipher);
     BSON_ASSERT (NULL == args.iv ||
                  EVP_CIPHER_iv_length (cipher) == args.iv->len);
     BSON_ASSERT (EVP_CIPHER_key_length (cipher) == args.key->len);
  
     if (!EVP_EncryptInit_ex (ctx,
                              cipher,
                              NULL /* engine */,
                              args.key->data,
                              NULL == args.iv ? NULL : args.iv->data)) {
        CLIENT_ERR ("error in EVP_EncryptInit_ex: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        goto done;
     }
  
     /* Disable the default OpenSSL padding. */
     EVP_CIPHER_CTX_set_padding (ctx, 0);
  
     *args.bytes_written = 0;
     if (!EVP_EncryptUpdate (ctx,
                             args.out->data,
                             &intermediate_bytes_written,
                             args.in->data,
                             args.in->len)) {
        CLIENT_ERR ("error in EVP_EncryptUpdate: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        goto done;
     }
  
     *args.bytes_written = (uint32_t) intermediate_bytes_written;
  
     if (!EVP_EncryptFinal_ex (
            ctx, args.out->data, &intermediate_bytes_written)) {
        CLIENT_ERR ("error in EVP_EncryptFinal_ex: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        goto done;
     }
  
     *args.bytes_written += (uint32_t) intermediate_bytes_written;
  
     ret = true;
  done:
     EVP_CIPHER_CTX_free (ctx);
     return ret;
  }
  
  /* _decrypt_with_cipher decrypts @in with the OpenSSL cipher specified by
   * @cipher.
   * @key is the input key. @iv is the input IV.
   * @out is the output plaintext. @out must be allocated by the caller with
   * enough room for the plaintext.
   * @bytes_written is the number of bytes that were written to @out.
   * Returns false and sets @status on error. @status is required. */
  static bool
  _decrypt_with_cipher (const EVP_CIPHER *cipher, aes_256_args_t args)
  {
     EVP_CIPHER_CTX *ctx;
     bool ret = false;
     int intermediate_bytes_written;
     mongocrypt_status_t *status = args.status;
  
     ctx = EVP_CIPHER_CTX_new ();
  
     BSON_ASSERT (EVP_CIPHER_iv_length (cipher) == args.iv->len);
     BSON_ASSERT (EVP_CIPHER_key_length (cipher) == args.key->len);
  
     if (!EVP_DecryptInit_ex (
            ctx, cipher, NULL /* engine */, args.key->data, args.iv->data)) {
        CLIENT_ERR ("error in EVP_DecryptInit_ex: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        goto done;
     }
  
     /* Disable padding. */
     EVP_CIPHER_CTX_set_padding (ctx, 0);
  
     *args.bytes_written = 0;
  
     if (!EVP_DecryptUpdate (ctx,
                             args.out->data,
                             &intermediate_bytes_written,
                             args.in->data,
                             args.in->len)) {
        CLIENT_ERR ("error in EVP_DecryptUpdate: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        goto done;
     }
  
     *args.bytes_written = intermediate_bytes_written;
  
     if (!EVP_DecryptFinal_ex (
            ctx, args.out->data, &intermediate_bytes_written)) {
        CLIENT_ERR ("error in EVP_DecryptFinal_ex: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        goto done;
     }
  
     *args.bytes_written += intermediate_bytes_written;
  
     ret = true;
  done:
     EVP_CIPHER_CTX_free (ctx);
     return ret;
  }
  
  bool
  _native_crypto_aes_256_cbc_encrypt (aes_256_args_t args)
  {
     return _encrypt_with_cipher (EVP_aes_256_cbc (), args);
  }
  
  bool
  _native_crypto_aes_256_cbc_decrypt (aes_256_args_t args)
  {
     return _decrypt_with_cipher (EVP_aes_256_cbc (), args);
  }
  
  bool
  _native_crypto_aes_256_ecb_encrypt (aes_256_args_t args)
  {
     return _encrypt_with_cipher (EVP_aes_256_ecb (), args);
  }
  
  
  /* _hmac_with_hash computes an HMAC of @in with the OpenSSL hash specified by
   * @hash.
   * @key is the input key.
   * @out is the output. @out must be allocated by the caller with
   * the exact length for the output. E.g. for HMAC 256, @out->len must be 32.
   * Returns false and sets @status on error. @status is required. */
  bool
  _hmac_with_hash (const EVP_MD *hash,
                   const _mongocrypt_buffer_t *key,
                   const _mongocrypt_buffer_t *in,
                   _mongocrypt_buffer_t *out,
                   mongocrypt_status_t *status)
  {
  #if OPENSSL_VERSION_NUMBER >= 0x10100000L
     if (!HMAC (hash,
                key->data,
                key->len,
                in->data,
                in->len,
                out->data,
                NULL /* unused out len */)) {
        CLIENT_ERR ("error initializing HMAC: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        return false;
     }
     return true;
  #else
     HMAC_CTX *ctx;
     bool ret = false;
  
     ctx = HMAC_CTX_new ();
  
     if (out->len != EVP_MD_size (hash)) {
        CLIENT_ERR ("out does not contain %d bytes", EVP_MD_size (hash));
        return false;
     }
  
     if (!HMAC_Init_ex (ctx, key->data, key->len, hash, NULL /* engine */)) {
        CLIENT_ERR ("error initializing HMAC: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        goto done;
     }
  
     if (!HMAC_Update (ctx, in->data, in->len)) {
        CLIENT_ERR ("error updating HMAC: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        goto done;
     }
  
     if (!HMAC_Final (ctx, out->data, NULL /* unused out len */)) {
        CLIENT_ERR ("error finalizing: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        goto done;
     }
  
     ret = true;
  done:
     HMAC_CTX_free (ctx);
     return ret;
  #endif
  }
  
  bool
  _native_crypto_hmac_sha_512 (const _mongocrypt_buffer_t *key,
                               const _mongocrypt_buffer_t *in,
                               _mongocrypt_buffer_t *out,
                               mongocrypt_status_t *status)
  {
     return _hmac_with_hash (EVP_sha512 (), key, in, out, status);
  }
  
  
  bool
  _native_crypto_random (_mongocrypt_buffer_t *out,
                         uint32_t count,
                         mongocrypt_status_t *status)
  {
     int ret = RAND_bytes (out->data, count);
     /* From man page: "RAND_bytes() and RAND_priv_bytes() return 1 on success, -1
      * if not supported by the current RAND method, or 0 on other failure. The
      * error code can be obtained by ERR_get_error(3)" */
     if (ret == -1) {
        CLIENT_ERR ("secure random IV not supported: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        return false;
     } else if (ret == 0) {
        CLIENT_ERR ("failed to generate random IV: %s",
                    ERR_error_string (ERR_get_error (), NULL));
        return false;
     }
     return true;
  }
  
  bool
  _native_crypto_aes_256_ctr_encrypt (aes_256_args_t args)
  {
     return _encrypt_with_cipher (EVP_aes_256_ctr (), args);
  }
  
  bool
  _native_crypto_aes_256_ctr_decrypt (aes_256_args_t args)
  {
     return _decrypt_with_cipher (EVP_aes_256_ctr (), args);
  }
  
  bool
  _native_crypto_hmac_sha_256 (const _mongocrypt_buffer_t *key,
                               const _mongocrypt_buffer_t *in,
                               _mongocrypt_buffer_t *out,
                               mongocrypt_status_t *status)
  {
     return _hmac_with_hash (EVP_sha256 (), key, in, out, status);
  }
  
  #endif /* MONGOCRYPT_ENABLE_CRYPTO_LIBCRYPTO */
  