/* Copyright (c) 2019, Google Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */

#include <signal.h>
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include <cstring>

#include <sstream>

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <cstdarg>
#include <iostream>

#include <openssl/aead.h>
#include <openssl/aes.h>
#include <openssl/bn.h>
#include <openssl/cipher.h>
#include <openssl/cmac.h>
#include <openssl/ctrdrbg.h>
#include <openssl/curve25519.h>
#include <openssl/dh.h>
#include <openssl/digest.h>
#include <openssl/ec.h>
#include <openssl/ec_key.h>
#include <openssl/ecdh.h>
#include <openssl/ecdsa.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/experimental/kem_deterministic_api.h>
#include <openssl/hkdf.h>
#include <openssl/hmac.h>
#include <openssl/kdf.h>
#include <openssl/obj.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/span.h>
#include <openssl/sshkdf.h>

#include "../../../../crypto/fipsmodule/ec/internal.h"
#include "../../../../crypto/fipsmodule/hmac/internal.h"
#include "../../../../crypto/fipsmodule/rand/internal.h"
#include "../../../../crypto/fipsmodule/curve25519/internal.h"
#include "modulewrapper.h"


namespace bssl {
namespace acvp {

#if defined(OPENSSL_TRUSTY)
#include <trusty_log.h>
#define LOG_ERROR(...) TLOGE(__VA_ARGS__)
#define TLOG_TAG "modulewrapper"
#else
#define LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
#endif  // OPENSSL_TRUSTY

#define AES_GCM_NONCE_LENGTH 12

constexpr size_t kMaxArgLength = (1 << 20);

RequestBuffer::~RequestBuffer() = default;

class RequestBufferImpl : public RequestBuffer {
 public:
  ~RequestBufferImpl() = default;

  std::vector<uint8_t> buf;
  Span<const uint8_t> args[kMaxArgs];
};

// static
std::unique_ptr<RequestBuffer> RequestBuffer::New() {
  return std::unique_ptr<RequestBuffer>(new RequestBufferImpl);
}

static bool ReadAll(std::istream *stream, void *in_data, size_t data_len) {
  size_t read = stream->read(static_cast<char *>(in_data), data_len).gcount();

  if (read != data_len) {
    return false;
  }
  return true;
}

Span<const Span<const uint8_t>> ParseArgsFromStream(std::istream *stream,
                                                    RequestBuffer *in_buffer) {
  RequestBufferImpl *buffer = static_cast<RequestBufferImpl *>(in_buffer);
  uint32_t nums[1 + kMaxArgs];
  const Span<const Span<const uint8_t>> empty_span;

  if (!ReadAll(stream, nums, sizeof(uint32_t) * 2)) {
    return empty_span;
  }

  const size_t num_args = nums[0];
  if (num_args == 0) {
    LOG_ERROR("Invalid, zero-argument operation requested.\n");
    return empty_span;
  } else if (num_args > kMaxArgs) {
    LOG_ERROR("Operation requested with %zu args, but %zu is the limit.\n",
              num_args, kMaxArgs);
    return empty_span;
  }

  if (num_args > 1 &&
      !ReadAll(stream, &nums[2], sizeof(uint32_t) * (num_args - 1))) {
    return empty_span;
  }

  size_t need = 0;
  for (size_t i = 0; i < num_args; i++) {
    const size_t arg_length = nums[i + 1];
    if (i == 0 && arg_length > kMaxNameLength) {
      LOG_ERROR("Operation with name of length %zu exceeded limit of %zu.\n",
                arg_length, kMaxNameLength);
      return empty_span;
    } else if (arg_length > kMaxArgLength) {
      LOG_ERROR(
          "Operation with argument of length %zu exceeded limit of %zu.\n",
          arg_length, kMaxArgLength);
      return empty_span;
    }

    // This static_assert confirms that the following addition doesn't
    // overflow.
    static_assert((kMaxArgs - 1 * kMaxArgLength) + kMaxNameLength > (1 << 30),
                  "Argument limits permit excessive messages");
    need += arg_length;
  }

  if (need > buffer->buf.size()) {
    size_t alloced = need + (need >> 1);
    if (alloced < need) {
      abort();
    }
    buffer->buf.resize(alloced);
  }

  if (!ReadAll(stream, buffer->buf.data(), need)) {
    return empty_span;
  }

  size_t offset = 0;
  for (size_t i = 0; i < num_args; i++) {
    buffer->args[i] = Span<const uint8_t>(&buffer->buf[offset], nums[i + 1]);
    offset += nums[i + 1];
  }

  return Span<const Span<const uint8_t>>(buffer->args, num_args);
}

bool WriteReplyToStream(std::ostream *stream,
                        const std::vector<Span<const uint8_t>> &spans) {
  if (spans.empty() || spans.size() > kMaxArgs) {
    abort();
  }
  uint32_t nums[1 + kMaxArgs];
  nums[0] = spans.size();
  for (size_t i = 0; i < spans.size(); i++) {
    const auto &span = spans[i];
    nums[i + 1] = span.size();
  }
  stream->write(reinterpret_cast<const char *>(nums),
                sizeof(uint32_t) * (1 + spans.size()));

  for (size_t i = 0; i < spans.size(); i++) {
    const auto &span = spans[i];
    if (span.empty()) {
      continue;
    }
    stream->write(reinterpret_cast<const char *>(span.data()),
                  sizeof(uint8_t) * span.size());
  }

  stream->flush();
  return true;
}

static bool GetConfig(const Span<const uint8_t> args[],
                      ReplyCallback write_reply) {
  static constexpr char kConfig[] =
      R"([
      {
        "algorithm": "SHA2-224",
        "revision": "1.0",
        "messageLength": [{
          "min": 0, "max": 65528, "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA2-256",
        "revision": "1.0",
        "messageLength": [{
          "min": 0, "max": 65528, "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA2-384",
        "revision": "1.0",
        "messageLength": [{
          "min": 0, "max": 65528, "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA2-512",
        "revision": "1.0",
        "messageLength": [{
          "min": 0, "max": 65528, "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA2-512/224",
        "revision": "1.0",
        "messageLength": [{
          "min": 0, "max": 65528, "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA2-512/256",
        "revision": "1.0",
        "messageLength": [{
          "min": 0, "max": 65528, "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA3-224",
        "revision": "2.0",
        "messageLength": [{
          "min": 0, "max": 65536, "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA3-256",
        "revision": "2.0",
        "messageLength": [{
          "min": 0, "max": 65536, "increment": 8
          }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA3-384",
        "revision": "2.0",
        "messageLength": [{
          "min": 0, "max": 65536, "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA3-512",
        "revision": "2.0",
        "messageLength": [{
          "min": 0, "max": 65536, "increment": 8
          }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHAKE-128",
        "revision": "1.0",
        "messageLength": [{
          "min": 0, "max": 65536, "increment": 8
        }],
        "outputLen": [{
            "min": 128,
            "max": 8192,
            "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHAKE-256",
        "revision": "1.0",
        "messageLength": [{
          "min": 0, "max": 65536, "increment": 8
        }],
        "outputLen": [{
            "min": 128,
            "max": 8192,
            "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "SHA-1",
        "revision": "1.0",
        "messageLength": [{
          "min": 0, "max": 65528, "increment": 8
        }],
        "performLargeDataTest": [1, 2, 4, 8]
      },
      {
        "algorithm": "ACVP-AES-XTS",
        "revision": "2.0",
        "direction": ["encrypt", "decrypt"],
        "keyLen": [256],
        "payloadLen": [1024],
        "tweakMode": ["number"],
        "dataUnitLenMatchesPayload": true
      },
      {
        "algorithm": "ACVP-AES-ECB",
        "revision": "1.0",
        "direction": ["encrypt", "decrypt"],
        "keyLen": [128, 192, 256]
      },
      {
        "algorithm": "ACVP-AES-CTR",
        "revision": "1.0",
        "direction": ["encrypt", "decrypt"],
        "keyLen": [128, 192, 256],
        "payloadLen": [{
          "min": 8, "max": 128, "increment": 8
        }],
        "incrementalCounter": true,
        "overflowCounter": true,
        "performCounterTests": true
      },
      {
        "algorithm": "ACVP-AES-CBC",
        "revision": "1.0",
        "direction": ["encrypt", "decrypt"],
        "keyLen": [128, 192, 256]
      },
      {
        "algorithm": "ACVP-AES-GCM",
        "revision": "1.0",
        "direction": ["encrypt", "decrypt"],
        "keyLen": [128, 192, 256],
        "payloadLen": [{
          "min": 0, "max": 65536, "increment": 8
        }],
        "aadLen": [{
          "min": 0, "max": 65536, "increment": 8
        }],
        "tagLen": [32, 64, 96, 104, 112, 120, 128],
        "ivLen": [96],
        "ivGen": ["external", "internal"],
        "ivGenMode": "8.2.2"
      },
      {
        "algorithm": "ACVP-AES-GMAC",
        "revision": "1.0",
        "direction": ["encrypt", "decrypt"],
        "keyLen": [128, 192, 256],
        "payloadLen": [{
          "min": 0, "max": 65536, "increment": 8
        }],
        "aadLen": [{
          "min": 0, "max": 65536, "increment": 8
        }],
        "tagLen": [32, 64, 96, 104, 112, 120, 128],
        "ivLen": [96],
        "ivGen": "external"
      },
      {
        "algorithm": "ACVP-AES-KW",
        "revision": "1.0",
        "direction": [
            "encrypt",
            "decrypt"
        ],
        "kwCipher": [
            "cipher"
        ],
        "keyLen": [
            128, 192, 256
        ],
        "payloadLen": [{"min": 128, "max": 4096, "increment": 64}]
      },
      {
        "algorithm": "ACVP-AES-KWP",
        "revision": "1.0",
        "direction": [
            "encrypt",
            "decrypt"
        ],
        "kwCipher": [
            "cipher"
        ],
        "keyLen": [
            128, 192, 256
        ],
        "payloadLen": [{"min": 8, "max": 4096, "increment": 8}]
      },
      {
        "algorithm": "ACVP-AES-CCM",
        "revision": "1.0",
        "direction": [
            "encrypt",
            "decrypt"
        ],
        "keyLen": [
            128
        ],
        "payloadLen": [{"min": 0, "max": 256, "increment": 8}],
        "ivLen": [104],
        "tagLen": [32, 64],
        "aadLen": [{"min": 0, "max": 524288, "increment": 8}]
      },
      {
        "algorithm": "HMAC-SHA-1",
        "revision": "1.0",
        "keyLen": [{
          "min": 8, "max": 524288, "increment": 8
        }],
        "macLen": [{
          "min": 32, "max": 160, "increment": 8
        }]
      },
      {
        "algorithm": "HMAC-SHA2-224",
        "revision": "1.0",
        "keyLen": [{
          "min": 8, "max": 524288, "increment": 8
        }],
        "macLen": [{
          "min": 32, "max": 224, "increment": 8
        }]
      },
      {
        "algorithm": "HMAC-SHA2-256",
        "revision": "1.0",
        "keyLen": [{
          "min": 8, "max": 524288, "increment": 8
        }],
        "macLen": [{
          "min": 32, "max": 256, "increment": 8
        }]
      },
      {
        "algorithm": "HMAC-SHA2-384",
        "revision": "1.0",
        "keyLen": [{
          "min": 8, "max": 524288, "increment": 8
        }],
        "macLen": [{
          "min": 32, "max": 384, "increment": 8
        }]
      },
      {
        "algorithm": "HMAC-SHA2-512",
        "revision": "1.0",
        "keyLen": [{
          "min": 8, "max": 524288, "increment": 8
        }],
        "macLen": [{
          "min": 32, "max": 512, "increment": 8
        }]
      },
      {
        "algorithm": "HMAC-SHA2-512/224",
        "revision": "1.0",
        "keyLen": [{
          "min": 8, "max": 524288, "increment": 8
        }],
        "macLen": [{
          "min": 32, "max": 224, "increment": 8
        }]
      },
      {
        "algorithm": "HMAC-SHA2-512/256",
        "revision": "1.0",
        "keyLen": [{
          "min": 8, "max": 524288, "increment": 8
        }],
        "macLen": [{
          "min": 32, "max": 256, "increment": 8
        }]
      },
      {
        "vsId": 0,
        "algorithm": "PBKDF",
        "revision": "1.0",
        "capabilities": [
          {
            "iterationCount": [
              {
                "min": 1,
                "max": 10000,
                "increment": 1
              }
            ],
            "passwordLen": [
              {
                "min": 8,
                "max": 64,
                "increment": 1
              }
            ],
            "saltLen": [
              {
                "min": 128,
                "max": 512,
                "increment": 8
              }
            ],
            "keyLen": [
              {
                "min": 112,
                "max": 2048,
                "increment": 8
              }
            ],
            "hmacAlg": [
              "SHA-1",
              "SHA2-224",
              "SHA2-256",
              "SHA2-384",
              "SHA2-512"
            ]
          }
        ]
      },
      {
        "algorithm": "ctrDRBG",
        "revision": "1.0",
        "predResistanceEnabled": [false],
        "reseedImplemented": true,
        "capabilities": [{
          "mode": "AES-256",
          "derFuncEnabled": false,
          "entropyInputLen": [384],
          "nonceLen": [0],
          "persoStringLen": [{"min": 0, "max": 384, "increment": 16}],
          "additionalInputLen": [
            {"min": 0, "max": 384, "increment": 16}
          ],
          "returnedBitsLen": 2048
        }]
      },
      {
        "algorithm": "ECDSA",
        "mode": "keyGen",
        "revision": "1.0",
        "curve": [
          "P-224",
          "P-256",
          "P-384",
          "P-521"
        ],
        "secretGenerationMode": [
          "testing candidates"
        ]
      },
      {
        "algorithm": "ECDSA",
        "mode": "keyVer",
        "revision": "1.0",
        "curve": [
          "P-224",
          "P-256",
          "P-384",
          "P-521"
        ]
      },
      {
        "algorithm": "ECDSA",
        "mode": "sigGen",
        "revision": "1.0",
        "capabilities": [{
          "curve": [
            "P-224",
            "P-256",
            "P-384",
            "P-521"
          ],
          "hashAlg": [
            "SHA2-224",
            "SHA2-256",
            "SHA2-384",
            "SHA2-512",
            "SHA2-512/224",
            "SHA2-512/256",
            "SHA3-224",
            "SHA3-256",
            "SHA3-384",
            "SHA3-512"
          ]
        }]
      },
      {
        "algorithm": "ECDSA",
        "mode": "sigVer",
        "revision": "1.0",
        "capabilities": [{
          "curve": [
            "P-224",
            "P-256",
            "P-384",
            "P-521"
          ],
          "hashAlg": [
            "SHA-1",
            "SHA2-224",
            "SHA2-256",
            "SHA2-384",
            "SHA2-512",
            "SHA2-512/224",
            "SHA2-512/256",
            "SHA3-224",
            "SHA3-256",
            "SHA3-384",
            "SHA3-512"
          ]
        }]
      },)"
      R"({
        "algorithm": "RSA",
        "mode": "keyGen",
        "revision": "FIPS186-5",
        "infoGeneratedByServer": true,
        "pubExpMode": "fixed",
        "fixedPubExp": "010001",
        "keyFormat": "standard",
        "capabilities": [{
          "randPQ": "probable",
          "properties": [{
              "modulo": 2048,
              "primeTest": [
                "2powSecStr"
              ],
              "pMod8": 0,
              "qMod8": 0
            },{
              "modulo": 3072,
              "primeTest": [
                "2powSecStr"
              ],
              "pMod8": 0,
              "qMod8": 0
            },{
              "modulo": 4096,
              "primeTest": [
                "2powSecStr"
              ],
              "pMod8": 0,
              "qMod8": 0
            },{
              "modulo": 6144,
              "primeTest": [
                "2powSecStr"
              ],
              "pMod8": 0,
              "qMod8": 0
            },{
              "modulo": 8192,
              "primeTest": [
                "2powSecStr"
              ],
              "pMod8": 0,
              "qMod8": 0
          }]
        }]
      },)"
      R"({
        "algorithm": "RSA",
        "mode": "sigGen",
        "revision": "FIPS186-5",
        "capabilities": [{
          "sigType": "pkcs1v1.5",
          "properties": [{
              "modulo": 2048,
              "hashPair": [{
                  "hashAlg": "SHA2-224"
                },{
                  "hashAlg": "SHA2-256"
                },{
                  "hashAlg": "SHA2-384"
                },{
                  "hashAlg": "SHA2-512"
                },{
                  "hashAlg": "SHA2-512/224"
                },{
                  "hashAlg": "SHA2-512/256"
                },{
                  "hashAlg": "SHA3-224"
                },{
                  "hashAlg": "SHA3-256"
                },{
                  "hashAlg": "SHA3-384"
                },{
                  "hashAlg": "SHA3-512"
              }]
            },{
              "modulo": 3072,
              "hashPair": [{
                  "hashAlg": "SHA2-224"
                },{
                  "hashAlg": "SHA2-256"
                },{
                  "hashAlg": "SHA2-384"
                },{
                  "hashAlg": "SHA2-512"
                },{
                  "hashAlg": "SHA2-512/224"
                },{
                  "hashAlg": "SHA2-512/256"
                },{
                  "hashAlg": "SHA3-224"
                },{
                  "hashAlg": "SHA3-256"
                },{
                  "hashAlg": "SHA3-384"
                },{
                  "hashAlg": "SHA3-512"
              }]
            },{
              "modulo": 4096,
              "hashPair": [{
                  "hashAlg": "SHA2-224"
                },{
                  "hashAlg": "SHA2-256"
                },{
                  "hashAlg": "SHA2-384"
                },{
                  "hashAlg": "SHA2-512"
                },{
                  "hashAlg": "SHA2-512/224"
                },{
                  "hashAlg": "SHA2-512/256"
                },{
                  "hashAlg": "SHA3-224"
                },{
                  "hashAlg": "SHA3-256"
                },{
                  "hashAlg": "SHA3-384"
                },{
                  "hashAlg": "SHA3-512"
              }]
            }]
          },{
            "sigType": "pss",
            "properties": [{
                "modulo": 2048,
                "maskFunction": ["mgf1"],
                "hashPair": [{
                    "hashAlg": "SHA2-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA2-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA2-512",
                    "saltLen": 64
                  },{
                    "hashAlg": "SHA2-512/224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-512/256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA3-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA3-512",
                    "saltLen": 64
                }]
              },{
                "modulo": 3072,
                "maskFunction": ["mgf1"],
                "hashPair": [{
                    "hashAlg": "SHA2-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA2-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA2-512",
                    "saltLen": 64
                  },{
                    "hashAlg": "SHA2-512/224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-512/256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA3-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA3-512",
                    "saltLen": 64
                }]
              },{
                "modulo": 4096,
                "maskFunction": ["mgf1"],
                "hashPair": [{
                    "hashAlg": "SHA2-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA2-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA2-512",
                    "saltLen": 64
                  },{
                    "hashAlg": "SHA2-512/224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-512/256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA3-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA3-512",
                    "saltLen": 64
                }]
            }]
        }]
      },)"
      R"({
        "algorithm": "RSA",
        "mode": "sigVer",
        "revision": "FIPS186-4",
        "pubExpMode": "fixed",
        "fixedPubExp": "010001",
        "capabilities": [{
            "sigType": "pkcs1v1.5",
            "properties": [{
                "modulo": 1024,
                "hashPair": [{
                    "hashAlg": "SHA-1"
                  },{
                    "hashAlg": "SHA2-224"
                  },{
                    "hashAlg": "SHA2-256"
                  },{
                    "hashAlg": "SHA2-384"
                  },{
                    "hashAlg": "SHA2-512"
                  },{
                    "hashAlg": "SHA2-512/224"
                  },{
                    "hashAlg": "SHA2-512/256"
                }]
              },{
                "modulo": 2048,
                "hashPair": [{
                    "hashAlg": "SHA-1"
                }]
              },{
                "modulo": 3072,
                "hashPair": [{
                    "hashAlg": "SHA-1"
                }]
              },{
                "modulo": 4096,
                "hashPair": [{
                    "hashAlg": "SHA-1"
                }]
            }]
          },{
            "sigType": "pss",
            "properties": [{
                "modulo": 1024,
                "hashPair": [{
                    "hashAlg": "SHA-1",
                    "saltLen": 20
                  },{
                    "hashAlg": "SHA2-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA2-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA2-512/224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-512/256",
                    "saltLen": 32
                }]
              },{
                "modulo": 2048,
                "hashPair": [{
                    "hashAlg": "SHA-1",
                    "saltLen": 20
                }]
              },{
                "modulo": 3072,
                "hashPair": [{
                    "hashAlg": "SHA-1",
                    "saltLen": 20
                }]
              },{
                "modulo": 4096,
                "hashPair": [{
                    "hashAlg": "SHA-1",
                    "saltLen": 20
                }]
            }]
        }]
      },)"
      R"({
        "algorithm": "RSA",
        "mode": "sigVer",
        "revision": "FIPS186-5",
        "pubExpMode": "fixed",
        "fixedPubExp": "010001",
        "capabilities": [{
            "sigType": "pkcs1v1.5",
            "properties": [{
                "modulo": 2048,
                "hashPair": [{
                    "hashAlg": "SHA2-224"
                  },{
                    "hashAlg": "SHA2-256"
                  },{
                    "hashAlg": "SHA2-384"
                  },{
                    "hashAlg": "SHA2-512"
                  },{
                    "hashAlg": "SHA2-512/224"
                  },{
                    "hashAlg": "SHA2-512/256"
                  },{
                    "hashAlg": "SHA3-224"
                  },{
                    "hashAlg": "SHA3-256"
                  },{
                    "hashAlg": "SHA3-384"
                  },{
                    "hashAlg": "SHA3-512"
                }]
              },{
                "modulo": 3072,
                "hashPair": [{
                    "hashAlg": "SHA2-224"
                  },{
                    "hashAlg": "SHA2-256"
                  },{
                    "hashAlg": "SHA2-384"
                  },{
                    "hashAlg": "SHA2-512"
                  },{
                    "hashAlg": "SHA2-512/224"
                  },{
                    "hashAlg": "SHA2-512/256"
                  },{
                    "hashAlg": "SHA3-224"
                  },{
                    "hashAlg": "SHA3-256"
                  },{
                    "hashAlg": "SHA3-384"
                  },{
                    "hashAlg": "SHA3-512"
                }]
              },{
                "modulo": 4096,
                "hashPair": [{
                    "hashAlg": "SHA2-224"
                  },{
                    "hashAlg": "SHA2-256"
                  },{
                    "hashAlg": "SHA2-384"
                  },{
                    "hashAlg": "SHA2-512"
                  },{
                    "hashAlg": "SHA2-512/224"
                  },{
                    "hashAlg": "SHA2-512/256"
                  },{
                    "hashAlg": "SHA3-224"
                  },{
                    "hashAlg": "SHA3-256"
                  },{
                    "hashAlg": "SHA3-384"
                  },{
                    "hashAlg": "SHA3-512"
                }]
            }]
          },{
            "sigType": "pss",
            "properties": [{
                "modulo": 2048,
                "maskFunction": ["mgf1"],
                "hashPair": [{
                    "hashAlg": "SHA2-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA2-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA2-512",
                    "saltLen": 64
                  },{
                    "hashAlg": "SHA2-512/224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-512/256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA3-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA3-512",
                    "saltLen": 64
                }]
              },{
                "modulo": 3072,
                "maskFunction": ["mgf1"],
                "hashPair": [{
                    "hashAlg": "SHA2-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA2-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA2-512",
                    "saltLen": 64
                  },{
                    "hashAlg": "SHA2-512/224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-512/256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA3-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA3-512",
                    "saltLen": 64
                }]
              },{
                "modulo": 4096,
                "maskFunction": ["mgf1"],
                "hashPair": [{
                    "hashAlg": "SHA2-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA2-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA2-512",
                    "saltLen": 64
                  },{
                    "hashAlg": "SHA2-512/224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA2-512/256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-224",
                    "saltLen": 28
                  },{
                    "hashAlg": "SHA3-256",
                    "saltLen": 32
                  },{
                    "hashAlg": "SHA3-384",
                    "saltLen": 48
                  },{
                    "hashAlg": "SHA3-512",
                    "saltLen": 64
                }]
            }]
        }]
      },)"
      R"({
        "algorithm": "CMAC-AES",
        "acvptoolTestOnly": true,
        "revision": "1.0",
        "capabilities": [{
          "direction": ["gen", "ver"],
          "msgLen": [{
            "min": 0,
            "max": 524288,
            "increment": 8
          }],
          "keyLen": [128, 256],
          "macLen": [{
            "min": 8,
            "max": 128,
            "increment": 8
          }]
        }]
      },
      {
        "algorithm": "KDF",
        "revision": "1.0",
        "capabilities": [{
          "kdfMode": "feedback",
          "macMode": [
            "HMAC-SHA-1",
            "HMAC-SHA2-224",
            "HMAC-SHA2-256",
            "HMAC-SHA2-384",
            "HMAC-SHA2-512",
            "HMAC-SHA2-512/224",
            "HMAC-SHA2-512/256"
          ],
          "customKeyInLength": 0,
          "supportedLengths": [{
            "min": 8,
            "max": 1024,
            "increment": 8
          }],
          "fixedDataOrder": ["after fixed data"],
          "counterLength": [8],
          "supportsEmptyIv": true,
          "requiresEmptyIv": true
        },{
          "kdfMode": "counter",
          "macMode": [
            "HMAC-SHA-1",
            "HMAC-SHA2-224",
            "HMAC-SHA2-256",
            "HMAC-SHA2-384",
            "HMAC-SHA2-512",
            "HMAC-SHA2-512/224",
            "HMAC-SHA2-512/256"
          ],
          "supportedLengths": [{
            "min": 8,
            "max": 4096,
            "increment": 8
          }],
          "fixedDataOrder": ["before fixed data"],
          "counterLength": [32]
        }]
      },
      {
        "algorithm": "kdf-components",
        "revision": "1.0",
        "mode": "tls",
        "tlsVersion": [
          "v1.0/1.1",
          "v1.2"
        ],
        "hashAlg": [
          "SHA2-256",
          "SHA2-384",
          "SHA2-512"
        ]
      },
      {
        "algorithm": "kdf-components",
        "mode": "ssh",
        "revision": "1.0",
        "isSample": true,
        "hashAlg": [
          "SHA-1",
          "SHA2-224",
          "SHA2-256",
          "SHA2-384",
          "SHA2-512"
        ],
        "cipher": [
          "TDES",
          "AES-128",
          "AES-192",
          "AES-256"
        ]
      },
      {
        "algorithm": "TLS-v1.2",
        "revision": "RFC7627",
        "mode": "KDF",
        "hashAlg": [
          "SHA2-256",
          "SHA2-384",
          "SHA2-512"
        ]
      },
      {
        "vsId": 0,
        "algorithm": "KDA",
        "mode": "HKDF",
        "revision": "Sp800-56Cr1",
        "isSample": true,
        "fixedInfoPattern": "uPartyInfo||vPartyInfo||l",
        "encoding": [
          "concatenation"
        ],
        "hmacAlg": [
          "SHA-1",
          "SHA2-224",
          "SHA2-256",
          "SHA2-384",
          "SHA2-512"
        ],
        "macSaltMethods": [
          "default",
          "random"
        ],
        "l": 1024,
        "z": [
          {
            "min": 224,
            "max": 65536,
            "increment": 8
          }
        ],
        "performMultiExpansionTests": false
      },
      {
        "algorithm": "KAS-ECC-SSC",
        "revision": "Sp800-56Ar3",
        "scheme": {
          "ephemeralUnified": {
            "kasRole": [
              "initiator",
              "responder"
            ]
          },
          "staticUnified": {
            "kasRole": [
              "initiator",
              "responder"
            ]
          }
        },
        "domainParameterGenerationMethods": [
          "P-224",
          "P-256",
          "P-384",
          "P-521"
        ]
      },
      {
        "algorithm": "KAS-FFC-SSC",
        "revision": "Sp800-56Ar3",
        "scheme": {
          "dhEphem": {
            "kasRole": [
              "initiator",
              "responder"
            ]
          }
        },
        "domainParameterGenerationMethods": [
          "FB",
          "FC"
        ]
      },
      {
        "algorithm": "KDA",
        "mode": "OneStep",
        "revision": "Sp800-56Cr2",
        "prereqVals": [],
        "auxFunctions": [
          {"auxFunctionName": "SHA-1"},
          {"auxFunctionName": "SHA2-224"},
          {"auxFunctionName": "SHA2-256"},
          {"auxFunctionName": "SHA2-384"},
          {"auxFunctionName": "SHA2-512"},
          {"auxFunctionName": "SHA2-512/224"},
          {"auxFunctionName": "SHA2-512/256"},
          {"auxFunctionName": "SHA3-224"},
          {"auxFunctionName": "SHA3-256"},
          {"auxFunctionName": "SHA3-384"},
          {"auxFunctionName": "SHA3-512"},
          {"auxFunctionName": "HMAC-SHA-1", "macSaltMethods": ["default", "random"]},
          {"auxFunctionName": "HMAC-SHA2-224", "macSaltMethods": ["default", "random"]},
          {"auxFunctionName": "HMAC-SHA2-256", "macSaltMethods": ["default", "random"]},
          {"auxFunctionName": "HMAC-SHA2-384", "macSaltMethods": ["default", "random"]},
          {"auxFunctionName": "HMAC-SHA2-512", "macSaltMethods": ["default", "random"]},
          {"auxFunctionName": "HMAC-SHA2-512/224", "macSaltMethods": ["default", "random"]},
          {"auxFunctionName": "HMAC-SHA2-512/256", "macSaltMethods": ["default", "random"]}
        ],
        "fixedInfoPattern": "uPartyInfo||vPartyInfo",
        "encoding": ["concatenation"],
        "z": [{"min": 224, "max": 8192, "increment": 8}],
        "l": 2048
      },)"
      R"({
        "algorithm": "ML-KEM",
        "mode": "keyGen",
        "revision": "FIPS203",
        "parameterSets": ["ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"]
      },
      {
        "algorithm": "ML-KEM",
        "mode": "encapDecap",
        "revision": "FIPS203",
        "parameterSets": ["ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"],
        "functions": ["encapsulation", "decapsulation"]
      },)"
      R"({
        "algorithm": "EDDSA",
        "mode": "keyGen",
        "revision": "1.0",
        "curve": ["ED-25519"]
      },{
        "algorithm": "EDDSA",
        "mode": "keyVer",
        "revision": "1.0",
        "curve": ["ED-25519"]
      },{
        "algorithm": "EDDSA",
        "mode": "sigGen",
        "revision": "1.0",
        "curve": ["ED-25519"],
        "pure": true,
        "preHash": false
      },{
        "algorithm": "EDDSA",
        "mode": "sigVer",
        "revision": "1.0",
        "curve": ["ED-25519"],
        "pure": true,
        "preHash": false
      }])";
  return write_reply({Span<const uint8_t>(
      reinterpret_cast<const uint8_t *>(kConfig), sizeof(kConfig) - 1)});
}

template <uint8_t *(*OneShotHash)(const uint8_t *, size_t, uint8_t *),
          size_t DigestLength>
static bool Hash(const Span<const uint8_t> args[], ReplyCallback write_reply) {
  uint8_t digest[DigestLength];
  OneShotHash(args[0].data(), args[0].size(), digest);
  return write_reply({Span<const uint8_t>(digest)});
}

template <const EVP_MD *(MDFunc)(), size_t DigestLength>
static bool HashSha3(const Span<const uint8_t> args[],
                     ReplyCallback write_reply) {
  uint8_t digest[DigestLength];
  const EVP_MD *md = MDFunc();
  unsigned int md_out_size = DigestLength;

  EVP_Digest(args[0].data(), args[0].size(), digest, &md_out_size, md, NULL);

  return write_reply({Span<const uint8_t>(digest)});
}

template <const EVP_MD *(MDFunc)()>
static bool HashXof(const Span<const uint8_t> args[],
                    ReplyCallback write_reply) {
  // NOTE: Max outLen supported by ACVP is 65536 bits (8192 bytes). If that
  //       changes, we'll need to use a bigger stack-allocated array size here.
  //       https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#name-capabilities-registration
  uint8_t digest[8192];
  const EVP_MD *md = MDFunc();
  const uint8_t *outlen_bytes = args[1].data();
  // MD outLen is passed to modulewrapper as a length-4 byte array representing
  // a little-endian unsigned 32-bit integer.
  uint32_t md_out_size = CRYPTO_load_u32_le(outlen_bytes);

  EVP_Digest(args[0].data(), args[0].size(), digest, &md_out_size, md, NULL);

  return write_reply({Span<const uint8_t>(digest, md_out_size)});
}

template <uint8_t *(*OneShotHash)(const uint8_t *, size_t, uint8_t *),
          size_t DigestLength>
static bool HashMCT(const Span<const uint8_t> args[],
                    ReplyCallback write_reply) {
  if (args[0].size() != DigestLength) {
    return false;
  }

  uint8_t buf[DigestLength * 3];
  memcpy(buf, args[0].data(), DigestLength);
  memcpy(buf + DigestLength, args[0].data(), DigestLength);
  memcpy(buf + 2 * DigestLength, args[0].data(), DigestLength);

  for (size_t i = 0; i < 1000; i++) {
    uint8_t digest[DigestLength];
    OneShotHash(buf, sizeof(buf), digest);
    memmove(buf, buf + DigestLength, DigestLength * 2);
    memcpy(buf + DigestLength * 2, digest, DigestLength);
  }

  return write_reply(
      {Span<const uint8_t>(buf + 2 * DigestLength, DigestLength)});
}

template <const EVP_MD *(MDFunc)(), size_t DigestLength>
static bool HashMCTSha3(const Span<const uint8_t> args[],
                        ReplyCallback write_reply) {
  if (args[0].size() != DigestLength) {
    return false;
  }
  const EVP_MD *evp_md = MDFunc();
  unsigned int md_out_size = DigestLength;

  // The following logic conforms to the Monte Carlo tests described in
  // https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#name-monte-carlo-tests-for-sha3-
  unsigned char md[1001][DigestLength];
  unsigned char msg[1001][DigestLength];

  memcpy(md[0], args[0].data(), DigestLength);

  for (size_t i = 1; i <= 1000; i++) {
    memcpy(msg[i], md[i - 1], DigestLength);
    EVP_Digest(msg[i], sizeof(msg[i]), md[i], &md_out_size, evp_md, NULL);
  }

  return write_reply({Span<const uint8_t>(md[1000])});
}

template <const EVP_MD *(MDFunc)()>
static bool HashMCTXof(const Span<const uint8_t> args[],
                       ReplyCallback write_reply) {
  const uint8_t *max_outlen_bytes = args[1].data();
  const uint8_t *min_outlen_bytes = args[2].data();
  const uint8_t *outlen_bytes = args[3].data();

  // The various output lens are passed to modulewrapper as a length-4 byte
  // array representing a little-endian unsigned 32-bit integer.
  uint32_t min_output_len = CRYPTO_load_u32_le(min_outlen_bytes);
  uint32_t max_output_len = CRYPTO_load_u32_le(max_outlen_bytes);
  uint32_t output_len = CRYPTO_load_u32_le(outlen_bytes);
  uint32_t range = max_output_len - min_output_len + 1;

  const size_t array_len = 1001;
  std::vector<std::vector<uint8_t>> md(array_len);
  std::vector<std::vector<uint8_t>> msg(array_len, std::vector<uint8_t>(16));

  // Zero out |msg| to clear any residual stack garbage before XOF computation
  for (size_t i = 0; i < array_len; i++) {
    OPENSSL_cleanse(msg[i].data(), 16 * sizeof(uint8_t));
  }

  // The following logic conforms to the SHAKE Monte Carlo tests described in
  // https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#name-monte-carlo-tests-for-sha3-
  md[0].resize(args[0].size());
  memcpy(md[0].data(), args[0].data(), args[0].size());

  for (size_t i = 1; i < array_len; i++) {
    md[i].resize(output_len);

    size_t msg_size = std::min<size_t>(md[i - 1].size(), 16);
    memcpy(msg[i].data(), md[i - 1].data(), msg_size);

    EVP_Digest(msg[i].data(), msg[i].size(), md[i].data(), &output_len,
               MDFunc(), NULL);

    uint16_t rightmost_output_bits =
        (md[i][output_len - 2] << 8) | md[i][output_len - 1];
    output_len = min_output_len + (rightmost_output_bits % range);
  }

  // We're sending the new output len back to the ACVP tool as a length-4 byte
  // array representing a little-endian unsigned 32-bit integer as well.
  uint8_t new_outlen_bytes[4];
  CRYPTO_store_u32_le(new_outlen_bytes, output_len);

  return write_reply(
      {Span<const uint8_t>(md[1000]), Span<const uint8_t>(new_outlen_bytes)});
}

// The following logic conforms to the Large Data Tests described in
// https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#name-large-data-tests-for-sha-1-
// Which are the same for SHA-1, SHA2, and SHA3
static unsigned char *BuildLDTMessage(const bssl::Span<const uint8_t> part_msg,
                                      int times) {
  size_t full_msg_size = part_msg.size() * times;
  unsigned char *full_msg = (unsigned char *)malloc(full_msg_size);
  for (int i = 0; i < times; i++) {
    memcpy(full_msg + i * part_msg.size(), part_msg.data(), part_msg.size());
  }

  return full_msg;
}

template <uint8_t *(*OneShotHash)(const uint8_t *, size_t, uint8_t *),
          size_t DigestLength>
static bool HashLDT(const Span<const uint8_t> args[],
                    ReplyCallback write_reply) {
  uint8_t digest[DigestLength];
  int times;
  memcpy(&times, args[1].data(), sizeof(int));

  unsigned char *msg = BuildLDTMessage(args[0], times);

  OneShotHash(msg, args[0].size() * times, digest);
  free(msg);
  return write_reply({Span<const uint8_t>(digest)});
}

template <const EVP_MD *(MDFunc)(), size_t DigestLength>
static bool HashLDTSha3(const Span<const uint8_t> args[],
                        ReplyCallback write_reply) {
  uint8_t digest[DigestLength];
  const EVP_MD *md = MDFunc();
  unsigned int md_out_size = DigestLength;

  int times;
  memcpy(&times, args[1].data(), sizeof(int));

  unsigned char *msg = BuildLDTMessage(args[0], times);

  EVP_Digest(msg, args[0].size() * times, digest, &md_out_size, md, nullptr);
  free(msg);
  return write_reply({Span<const uint8_t>(digest)});
}

static uint32_t GetIterations(const Span<const uint8_t> iterations_bytes) {
  uint32_t iterations;
  if (iterations_bytes.size() != sizeof(iterations)) {
    LOG_ERROR(
        "Expected %u-byte input for number of iterations, but found %u "
        "bytes.\n",
        static_cast<unsigned>(sizeof(iterations)),
        static_cast<unsigned>(iterations_bytes.size()));
    abort();
  }

  memcpy(&iterations, iterations_bytes.data(), sizeof(iterations));
  if (iterations == 0 || iterations == UINT32_MAX) {
    LOG_ERROR("Invalid number of iterations: %x.\n",
              static_cast<unsigned>(iterations));
    abort();
  }

  return iterations;
}

template <int (*SetKey)(const uint8_t *key, unsigned bits, AES_KEY *out),
          void (*Block)(const uint8_t *in, uint8_t *out, const AES_KEY *key)>
static bool AES(const Span<const uint8_t> args[], ReplyCallback write_reply) {
  AES_KEY key;
  if (SetKey(args[0].data(), args[0].size() * 8, &key) != 0) {
    return false;
  }
  if (args[1].size() % AES_BLOCK_SIZE != 0) {
    return false;
  }
  std::vector<uint8_t> result(args[1].begin(), args[1].end());
  const uint32_t iterations = GetIterations(args[2]);

  std::vector<uint8_t> prev_result;
  for (uint32_t j = 0; j < iterations; j++) {
    if (j == iterations - 1) {
      prev_result = result;
    }

    for (size_t i = 0; i < args[1].size(); i += AES_BLOCK_SIZE) {
      Block(result.data() + i, result.data() + i, &key);
    }
  }

  return write_reply(
      {Span<const uint8_t>(result), Span<const uint8_t>(prev_result)});
}

template <bool Encrypt>
static bool AES_XTS(const Span<const uint8_t> args[],
                    ReplyCallback write_reply) {
  const EVP_CIPHER *cipher = EVP_aes_256_xts();

  std::vector<uint8_t> key(args[0].begin(), args[0].end());
  std::vector<uint8_t> plaintext(args[1].begin(), args[1].end());
  std::vector<uint8_t> iv(args[2].begin(), args[2].end());

  bssl::Span<const uint8_t> in = plaintext;
  std::vector<uint8_t> out(plaintext.size());

  bssl::ScopedEVP_CIPHER_CTX ctx;
  int len;

  ctx.Reset();
  if (Encrypt) {
    if (!EVP_EncryptInit_ex(ctx.get(), cipher, nullptr, key.data(),
                            iv.data())) {
      LOG_ERROR("Failed XTS encrypt setup");
      return false;
    }
    if (!EVP_EncryptUpdate(ctx.get(), out.data(), &len, in.data(), in.size())) {
      LOG_ERROR("Failed XTS encrypt");
      return false;
    }
  } else {
    if (!EVP_DecryptInit_ex(ctx.get(), cipher, nullptr, key.data(),
                            iv.data())) {
      LOG_ERROR("Failed XTS decrypt setup");
      return false;
    }
    if (!EVP_DecryptUpdate(ctx.get(), out.data(), &len, in.data(), in.size())) {
      LOG_ERROR("Failed XTS decrypt");
      return false;
    }
  }
  out.resize(len);
  return write_reply({Span<const uint8_t>(out)});
}

template <int (*SetKey)(const uint8_t *key, unsigned bits, AES_KEY *out),
          int Direction>
static bool AES_CBC(const Span<const uint8_t> args[],
                    ReplyCallback write_reply) {
  AES_KEY key;
  if (SetKey(args[0].data(), args[0].size() * 8, &key) != 0) {
    return false;
  }
  if (args[1].size() % AES_BLOCK_SIZE != 0 || args[1].empty() ||
      args[2].size() != AES_BLOCK_SIZE) {
    return false;
  }
  std::vector<uint8_t> input(args[1].begin(), args[1].end());
  std::vector<uint8_t> iv(args[2].begin(), args[2].end());
  const uint32_t iterations = GetIterations(args[3]);

  std::vector<uint8_t> result(input.size());
  std::vector<uint8_t> prev_result, prev_input;

  for (uint32_t j = 0; j < iterations; j++) {
    prev_result = result;
    if (j > 0) {
      if (Direction == AES_ENCRYPT) {
        iv = result;
      } else {
        iv = prev_input;
      }
    }

    // AES_cbc_encrypt will mutate the given IV, but we need it later.
    uint8_t iv_copy[AES_BLOCK_SIZE];
    memcpy(iv_copy, iv.data(), sizeof(iv_copy));
    AES_cbc_encrypt(input.data(), result.data(), input.size(), &key, iv_copy,
                    Direction);

    if (Direction == AES_DECRYPT) {
      prev_input = input;
    }

    if (j == 0) {
      input = iv;
    } else {
      input = prev_result;
    }
  }

  return write_reply(
      {Span<const uint8_t>(result), Span<const uint8_t>(prev_result)});
}

static bool AES_CTR(const Span<const uint8_t> args[],
                    ReplyCallback write_reply) {
  static const uint32_t kOneIteration = 1;
  if (args[3].size() != sizeof(kOneIteration) ||
      memcmp(args[3].data(), &kOneIteration, sizeof(kOneIteration))) {
    LOG_ERROR("Only a single iteration supported with AES-CTR\n");
    return false;
  }

  AES_KEY key;
  if (AES_set_encrypt_key(args[0].data(), args[0].size() * 8, &key) != 0) {
    return false;
  }
  if (args[2].size() != AES_BLOCK_SIZE) {
    return false;
  }
  uint8_t iv[AES_BLOCK_SIZE];
  memcpy(iv, args[2].data(), AES_BLOCK_SIZE);
  if (GetIterations(args[3]) != 1) {
    LOG_ERROR("Multiple iterations of AES-CTR is not supported.\n");
    return false;
  }

  std::vector<uint8_t> out;
  out.resize(args[1].size());
  unsigned num = 0;
  uint8_t ecount_buf[AES_BLOCK_SIZE];
  AES_ctr128_encrypt(args[1].data(), out.data(), args[1].size(), &key, iv,
                     ecount_buf, &num);
  return write_reply({Span<const uint8_t>(out)});
}

static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span,
                        Span<const uint8_t> key, Span<const uint8_t> nonce) {
  uint32_t tag_len_32;
  if (tag_len_span.size() != sizeof(tag_len_32)) {
    LOG_ERROR("Tag size value is %u bytes, not an uint32_t\n",
              static_cast<unsigned>(tag_len_span.size()));
    return false;
  }
  memcpy(&tag_len_32, tag_len_span.data(), sizeof(tag_len_32));

  const EVP_AEAD *aead;
  if (nonce.empty()) {
    // Internally generated IVs
    switch (key.size()) {
      case 16:
        aead = EVP_aead_aes_128_gcm_randnonce();
        break;
      case 32:
        aead = EVP_aead_aes_256_gcm_randnonce();
        break;
      default:
        LOG_ERROR("Bad AES-GCM key length %u\n",
                  static_cast<unsigned>(key.size()));
        return false;
    }
    // The 12-byte nonce is appended to the tag and is generated internally for
    // random nonce function. Thus, the "tag" must be extended by 12 bytes
    // for the purpose of the API.
    if (!EVP_AEAD_CTX_init(ctx, aead, key.data(), key.size(),
                           tag_len_32 + AES_GCM_NONCE_LENGTH, nullptr)) {
      LOG_ERROR("Failed to setup AES-GCM with tag length %u\n",
                static_cast<unsigned>(tag_len_32));
      return false;
    }
  } else {
    // External IVs
    switch (key.size()) {
      case 16:
        aead = EVP_aead_aes_128_gcm_tls12();
        break;
      case 24:
        aead = EVP_aead_aes_192_gcm();
        break;
      case 32:
        aead = EVP_aead_aes_256_gcm_tls12();
        break;
      default:
        LOG_ERROR("Bad AES-GCM key length %u\n",
                  static_cast<unsigned>(key.size()));
        return false;
    }
    if (!EVP_AEAD_CTX_init(ctx, aead, key.data(), key.size(), tag_len_32,
                           nullptr)) {
      LOG_ERROR("Failed to setup AES-GCM with tag length %u\n",
                static_cast<unsigned>(tag_len_32));
      return false;
    }
  }



  return true;
}

static bool AESCCMSetup(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span,
                        Span<const uint8_t> key, Span<const uint8_t> nonce) {
  uint32_t tag_len_32;
  if (tag_len_span.size() != sizeof(tag_len_32)) {
    LOG_ERROR("Tag size value is %u bytes, not an uint32_t\n",
              static_cast<unsigned>(tag_len_span.size()));
    return false;
  }
  memcpy(&tag_len_32, tag_len_span.data(), sizeof(tag_len_32));
  const EVP_AEAD *aead;
  switch (tag_len_32) {
    case 4:
      aead = EVP_aead_aes_128_ccm_bluetooth();
      break;

    case 8:
      aead = EVP_aead_aes_128_ccm_bluetooth_8();
      break;

    default:
      LOG_ERROR(
          "AES-CCM only supports 4- and 8-byte tags, but %u was requested\n",
          static_cast<unsigned>(tag_len_32));
      return false;
  }

  if (key.size() != 16) {
    LOG_ERROR("AES-CCM only supports 128-bit keys, but %u bits were given\n",
              static_cast<unsigned>(key.size() * 8));
    return false;
  }

  if (!EVP_AEAD_CTX_init(ctx, aead, key.data(), key.size(), tag_len_32,
                         nullptr)) {
    LOG_ERROR("Failed to setup AES-CCM with tag length %u\n",
              static_cast<unsigned>(tag_len_32));
    return false;
  }

  return true;
}

template <bool (*SetupFunc)(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span,
                            Span<const uint8_t> key, Span<const uint8_t> nonce)>
static bool AEADSeal(const Span<const uint8_t> args[],
                     ReplyCallback write_reply) {
  Span<const uint8_t> tag_len_span = args[0];
  Span<const uint8_t> key = args[1];
  Span<const uint8_t> plaintext = args[2];
  Span<const uint8_t> nonce = args[3];
  Span<const uint8_t> ad = args[4];

  bssl::ScopedEVP_AEAD_CTX ctx;
  if (!SetupFunc(ctx.get(), tag_len_span, key, nonce)) {
    return false;
  }

  if (EVP_AEAD_MAX_OVERHEAD + plaintext.size() < EVP_AEAD_MAX_OVERHEAD) {
    return false;
  }
  std::vector<uint8_t> out(EVP_AEAD_MAX_OVERHEAD + plaintext.size());

  size_t out_len;
  if (nonce.empty()) {
    // The nonce parameter when using Internal IV generation must be
    // zero-length.
    if (!EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(), nullptr,
                           0, plaintext.data(), plaintext.size(), ad.data(),
                           ad.size())) {
      return false;
    }
  } else {
    // External IV AEAD sealing.
    if (!EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(),
                           nonce.data(), nonce.size(), plaintext.data(),
                           plaintext.size(), ad.data(), ad.size())) {
      return false;
    }
  }

  out.resize(out_len);
  return write_reply({Span<const uint8_t>(out)});
}

template <bool (*SetupFunc)(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span,
                            Span<const uint8_t> key, Span<const uint8_t> nonce)>
static bool AEADOpen(const Span<const uint8_t> args[],
                     ReplyCallback write_reply) {
  Span<const uint8_t> tag_len_span = args[0];
  Span<const uint8_t> key = args[1];
  Span<const uint8_t> ciphertext = args[2];
  Span<const uint8_t> nonce = args[3];
  Span<const uint8_t> ad = args[4];

  bssl::ScopedEVP_AEAD_CTX ctx;
  if (!SetupFunc(ctx.get(), tag_len_span, key, nonce)) {
    return false;
  }

  std::vector<uint8_t> out(ciphertext.size());
  size_t out_len;
  uint8_t success_flag[1] = {0};

  if (!EVP_AEAD_CTX_open(ctx.get(), out.data(), &out_len, out.size(),
                         nonce.data(), nonce.size(), ciphertext.data(),
                         ciphertext.size(), ad.data(), ad.size())) {
    return write_reply(
        {Span<const uint8_t>(success_flag), Span<const uint8_t>()});
  }

  out.resize(out_len);
  success_flag[0] = 1;
  return write_reply(
      {Span<const uint8_t>(success_flag), Span<const uint8_t>(out)});
}

static bool AESPaddedKeyWrapSetup(AES_KEY *out, bool decrypt,
                                  Span<const uint8_t> key) {
  if ((decrypt ? AES_set_decrypt_key : AES_set_encrypt_key)(
          key.data(), key.size() * 8, out) != 0) {
    LOG_ERROR("Invalid AES key length for AES-KW(P): %u\n",
              static_cast<unsigned>(key.size()));
    return false;
  }
  return true;
}

static bool AESKeyWrapSetup(AES_KEY *out, bool decrypt, Span<const uint8_t> key,
                            Span<const uint8_t> input) {
  if (!AESPaddedKeyWrapSetup(out, decrypt, key)) {
    return false;
  }

  if (input.size() % 8) {
    LOG_ERROR("Invalid AES-KW input length: %u\n",
              static_cast<unsigned>(input.size()));
    return false;
  }

  return true;
}

static bool AESKeyWrapSeal(const Span<const uint8_t> args[],
                           ReplyCallback write_reply) {
  Span<const uint8_t> key = args[1];
  Span<const uint8_t> plaintext = args[2];

  AES_KEY aes;
  if (!AESKeyWrapSetup(&aes, /*decrypt=*/false, key, plaintext) ||
      plaintext.size() > INT_MAX - 8) {
    return false;
  }

  std::vector<uint8_t> out(plaintext.size() + 8);
  if (AES_wrap_key(&aes, /*iv=*/nullptr, out.data(), plaintext.data(),
                   plaintext.size()) != static_cast<int>(out.size())) {
    LOG_ERROR("AES-KW failed\n");
    return false;
  }

  return write_reply({Span<const uint8_t>(out)});
}

static bool AESKeyWrapOpen(const Span<const uint8_t> args[],
                           ReplyCallback write_reply) {
  Span<const uint8_t> key = args[1];
  Span<const uint8_t> ciphertext = args[2];

  AES_KEY aes;
  if (!AESKeyWrapSetup(&aes, /*decrypt=*/true, key, ciphertext) ||
      ciphertext.size() < 8 || ciphertext.size() > INT_MAX) {
    return false;
  }

  std::vector<uint8_t> out(ciphertext.size() - 8);
  uint8_t success_flag[1] = {0};
  if (AES_unwrap_key(&aes, /*iv=*/nullptr, out.data(), ciphertext.data(),
                     ciphertext.size()) != static_cast<int>(out.size())) {
    return write_reply(
        {Span<const uint8_t>(success_flag), Span<const uint8_t>()});
  }

  success_flag[0] = 1;
  return write_reply(
      {Span<const uint8_t>(success_flag), Span<const uint8_t>(out)});
}

static bool AESPaddedKeyWrapSeal(const Span<const uint8_t> args[],
                                 ReplyCallback write_reply) {
  Span<const uint8_t> key = args[1];
  Span<const uint8_t> plaintext = args[2];

  AES_KEY aes;
  if (!AESPaddedKeyWrapSetup(&aes, /*decrypt=*/false, key) ||
      plaintext.size() + 15 < 15) {
    return false;
  }

  std::vector<uint8_t> out(plaintext.size() + 15);
  size_t out_len;
  if (!AES_wrap_key_padded(&aes, out.data(), &out_len, out.size(),
                           plaintext.data(), plaintext.size())) {
    LOG_ERROR("AES-KWP failed\n");
    return false;
  }

  out.resize(out_len);
  return write_reply({Span<const uint8_t>(out)});
}

static bool AESPaddedKeyWrapOpen(const Span<const uint8_t> args[],
                                 ReplyCallback write_reply) {
  Span<const uint8_t> key = args[1];
  Span<const uint8_t> ciphertext = args[2];

  AES_KEY aes;
  if (!AESPaddedKeyWrapSetup(&aes, /*decrypt=*/true, key) ||
      ciphertext.size() % 8) {
    return false;
  }

  std::vector<uint8_t> out(ciphertext.size());
  size_t out_len;
  uint8_t success_flag[1] = {0};
  if (!AES_unwrap_key_padded(&aes, out.data(), &out_len, out.size(),
                             ciphertext.data(), ciphertext.size())) {
    return write_reply(
        {Span<const uint8_t>(success_flag), Span<const uint8_t>()});
  }

  success_flag[0] = 1;
  out.resize(out_len);
  return write_reply(
      {Span<const uint8_t>(success_flag), Span<const uint8_t>(out)});
}

template <bool Encrypt>
static bool TDES(const Span<const uint8_t> args[], ReplyCallback write_reply) {
  const EVP_CIPHER *cipher = EVP_des_ede3();

  if (args[0].size() != 24) {
    LOG_ERROR("Bad key length %u for 3DES.\n",
              static_cast<unsigned>(args[0].size()));
    return false;
  }
  bssl::ScopedEVP_CIPHER_CTX ctx;
  if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].data(), nullptr,
                         Encrypt ? 1 : 0) ||
      !EVP_CIPHER_CTX_set_padding(ctx.get(), 0)) {
    return false;
  }

  if (args[1].size() % 8) {
    LOG_ERROR("Bad input length %u for 3DES.\n",
              static_cast<unsigned>(args[1].size()));
    return false;
  }
  std::vector<uint8_t> result(args[1].begin(), args[1].end());

  const uint32_t iterations = GetIterations(args[2]);
  std::vector<uint8_t> prev_result, prev_prev_result;

  for (uint32_t j = 0; j < iterations; j++) {
    if (j == iterations - 1) {
      prev_result = result;
    } else if (iterations >= 2 && j == iterations - 2) {
      prev_prev_result = result;
    }

    int out_len;
    if (!EVP_CipherUpdate(ctx.get(), result.data(), &out_len, result.data(),
                          result.size()) ||
        out_len != static_cast<int>(result.size())) {
      return false;
    }
  }

  return write_reply({Span<const uint8_t>(result),
                      Span<const uint8_t>(prev_result),
                      Span<const uint8_t>(prev_prev_result)});
}

template <bool Encrypt>
static bool TDES_CBC(const Span<const uint8_t> args[],
                     ReplyCallback write_reply) {
  const EVP_CIPHER *cipher = EVP_des_ede3_cbc();

  if (args[0].size() != 24) {
    LOG_ERROR("Bad key length %u for 3DES.\n",
              static_cast<unsigned>(args[0].size()));
    return false;
  }

  if (args[1].size() % 8 || args[1].size() == 0) {
    LOG_ERROR("Bad input length %u for 3DES.\n",
              static_cast<unsigned>(args[1].size()));
    return false;
  }
  std::vector<uint8_t> input(args[1].begin(), args[1].end());

  if (args[2].size() != EVP_CIPHER_iv_length(cipher)) {
    LOG_ERROR("Bad IV length %u for 3DES.\n",
              static_cast<unsigned>(args[2].size()));
    return false;
  }
  std::vector<uint8_t> iv(args[2].begin(), args[2].end());
  const uint32_t iterations = GetIterations(args[3]);

  std::vector<uint8_t> result(input.size());
  std::vector<uint8_t> prev_result, prev_prev_result;
  bssl::ScopedEVP_CIPHER_CTX ctx;
  if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].data(), iv.data(),
                         Encrypt ? 1 : 0) ||
      !EVP_CIPHER_CTX_set_padding(ctx.get(), 0)) {
    return false;
  }

  for (uint32_t j = 0; j < iterations; j++) {
    prev_prev_result = prev_result;
    prev_result = result;

    int out_len, out_len2;
    if (!EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, nullptr, iv.data(),
                           -1) ||
        !EVP_CipherUpdate(ctx.get(), result.data(), &out_len, input.data(),
                          input.size()) ||
        !EVP_CipherFinal_ex(ctx.get(), result.data() + out_len, &out_len2) ||
        (out_len + out_len2) != static_cast<int>(result.size())) {
      return false;
    }

    if (Encrypt) {
      if (j == 0) {
        input = iv;
      } else {
        input = prev_result;
      }
      iv = result;
    } else {
      iv = input;
      input = result;
    }
  }

  return write_reply({Span<const uint8_t>(result),
                      Span<const uint8_t>(prev_result),
                      Span<const uint8_t>(prev_prev_result)});
}

template <const EVP_MD *HashFunc()>
static bool HMAC(const Span<const uint8_t> args[], ReplyCallback write_reply) {
  const EVP_MD *const md = HashFunc();

  // Approved HMAC computation
  uint8_t digest[EVP_MAX_MD_SIZE];
  unsigned digest_len;
  if (::HMAC(md, args[1].data(), args[1].size(), args[0].data(), args[0].size(),
             digest, &digest_len) == nullptr) {
    return false;
  }

  // HMAC computation with precomputed keys
  // The purpose of this call is to test |HMAC_set_precomputed_key_export| and
  // |HMAC_get_precomputed_key|, which are called by |HMAC_with_precompute|.
  uint8_t digest_with_precompute[EVP_MAX_MD_SIZE];
  unsigned digest_with_precompute_len;
  if (::HMAC_with_precompute(md, args[1].data(), args[1].size(), args[0].data(),
                             args[0].size(), digest_with_precompute,
                             &digest_with_precompute_len) == nullptr) {
    return false;
  }

  // The two HMAC computations must yield exactly the same results
  if (digest_len != digest_with_precompute_len ||
      memcmp(digest, digest_with_precompute, digest_len) != 0) {
    return false;
  }

  return write_reply({Span<const uint8_t>(digest, digest_len)});
}

template <bool WithReseed>
static bool DRBG(const Span<const uint8_t> args[], ReplyCallback write_reply) {
  const auto out_len_bytes = args[0];
  const auto entropy = args[1];
  const auto personalisation = args[2];

  Span<const uint8_t> reseed_additional_data, reseed_entropy, additional_data1,
      additional_data2, nonce;
  if (!WithReseed) {
    additional_data1 = args[3];
    additional_data2 = args[4];
    nonce = args[5];
  } else {
    reseed_additional_data = args[3];
    reseed_entropy = args[4];
    additional_data1 = args[5];
    additional_data2 = args[6];
    nonce = args[7];
  }

  uint32_t out_len;
  if (out_len_bytes.size() != sizeof(out_len) ||
      entropy.size() != CTR_DRBG_ENTROPY_LEN ||
      (!reseed_entropy.empty() &&
       reseed_entropy.size() != CTR_DRBG_ENTROPY_LEN) ||
      // nonces are not supported
      nonce.size() != 0) {
    return false;
  }
  memcpy(&out_len, out_len_bytes.data(), sizeof(out_len));
  if (out_len > (1 << 24)) {
    return false;
  }
  std::vector<uint8_t> out(out_len);

  CTR_DRBG_STATE drbg;
  if (!CTR_DRBG_init(&drbg, entropy.data(), personalisation.data(),
                     personalisation.size()) ||
      (!reseed_entropy.empty() &&
       !CTR_DRBG_reseed(&drbg, reseed_entropy.data(),
                        reseed_additional_data.data(),
                        reseed_additional_data.size())) ||
      !CTR_DRBG_generate(&drbg, out.data(), out_len, additional_data1.data(),
                         additional_data1.size()) ||
      !CTR_DRBG_generate(&drbg, out.data(), out_len, additional_data2.data(),
                         additional_data2.size())) {
    return false;
  }

  return write_reply({Span<const uint8_t>(out)});
}

static bool StringEq(Span<const uint8_t> a, const char *b) {
  const size_t len = strlen(b);
  return a.size() == len && memcmp(a.data(), b, len) == 0;
}

static bssl::UniquePtr<EC_KEY> ECKeyFromName(Span<const uint8_t> name) {
  int nid;
  if (StringEq(name, "P-224")) {
    nid = NID_secp224r1;
  } else if (StringEq(name, "P-256")) {
    nid = NID_X9_62_prime256v1;
  } else if (StringEq(name, "P-384")) {
    nid = NID_secp384r1;
  } else if (StringEq(name, "P-521")) {
    nid = NID_secp521r1;
  } else {
    return nullptr;
  }

  return bssl::UniquePtr<EC_KEY>(EC_KEY_new_by_curve_name(nid));
}

static std::vector<uint8_t> BIGNUMBytes(const BIGNUM *bn) {
  const size_t len = BN_num_bytes(bn);
  std::vector<uint8_t> ret(len);
  BN_bn2bin(bn, ret.data());
  return ret;
}

static std::pair<std::vector<uint8_t>, std::vector<uint8_t>> GetPublicKeyBytes(
    const EC_KEY *key) {
  bssl::UniquePtr<BIGNUM> x(BN_new());
  bssl::UniquePtr<BIGNUM> y(BN_new());
  if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(key),
                                           EC_KEY_get0_public_key(key), x.get(),
                                           y.get(), /*ctx=*/nullptr)) {
    abort();
  }

  std::vector<uint8_t> x_bytes = BIGNUMBytes(x.get());
  std::vector<uint8_t> y_bytes = BIGNUMBytes(y.get());

  return std::make_pair(std::move(x_bytes), std::move(y_bytes));
}

static bool ECDSAKeyGen(const Span<const uint8_t> args[],
                        ReplyCallback write_reply) {
  bssl::UniquePtr<EC_KEY> key = ECKeyFromName(args[0]);
  if (!key || !EC_KEY_generate_key_fips(key.get())) {
    return false;
  }

  const auto pub_key = GetPublicKeyBytes(key.get());
  std::vector<uint8_t> d_bytes =
      BIGNUMBytes(EC_KEY_get0_private_key(key.get()));

  return write_reply({Span<const uint8_t>(d_bytes),
                      Span<const uint8_t>(pub_key.first),
                      Span<const uint8_t>(pub_key.second)});
}

static bssl::UniquePtr<BIGNUM> BytesToBIGNUM(Span<const uint8_t> bytes) {
  bssl::UniquePtr<BIGNUM> bn(BN_new());
  BN_bin2bn(bytes.data(), bytes.size(), bn.get());
  return bn;
}

static bool ECDSAKeyVer(const Span<const uint8_t> args[],
                        ReplyCallback write_reply) {
  bssl::UniquePtr<EC_KEY> key = ECKeyFromName(args[0]);
  if (!key) {
    return false;
  }

  bssl::UniquePtr<BIGNUM> x(BytesToBIGNUM(args[1]));
  bssl::UniquePtr<BIGNUM> y(BytesToBIGNUM(args[2]));

  bssl::UniquePtr<EC_POINT> point(EC_POINT_new(EC_KEY_get0_group(key.get())));
  uint8_t reply[1];
  if (!EC_POINT_set_affine_coordinates_GFp(EC_KEY_get0_group(key.get()),
                                           point.get(), x.get(), y.get(),
                                           /*ctx=*/nullptr) ||
      !EC_KEY_set_public_key(key.get(), point.get()) ||
      !EC_KEY_check_fips(key.get())) {
    reply[0] = 0;
  } else {
    reply[0] = 1;
  }

  return write_reply({Span<const uint8_t>(reply)});
}

static const EVP_MD *HashFromName(Span<const uint8_t> name) {
  if (StringEq(name, "SHA-1")) {
    return EVP_sha1();
  } else if (StringEq(name, "SHA2-224")) {
    return EVP_sha224();
  } else if (StringEq(name, "SHA2-256")) {
    return EVP_sha256();
  } else if (StringEq(name, "SHA2-384")) {
    return EVP_sha384();
  } else if (StringEq(name, "SHA2-512")) {
    return EVP_sha512();
  } else if (StringEq(name, "SHA2-512/224")) {
    return EVP_sha512_224();
  } else if (StringEq(name, "SHA2-512/256")) {
    return EVP_sha512_256();
  } else if (StringEq(name, "SHA3-224")) {
    return EVP_sha3_224();
  } else if (StringEq(name, "SHA3-256")) {
    return EVP_sha3_256();
  } else if (StringEq(name, "SHA3-384")) {
    return EVP_sha3_384();
  } else if (StringEq(name, "SHA3-512")) {
    return EVP_sha3_512();
  } else if (StringEq(name, "SHAKE-128")) {
    return EVP_shake128();
  } else if (StringEq(name, "SHAKE-256")) {
    return EVP_shake256();
  } else {
    return nullptr;
  }
}

static bool ECDSASigGen(const Span<const uint8_t> args[],
                        ReplyCallback write_reply) {
  bssl::UniquePtr<EC_KEY> key = ECKeyFromName(args[0]);
  bssl::UniquePtr<BIGNUM> d = BytesToBIGNUM(args[1]);
  const EVP_MD *hash = HashFromName(args[2]);
  auto msg = args[3];
  if (!key || !hash || !EC_KEY_set_private_key(key.get(), d.get())) {
    return false;
  }
  bssl::ScopedEVP_MD_CTX ctx;
  EVP_PKEY_CTX *pctx;
  bssl::UniquePtr<EVP_PKEY> evp_pkey(EVP_PKEY_new());
  if (!evp_pkey || !EVP_PKEY_set1_EC_KEY(evp_pkey.get(), key.get())) {
    return false;
  }
  std::vector<uint8_t> sig_der;
  size_t len;
  if (!EVP_DigestSignInit(ctx.get(), &pctx, hash, nullptr, evp_pkey.get()) ||
      !EVP_DigestSign(ctx.get(), nullptr, &len, msg.data(), msg.size())) {
    return false;
  }
  sig_der.resize(len);
  if (!EVP_DigestSign(ctx.get(), sig_der.data(), &len, msg.data(),
                      msg.size())) {
    return false;
  }
  bssl::UniquePtr<ECDSA_SIG> sig(ECDSA_SIG_from_bytes(sig_der.data(), len));
  if (!sig) {
    return false;
  }

  std::vector<uint8_t> r_bytes(BIGNUMBytes(sig->r));
  std::vector<uint8_t> s_bytes(BIGNUMBytes(sig->s));

  return write_reply(
      {Span<const uint8_t>(r_bytes), Span<const uint8_t>(s_bytes)});
}

static bool ECDSASigVer(const Span<const uint8_t> args[],
                        ReplyCallback write_reply) {
  bssl::UniquePtr<EC_KEY> key = ECKeyFromName(args[0]);
  const EVP_MD *hash = HashFromName(args[1]);
  auto msg = args[2];
  bssl::UniquePtr<BIGNUM> x(BytesToBIGNUM(args[3]));
  bssl::UniquePtr<BIGNUM> y(BytesToBIGNUM(args[4]));
  bssl::UniquePtr<BIGNUM> r(BytesToBIGNUM(args[5]));
  bssl::UniquePtr<BIGNUM> s(BytesToBIGNUM(args[6]));
  ECDSA_SIG sig;
  sig.r = r.get();
  sig.s = s.get();
  uint8_t *der;
  size_t der_len;
  if (!key || !hash || !ECDSA_SIG_to_bytes(&der, &der_len, &sig)) {
    return false;
  }
  // Let |delete_der| manage the release of |der|.
  bssl::UniquePtr<uint8_t> delete_der(der);
  bssl::UniquePtr<EC_POINT> point(EC_POINT_new(EC_KEY_get0_group(key.get())));
  if (!EC_POINT_set_affine_coordinates_GFp(EC_KEY_get0_group(key.get()),
                                           point.get(), x.get(), y.get(),
                                           /*ctx=*/nullptr) ||
      !EC_KEY_set_public_key(key.get(), point.get()) ||
      !EC_KEY_check_fips(key.get())) {
    return false;
  }
  bssl::ScopedEVP_MD_CTX ctx;
  EVP_PKEY_CTX *pctx;
  bssl::UniquePtr<EVP_PKEY> evp_pkey(EVP_PKEY_new());
  if (!evp_pkey || !EVP_PKEY_set1_EC_KEY(evp_pkey.get(), key.get())) {
    return false;
  }
  uint8_t reply[1];
  if (!EVP_DigestVerifyInit(ctx.get(), &pctx, hash, nullptr, evp_pkey.get()) ||
      !EVP_DigestVerify(ctx.get(), der, der_len, msg.data(), msg.size())) {
    reply[0] = 0;
  } else {
    reply[0] = 1;
  }
  ERR_clear_error();

  return write_reply({Span<const uint8_t>(reply)});
}

static bool CMAC_AES(const Span<const uint8_t> args[],
                     ReplyCallback write_reply) {
  uint8_t mac[16];
  if (!AES_CMAC(mac, args[1].data(), args[1].size(), args[2].data(),
                args[2].size())) {
    return false;
  }

  uint32_t mac_len;
  if (args[0].size() != sizeof(mac_len)) {
    return false;
  }
  memcpy(&mac_len, args[0].data(), sizeof(mac_len));
  if (mac_len > sizeof(mac)) {
    return false;
  }

  return write_reply({Span<const uint8_t>(mac, mac_len)});
}

static bool CMAC_AESVerify(const Span<const uint8_t> args[],
                           ReplyCallback write_reply) {
  // This function is just for testing since libcrypto doesn't do the
  // verification itself. The regcap doesn't advertise "ver" support.
  uint8_t mac[16];
  if (!AES_CMAC(mac, args[0].data(), args[0].size(), args[1].data(),
                args[1].size()) ||
      args[2].size() > sizeof(mac)) {
    return false;
  }

  const uint8_t ok = (OPENSSL_memcmp(mac, args[2].data(), args[2].size()) == 0);
  return write_reply({Span<const uint8_t>(&ok, sizeof(ok))});
}

static std::map<unsigned, bssl::UniquePtr<EVP_PKEY>> &CachedRSAEVPKeys() {
  static std::map<unsigned, bssl::UniquePtr<EVP_PKEY>> keys;
  return keys;
}

static EVP_PKEY *AddRSAKeyToCache(bssl::UniquePtr<RSA> &rsa, unsigned bits) {
  bssl::UniquePtr<EVP_PKEY> evp_pkey(EVP_PKEY_new());
  if (!evp_pkey || !EVP_PKEY_set1_RSA(evp_pkey.get(), rsa.get())) {
    return nullptr;
  }

  EVP_PKEY *const ret = evp_pkey.get();
  CachedRSAEVPKeys().emplace(static_cast<unsigned>(bits), std::move(evp_pkey));
  return ret;
}

static EVP_PKEY *GetRSAKey(unsigned bits) {
  auto it = CachedRSAEVPKeys().find(bits);
  if (it != CachedRSAEVPKeys().end()) {
    return it->second.get();
  }

  bssl::UniquePtr<RSA> rsa(RSA_new());
  if (!RSA_generate_key_fips(rsa.get(), bits, nullptr)) {
    abort();
  }

  return AddRSAKeyToCache(rsa, bits);
}

static bool RSAKeyGen(const Span<const uint8_t> args[],
                      ReplyCallback write_reply) {
  uint32_t bits;
  if (args[0].size() != sizeof(bits)) {
    return false;
  }
  memcpy(&bits, args[0].data(), sizeof(bits));

  bssl::UniquePtr<RSA> rsa(RSA_new());
  if (!RSA_generate_key_fips(rsa.get(), bits, nullptr)) {
    LOG_ERROR("RSA_generate_key_fips failed for modulus length %u.\n", bits);
    return false;
  }

  const BIGNUM *n, *e, *d, *p, *q;
  RSA_get0_key(rsa.get(), &n, &e, &d);
  RSA_get0_factors(rsa.get(), &p, &q);

  if (!write_reply({BIGNUMBytes(e), BIGNUMBytes(p), BIGNUMBytes(q),
                    BIGNUMBytes(n), BIGNUMBytes(d)})) {
    return false;
  }

  if (AddRSAKeyToCache(rsa, bits) == nullptr) {
    return false;
  }
  return true;
}

template <const EVP_MD *(MDFunc)(), bool UsePSS>
static bool RSASigGen(const Span<const uint8_t> args[],
                      ReplyCallback write_reply) {
  uint32_t bits;
  if (args[0].size() != sizeof(bits)) {
    return false;
  }
  memcpy(&bits, args[0].data(), sizeof(bits));
  const Span<const uint8_t> msg = args[1];
  EVP_PKEY *const evp_pkey = GetRSAKey(bits);
  if (evp_pkey == nullptr) {
    return false;
  }
  RSA *const rsa = EVP_PKEY_get0_RSA(evp_pkey);
  if (rsa == nullptr) {
    return false;
  }
  const EVP_MD *const md = MDFunc();
  std::vector<uint8_t> sig;
  size_t sig_len;
  bssl::ScopedEVP_MD_CTX ctx;
  EVP_PKEY_CTX *pctx;
  int padding = UsePSS ? RSA_PKCS1_PSS_PADDING : RSA_PKCS1_PADDING;
  if (!EVP_DigestSignInit(ctx.get(), &pctx, md, nullptr, evp_pkey) ||
      !EVP_PKEY_CTX_set_rsa_padding(pctx, padding) ||
      (UsePSS &&
       !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, RSA_PSS_SALTLEN_DIGEST)) ||
      !EVP_DigestSign(ctx.get(), nullptr, &sig_len, msg.data(), msg.size())) {
    return false;
  }
  sig.resize(sig_len);
  if (!EVP_DigestSign(ctx.get(), sig.data(), &sig_len, msg.data(),
                      msg.size())) {
    return false;
  }

  return write_reply(
      {BIGNUMBytes(RSA_get0_n(rsa)), BIGNUMBytes(RSA_get0_e(rsa)), sig});
}

template <const EVP_MD *(MDFunc)(), bool UsePSS>
static bool RSASigVer(const Span<const uint8_t> args[],
                      ReplyCallback write_reply) {
  const Span<const uint8_t> n_bytes = args[0];
  const Span<const uint8_t> e_bytes = args[1];
  const Span<const uint8_t> msg = args[2];
  const Span<const uint8_t> sig = args[3];

  BIGNUM *n = BN_new();
  BIGNUM *e = BN_new();
  bssl::UniquePtr<RSA> key(RSA_new());
  if (!BN_bin2bn(n_bytes.data(), n_bytes.size(), n) ||
      !BN_bin2bn(e_bytes.data(), e_bytes.size(), e) ||
      !RSA_set0_key(key.get(), n, e, /*d=*/nullptr)) {
    return false;
  }

  const EVP_MD *const md = MDFunc();
  bssl::ScopedEVP_MD_CTX ctx;
  EVP_PKEY_CTX *pctx;
  bssl::UniquePtr<EVP_PKEY> evp_pkey(EVP_PKEY_new());
  if (!evp_pkey || !EVP_PKEY_set1_RSA(evp_pkey.get(), key.get())) {
    return false;
  }
  uint8_t ok;
  int padding = UsePSS ? RSA_PKCS1_PSS_PADDING : RSA_PKCS1_PADDING;
  if (!EVP_DigestVerifyInit(ctx.get(), &pctx, md, nullptr, evp_pkey.get()) ||
      !EVP_PKEY_CTX_set_rsa_padding(pctx, padding) ||
      (UsePSS &&
       !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, RSA_PSS_SALTLEN_DIGEST)) ||
      !EVP_DigestVerify(ctx.get(), sig.data(), sig.size(), msg.data(),
                        msg.size())) {
    ok = 0;
  } else {
    ok = 1;
  }
  ERR_clear_error();

  return write_reply({Span<const uint8_t>(&ok, 1)});
}

template <const EVP_MD *(MDFunc)()>
static bool TLSKDF(const Span<const uint8_t> args[],
                   ReplyCallback write_reply) {
  const Span<const uint8_t> out_len_bytes = args[0];
  const Span<const uint8_t> secret = args[1];
  const Span<const uint8_t> label = args[2];
  const Span<const uint8_t> seed1 = args[3];
  const Span<const uint8_t> seed2 = args[4];
  const EVP_MD *md = MDFunc();

  uint32_t out_len;
  if (out_len_bytes.size() != sizeof(out_len)) {
    return 0;
  }
  memcpy(&out_len, out_len_bytes.data(), sizeof(out_len));

  std::vector<uint8_t> out(static_cast<size_t>(out_len));
  if (!CRYPTO_tls1_prf(md, out.data(), out.size(), secret.data(), secret.size(),
                       reinterpret_cast<const char *>(label.data()),
                       label.size(), seed1.data(), seed1.size(), seed2.data(),
                       seed2.size())) {
    return 0;
  }

  return write_reply({out});
}

template <int Nid>
static bool ECDH(const Span<const uint8_t> args[], ReplyCallback write_reply) {
  bssl::UniquePtr<BIGNUM> their_x(BytesToBIGNUM(args[0]));
  bssl::UniquePtr<BIGNUM> their_y(BytesToBIGNUM(args[1]));
  const Span<const uint8_t> private_key = args[2];

  bssl::UniquePtr<EC_KEY> ec_key(EC_KEY_new_by_curve_name(Nid));
  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());

  const EC_GROUP *const group = EC_KEY_get0_group(ec_key.get());
  bssl::UniquePtr<EC_POINT> their_point(EC_POINT_new(group));
  if (!EC_POINT_set_affine_coordinates_GFp(
          group, their_point.get(), their_x.get(), their_y.get(), ctx.get())) {
    LOG_ERROR("Invalid peer point for ECDH.\n");
    return false;
  }

  if (!private_key.empty()) {
    bssl::UniquePtr<BIGNUM> our_k(BytesToBIGNUM(private_key));
    if (!EC_KEY_set_private_key(ec_key.get(), our_k.get())) {
      LOG_ERROR("EC_KEY_set_private_key failed.\n");
      return false;
    }

    bssl::UniquePtr<EC_POINT> our_pub(EC_POINT_new(group));
    if (!EC_POINT_mul(group, our_pub.get(), our_k.get(), nullptr, nullptr,
                      ctx.get()) ||
        !EC_KEY_set_public_key(ec_key.get(), our_pub.get())) {
      LOG_ERROR("Calculating public key failed.\n");
      return false;
    }
  } else if (!EC_KEY_generate_key_fips(ec_key.get())) {
    LOG_ERROR("EC_KEY_generate_key_fips failed.\n");
    return false;
  }

  // The output buffer is one larger than |EC_MAX_BYTES| so that truncation
  // can be detected.
  std::vector<uint8_t> output(EC_MAX_BYTES + 1);
  const int out_len =
      ECDH_compute_key(output.data(), output.size(), their_point.get(),
                       ec_key.get(), /*kdf=*/nullptr);
  if (out_len < 0) {
    LOG_ERROR("ECDH_compute_key failed.\n");
    return false;
  } else if (static_cast<size_t>(out_len) == output.size()) {
    LOG_ERROR("ECDH_compute_key output may have been truncated.\n");
    return false;
  }
  output.resize(static_cast<size_t>(out_len));

  const EC_POINT *pub = EC_KEY_get0_public_key(ec_key.get());
  bssl::UniquePtr<BIGNUM> x(BN_new());
  bssl::UniquePtr<BIGNUM> y(BN_new());
  if (!EC_POINT_get_affine_coordinates_GFp(group, pub, x.get(), y.get(),
                                           ctx.get())) {
    LOG_ERROR("EC_POINT_get_affine_coordinates_GFp failed.\n");
    return false;
  }

  return write_reply({BIGNUMBytes(x.get()), BIGNUMBytes(y.get()), output});
}

static bool FFDH(const Span<const uint8_t> args[], ReplyCallback write_reply) {
  bssl::UniquePtr<BIGNUM> p(BytesToBIGNUM(args[0]));
  bssl::UniquePtr<BIGNUM> q(BytesToBIGNUM(args[1]));
  bssl::UniquePtr<BIGNUM> g(BytesToBIGNUM(args[2]));
  bssl::UniquePtr<BIGNUM> their_pub(BytesToBIGNUM(args[3]));
  const Span<const uint8_t> private_key_span = args[4];
  const Span<const uint8_t> public_key_span = args[5];

  bssl::UniquePtr<DH> dh(DH_new());
  if (!DH_set0_pqg(dh.get(), p.get(), q.get(), g.get())) {
    LOG_ERROR("DH_set0_pqg failed.\n");
    return 0;
  }

  // DH_set0_pqg took ownership of these values.
  p.release();
  q.release();
  g.release();

  if (!private_key_span.empty()) {
    bssl::UniquePtr<BIGNUM> private_key(BytesToBIGNUM(private_key_span));
    bssl::UniquePtr<BIGNUM> public_key(BytesToBIGNUM(public_key_span));

    if (!DH_set0_key(dh.get(), public_key.get(), private_key.get())) {
      LOG_ERROR("DH_set0_key failed.\n");
      return 0;
    }

    // DH_set0_key took ownership of these values.
    public_key.release();
    private_key.release();
  } else if (!DH_generate_key(dh.get())) {
    LOG_ERROR("DH_generate_key failed.\n");
    return false;
  }

  std::vector<uint8_t> z(DH_size(dh.get()));
  if (DH_compute_key_padded(z.data(), their_pub.get(), dh.get()) !=
      static_cast<int>(z.size())) {
    LOG_ERROR("DH_compute_key_hashed failed.\n");
    return false;
  }

  return write_reply({BIGNUMBytes(DH_get0_pub_key(dh.get())), z});
}

static bool PBKDF(const Span<const uint8_t> args[], ReplyCallback write_reply) {
  const Span<const uint8_t> password = args[0];
  const Span<const uint8_t> salt = args[1];
  const Span<const uint8_t> iterations = args[2];
  const Span<const uint8_t> hmac_name = args[3];
  const Span<const uint8_t> key_len = args[4];

  // Read bit data into useable variables
  unsigned int iterations_uint;
  memcpy(&iterations_uint, iterations.data(), sizeof(iterations_uint));
  unsigned int key_len_uint;
  memcpy(&key_len_uint, key_len.data(), sizeof(key_len_uint));

  key_len_uint = key_len_uint / 8;

  // Get the SHA algorithm we want from the name provided to us
  const EVP_MD *hmac_alg = HashFromName(hmac_name);

  std::vector<uint8_t> out_key(key_len_uint);
  if (!PKCS5_PBKDF2_HMAC(reinterpret_cast<const char *>(password.data()),
                         password.size(), salt.data(), salt.size(),
                         iterations_uint, hmac_alg, key_len_uint,
                         out_key.data())) {
    return false;
  }

  return write_reply({Span<const uint8_t>(out_key)});
}

template <const EVP_MD *(MDFunc)()>
static bool HKDF(const Span<const uint8_t> args[], ReplyCallback write_reply) {
  const Span<const uint8_t> key = args[0];
  const Span<const uint8_t> salt = args[1];
  const Span<const uint8_t> info = args[2];
  const Span<const uint8_t> out_bytes = args[3];
  const EVP_MD *md = MDFunc();

  unsigned int out_bytes_uint;
  memcpy(&out_bytes_uint, out_bytes.data(), sizeof(out_bytes_uint));


  std::vector<uint8_t> out_key(out_bytes_uint);
  if (!::HKDF(out_key.data(), out_bytes_uint, md, key.data(), key.size(),
              salt.data(), salt.size(), info.data(), info.size())) {
    return false;
  }

  return write_reply({Span<const uint8_t>(out_key)});
}

template <const EVP_MD *(MDFunc)()>
static bool SSKDF_DIGEST(const Span<const uint8_t> args[],
                         ReplyCallback write_reply) {
  const Span<const uint8_t> key = args[0];
  const Span<const uint8_t> info = args[1];
  const Span<const uint8_t> out_bytes = args[2];
  const EVP_MD *md = MDFunc();

  uint32_t out_bytes_uint32;
  memcpy(&out_bytes_uint32, out_bytes.data(), sizeof(out_bytes_uint32));

  std::vector<uint8_t> out_key(out_bytes_uint32);
  if (!::SSKDF_digest(out_key.data(), out_bytes_uint32, md, key.data(),
                      key.size(), info.data(), info.size())) {
    return false;
  }

  return write_reply({Span<const uint8_t>(out_key)});
}

template <const EVP_MD *(MDFunc)()>
static bool SSKDF_HMAC(const Span<const uint8_t> args[],
                       ReplyCallback write_reply) {
  const Span<const uint8_t> key = args[0];
  const Span<const uint8_t> salt = args[1];
  const Span<const uint8_t> info = args[2];
  const Span<const uint8_t> out_bytes = args[3];
  const EVP_MD *md = MDFunc();

  uint32_t out_bytes_uint32;
  memcpy(&out_bytes_uint32, out_bytes.data(), sizeof(out_bytes_uint32));

  std::vector<uint8_t> out_key(out_bytes_uint32);
  if (!::SSKDF_hmac(out_key.data(), out_bytes_uint32, md, key.data(),
                    key.size(), info.data(), info.size(), salt.data(),
                    salt.size())) {
    return false;
  }

  return write_reply({Span<const uint8_t>(out_key)});
}

template <const EVP_MD *(MDFunc)(), char type_val>
static bool SSHKDF(const Span<const uint8_t> args[],
                   ReplyCallback write_reply) {
  const Span<const uint8_t> key = args[0];
  const Span<const uint8_t> hash = args[1];
  const Span<const uint8_t> session_id = args[2];
  const Span<const uint8_t> out_bytes = args[3];
  const EVP_MD *md = MDFunc();


  unsigned int out_bytes_uint;
  memcpy(&out_bytes_uint, out_bytes.data(), sizeof(out_bytes_uint));

  std::vector<uint8_t> out(out_bytes_uint);
  if (!::SSHKDF(md, key.data(), key.size(), hash.data(), hash.size(),
                session_id.data(), session_id.size(), type_val, out.data(),
                out_bytes_uint)) {
    return false;
  }

  return write_reply({Span<const uint8_t>(out)});
}

template <const EVP_MD *(MDFunc)()>
static bool HKDF_expand(const Span<const uint8_t> args[],
                        ReplyCallback write_reply) {
  const Span<const uint8_t> out_bytes = args[0];
  const Span<const uint8_t> key_in = args[1];
  const Span<const uint8_t> fixed_data = args[2];
  const EVP_MD *md = MDFunc();

  unsigned int out_bytes_uint;
  memcpy(&out_bytes_uint, out_bytes.data(), sizeof(out_bytes_uint));

  std::vector<uint8_t> out(out_bytes_uint);
  if (!::HKDF_expand(out.data(), out_bytes_uint, md, key_in.data(),
                     key_in.size(), fixed_data.data(), fixed_data.size())) {
    return false;
  }

  return write_reply({Span<const uint8_t>(out)});
}

template <const EVP_MD *(MDFunc)()>
static bool KBKDF_CTR_HMAC(const Span<const uint8_t> args[],
                           ReplyCallback write_reply) {
  const Span<const uint8_t> out_bytes = args[0];
  const Span<const uint8_t> key_in = args[1];
  const Span<const uint8_t> fixed_data = args[2];
  const EVP_MD *md = MDFunc();

  unsigned int out_bytes_uint;
  memcpy(&out_bytes_uint, out_bytes.data(), sizeof(out_bytes_uint));

  std::vector<uint8_t> out(out_bytes_uint);
  if (!::KBKDF_ctr_hmac(out.data(), out_bytes_uint, md, key_in.data(),
                        key_in.size(), fixed_data.data(), fixed_data.size())) {
    return false;
  }

  return write_reply({Span<const uint8_t>(out)});
}

template <int nid>
static bool ML_KEM_KEYGEN(const Span<const uint8_t> args[],
                          ReplyCallback write_reply) {
  const Span<const uint8_t> d = args[0];
  const Span<const uint8_t> z = args[1];

  std::vector<uint8_t> seed(d.size() + z.size());
  std::memcpy(seed.data(), d.data(), d.size());
  std::memcpy(seed.data() + d.size(), z.data(), z.size());

  EVP_PKEY *raw = NULL;
  size_t seed_len = 0;

  bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_KEM, nullptr));
  if (!EVP_PKEY_CTX_kem_set_params(ctx.get(), nid) ||
      !EVP_PKEY_keygen_init(ctx.get()) ||
      !EVP_PKEY_keygen_deterministic(ctx.get(), &raw, NULL, &seed_len) ||
      seed_len != seed.size() ||
      !EVP_PKEY_keygen_deterministic(ctx.get(), &raw, seed.data(), &seed_len)) {
    return false;
  }
  bssl::UniquePtr<EVP_PKEY> pkey(raw);

  size_t decaps_key_size = 0;
  size_t encaps_key_size = 0;

  if (!EVP_PKEY_get_raw_private_key(pkey.get(), nullptr, &decaps_key_size) ||
      !EVP_PKEY_get_raw_public_key(pkey.get(), nullptr, &encaps_key_size)) {
    return false;
  }

  std::vector<uint8_t> decaps_key(decaps_key_size);
  std::vector<uint8_t> encaps_key(encaps_key_size);

  if (!EVP_PKEY_get_raw_private_key(pkey.get(), decaps_key.data(),
                                    &decaps_key_size) ||
      !EVP_PKEY_get_raw_public_key(pkey.get(), encaps_key.data(),
                                   &encaps_key_size)) {
    return false;
  }

  return write_reply({Span<const uint8_t>(encaps_key.data(), encaps_key_size),
                      Span<const uint8_t>(decaps_key.data(), decaps_key_size)});
}

template <int nid>
static bool ML_KEM_ENCAP(const Span<const uint8_t> args[],
                         ReplyCallback write_reply) {
  const Span<const uint8_t> ek = args[0];
  const Span<const uint8_t> m = args[1];

  bssl::UniquePtr<EVP_PKEY> pkey(
      EVP_PKEY_kem_new_raw_public_key(nid, ek.data(), ek.size()));
  bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));

  size_t ciphertext_len = 0;
  size_t shared_secret_len = 0;
  size_t seed_len = 0;
  if (!EVP_PKEY_encapsulate_deterministic(ctx.get(), nullptr, &ciphertext_len,
                                          nullptr, &shared_secret_len, nullptr,
                                          &seed_len) ||
      seed_len != m.size()) {
    return false;
  }

  std::vector<uint8_t> ciphertext(ciphertext_len);
  std::vector<uint8_t> shared_secret(shared_secret_len);

  if (!EVP_PKEY_encapsulate_deterministic(
          ctx.get(), ciphertext.data(), &ciphertext_len, shared_secret.data(),
          &shared_secret_len, m.data(), &seed_len)) {
    return false;
  }

  return write_reply(
      {Span<const uint8_t>(ciphertext.data(), ciphertext_len),
       Span<const uint8_t>(shared_secret.data(), shared_secret_len)});
}

template <int nid>
static bool ML_KEM_DECAP(const Span<const uint8_t> args[],
                         ReplyCallback write_reply) {
  const Span<const uint8_t> dk = args[0];
  const Span<const uint8_t> c = args[1];

  bssl::UniquePtr<EVP_PKEY> pkey(
      EVP_PKEY_kem_new_raw_secret_key(nid, dk.data(), dk.size()));
  bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));

  size_t shared_secret_len = 0;
  if (!EVP_PKEY_decapsulate(ctx.get(), nullptr, &shared_secret_len, c.data(),
                            c.size())) {
    return false;
  }

  std::vector<uint8_t> shared_secret(shared_secret_len);

  if (!EVP_PKEY_decapsulate(ctx.get(), shared_secret.data(), &shared_secret_len,
                            c.data(), c.size())) {
    return false;
  }

  return write_reply(
      {Span<const uint8_t>(shared_secret.data(), shared_secret_len)});
}

static bool ED25519KeyGen(const Span<const uint8_t> args[],
                          ReplyCallback write_reply) {
  std::vector<uint8_t> private_key(ED25519_PRIVATE_KEY_LEN);
  std::vector<uint8_t> public_key(ED25519_PUBLIC_KEY_LEN);
  ::ED25519_keypair(public_key.data(), private_key.data());
  const Span<uint8_t> seed(private_key.data(), ED25519_PRIVATE_KEY_SEED_LEN);
  return write_reply({seed, Span<const uint8_t>(public_key)});
}

static bool ED25519KeyVer(const Span<const uint8_t> args[],
                          ReplyCallback write_reply) {
  const Span<const uint8_t> public_key = args[0];

  uint8_t reply[1] = {0};
  if (::ED25519_check_public_key(public_key.data())) {
    reply[0] = 1;
  } else {
    ERR_clear_error();
  }

  return write_reply({Span<const uint8_t>(reply)});
}

static bool ED25519SigGen(const Span<const uint8_t> args[],
                          ReplyCallback write_reply) {
  const Span<const uint8_t> seed = args[0];
  const Span<const uint8_t> message = args[1];

  std::vector<uint8_t> private_key(ED25519_PRIVATE_KEY_LEN);
  std::vector<uint8_t> public_key(ED25519_PUBLIC_KEY_LEN);
  std::vector<uint8_t> signature(ED25519_SIGNATURE_LEN);

  ::ED25519_keypair_from_seed(public_key.data(), private_key.data(),
                              seed.data());

  if (!::ED25519_sign(signature.data(), message.data(), message.size(),
                      private_key.data())) {
    return false;
  }

  return write_reply({Span<const uint8_t>(signature)});
}

static bool ED25519SigVer(const Span<const uint8_t> args[],
                          ReplyCallback write_reply) {
  const Span<const uint8_t> message = args[0];
  const Span<const uint8_t> public_key = args[1];
  const Span<const uint8_t> signature = args[2];

  uint8_t reply[1] = {0};
  if (::ED25519_verify(message.data(), message.size(), signature.data(),
                       public_key.data())) {
    reply[0] = 1;
  } else {
    ERR_clear_error();
  }

  return write_reply({Span<const uint8_t>(reply)});
}

static struct {
  char name[kMaxNameLength + 1];
  uint8_t num_expected_args;
  bool (*handler)(const Span<const uint8_t> args[], ReplyCallback write_reply);
} kFunctions[] = {
    {"getConfig", 0, GetConfig},
    {"SHA-1", 1, Hash<SHA1, SHA_DIGEST_LENGTH>},
    {"SHA2-224", 1, Hash<SHA224, SHA224_DIGEST_LENGTH>},
    {"SHA2-256", 1, Hash<SHA256, SHA256_DIGEST_LENGTH>},
    {"SHA2-384", 1, Hash<SHA384, SHA384_DIGEST_LENGTH>},
    {"SHA2-512", 1, Hash<SHA512, SHA512_DIGEST_LENGTH>},
    {"SHA2-512/224", 1, Hash<SHA512_224, SHA512_224_DIGEST_LENGTH>},
    {"SHA2-512/256", 1, Hash<SHA512_256, SHA512_256_DIGEST_LENGTH>},
    {"SHA3-224", 1, HashSha3<EVP_sha3_224, SHA224_DIGEST_LENGTH>},
    {"SHA3-256", 1, HashSha3<EVP_sha3_256, SHA256_DIGEST_LENGTH>},
    {"SHA3-384", 1, HashSha3<EVP_sha3_384, SHA384_DIGEST_LENGTH>},
    {"SHA3-512", 1, HashSha3<EVP_sha3_512, SHA512_DIGEST_LENGTH>},
    {"SHAKE-128", 2, HashXof<EVP_shake128>},
    {"SHAKE-256", 2, HashXof<EVP_shake256>},
    {"SHA-1/MCT", 1, HashMCT<SHA1, SHA_DIGEST_LENGTH>},
    {"SHA2-224/MCT", 1, HashMCT<SHA224, SHA224_DIGEST_LENGTH>},
    {"SHA2-256/MCT", 1, HashMCT<SHA256, SHA256_DIGEST_LENGTH>},
    {"SHA2-384/MCT", 1, HashMCT<SHA384, SHA384_DIGEST_LENGTH>},
    {"SHA2-512/MCT", 1, HashMCT<SHA512, SHA512_DIGEST_LENGTH>},
    {"SHA2-512/224/MCT", 1, HashMCT<SHA512_224, SHA512_224_DIGEST_LENGTH>},
    {"SHA2-512/256/MCT", 1, HashMCT<SHA512_256, SHA512_256_DIGEST_LENGTH>},
    {"SHA3-224/MCT", 1, HashMCTSha3<EVP_sha3_224, SHA224_DIGEST_LENGTH>},
    {"SHA3-256/MCT", 1, HashMCTSha3<EVP_sha3_256, SHA256_DIGEST_LENGTH>},
    {"SHA3-384/MCT", 1, HashMCTSha3<EVP_sha3_384, SHA384_DIGEST_LENGTH>},
    {"SHA3-512/MCT", 1, HashMCTSha3<EVP_sha3_512, SHA512_DIGEST_LENGTH>},
    {"SHAKE-128/MCT", 4, HashMCTXof<EVP_shake128>},
    {"SHAKE-256/MCT", 4, HashMCTXof<EVP_shake256>},
    {"SHA-1/LDT", 2, HashLDT<SHA1, SHA_DIGEST_LENGTH>},
    {"SHA2-224/LDT", 2, HashLDT<SHA224, SHA224_DIGEST_LENGTH>},
    {"SHA2-256/LDT", 2, HashLDT<SHA256, SHA256_DIGEST_LENGTH>},
    {"SHA2-384/LDT", 2, HashLDT<SHA384, SHA384_DIGEST_LENGTH>},
    {"SHA2-512/LDT", 2, HashLDT<SHA512, SHA512_DIGEST_LENGTH>},
    {"SHA2-512/224/LDT", 2, HashLDT<SHA512_224, SHA512_224_DIGEST_LENGTH>},
    {"SHA2-512/256/LDT", 2, HashLDT<SHA512_256, SHA512_256_DIGEST_LENGTH>},
    {"SHA3-224/LDT", 2, HashLDTSha3<EVP_sha3_224, SHA224_DIGEST_LENGTH>},
    {"SHA3-256/LDT", 2, HashLDTSha3<EVP_sha3_256, SHA256_DIGEST_LENGTH>},
    {"SHA3-384/LDT", 2, HashLDTSha3<EVP_sha3_384, SHA384_DIGEST_LENGTH>},
    {"SHA3-512/LDT", 2, HashLDTSha3<EVP_sha3_512, SHA512_DIGEST_LENGTH>},
    {"AES/encrypt", 3, AES<AES_set_encrypt_key, AES_encrypt>},
    {"AES/decrypt", 3, AES<AES_set_decrypt_key, AES_decrypt>},
    {"AES-XTS/encrypt", 3, AES_XTS<true>},
    {"AES-XTS/decrypt", 3, AES_XTS<false>},
    {"AES-CBC/encrypt", 4, AES_CBC<AES_set_encrypt_key, AES_ENCRYPT>},
    {"AES-CBC/decrypt", 4, AES_CBC<AES_set_decrypt_key, AES_DECRYPT>},
    {"AES-CTR/encrypt", 4, AES_CTR},
    {"AES-CTR/decrypt", 4, AES_CTR},
    {"AES-GCM/seal", 5, AEADSeal<AESGCMSetup>},
    {"AES-GCM/open", 5, AEADOpen<AESGCMSetup>},
    {"AES-KW/seal", 5, AESKeyWrapSeal},
    {"AES-KW/open", 5, AESKeyWrapOpen},
    {"AES-KWP/seal", 5, AESPaddedKeyWrapSeal},
    {"AES-KWP/open", 5, AESPaddedKeyWrapOpen},
    {"AES-CCM/seal", 5, AEADSeal<AESCCMSetup>},
    {"AES-CCM/open", 5, AEADOpen<AESCCMSetup>},
    {"3DES-ECB/encrypt", 3, TDES<true>},
    {"3DES-ECB/decrypt", 3, TDES<false>},
    {"3DES-CBC/encrypt", 4, TDES_CBC<true>},
    {"3DES-CBC/decrypt", 4, TDES_CBC<false>},
    {"HMAC-SHA-1", 2, HMAC<EVP_sha1>},
    {"HMAC-SHA2-224", 2, HMAC<EVP_sha224>},
    {"HMAC-SHA2-256", 2, HMAC<EVP_sha256>},
    {"HMAC-SHA2-384", 2, HMAC<EVP_sha384>},
    {"HMAC-SHA2-512", 2, HMAC<EVP_sha512>},
    {"HMAC-SHA2-512/224", 2, HMAC<EVP_sha512_224>},
    {"HMAC-SHA2-512/256", 2, HMAC<EVP_sha512_256>},
    {"ctrDRBG/AES-256", 6, DRBG<false>},
    {"ctrDRBG-reseed/AES-256", 8, DRBG<true>},
    {"ECDSA/keyGen", 1, ECDSAKeyGen},
    {"ECDSA/keyVer", 3, ECDSAKeyVer},
    {"ECDSA/sigGen", 4, ECDSASigGen},
    {"ECDSA/sigVer", 7, ECDSASigVer},
    {"CMAC-AES", 3, CMAC_AES},
    {"CMAC-AES/verify", 3, CMAC_AESVerify},
    {"RSA/keyGen", 1, RSAKeyGen},
    {"RSA/sigGen/SHA3-224/pkcs1v1.5", 2, RSASigGen<EVP_sha3_224, false>},
    {"RSA/sigGen/SHA3-256/pkcs1v1.5", 2, RSASigGen<EVP_sha3_256, false>},
    {"RSA/sigGen/SHA3-384/pkcs1v1.5", 2, RSASigGen<EVP_sha3_384, false>},
    {"RSA/sigGen/SHA3-512/pkcs1v1.5", 2, RSASigGen<EVP_sha3_512, false>},
    {"RSA/sigGen/SHA2-224/pkcs1v1.5", 2, RSASigGen<EVP_sha224, false>},
    {"RSA/sigGen/SHA2-256/pkcs1v1.5", 2, RSASigGen<EVP_sha256, false>},
    {"RSA/sigGen/SHA2-384/pkcs1v1.5", 2, RSASigGen<EVP_sha384, false>},
    {"RSA/sigGen/SHA2-512/pkcs1v1.5", 2, RSASigGen<EVP_sha512, false>},
    {"RSA/sigGen/SHA2-512/224/pkcs1v1.5", 2, RSASigGen<EVP_sha512_224, false>},
    {"RSA/sigGen/SHA2-512/256/pkcs1v1.5", 2, RSASigGen<EVP_sha512_256, false>},
    {"RSA/sigGen/SHA-1/pkcs1v1.5", 2, RSASigGen<EVP_sha1, false>},
    {"RSA/sigGen/SHA3-224/pss", 2, RSASigGen<EVP_sha3_224, true>},
    {"RSA/sigGen/SHA3-256/pss", 2, RSASigGen<EVP_sha3_256, true>},
    {"RSA/sigGen/SHA3-384/pss", 2, RSASigGen<EVP_sha3_384, true>},
    {"RSA/sigGen/SHA3-512/pss", 2, RSASigGen<EVP_sha3_512, true>},
    {"RSA/sigGen/SHA2-224/pss", 2, RSASigGen<EVP_sha224, true>},
    {"RSA/sigGen/SHA2-256/pss", 2, RSASigGen<EVP_sha256, true>},
    {"RSA/sigGen/SHA2-384/pss", 2, RSASigGen<EVP_sha384, true>},
    {"RSA/sigGen/SHA2-512/pss", 2, RSASigGen<EVP_sha512, true>},
    {"RSA/sigGen/SHA2-512/224/pss", 2, RSASigGen<EVP_sha512_224, true>},
    {"RSA/sigGen/SHA2-512/256/pss", 2, RSASigGen<EVP_sha512_256, true>},
    {"RSA/sigGen/SHA-1/pss", 2, RSASigGen<EVP_sha1, true>},
    {"RSA/sigGen/SHAKE-128/pss", 2, RSASigGen<EVP_shake128, true>},
    {"RSA/sigGen/SHAKE-256/pss", 2, RSASigGen<EVP_shake256, true>},
    {"RSA/sigVer/SHA3-224/pkcs1v1.5", 4, RSASigVer<EVP_sha3_224, false>},
    {"RSA/sigVer/SHA3-256/pkcs1v1.5", 4, RSASigVer<EVP_sha3_256, false>},
    {"RSA/sigVer/SHA3-384/pkcs1v1.5", 4, RSASigVer<EVP_sha3_384, false>},
    {"RSA/sigVer/SHA3-512/pkcs1v1.5", 4, RSASigVer<EVP_sha3_512, false>},
    {"RSA/sigVer/SHA2-224/pkcs1v1.5", 4, RSASigVer<EVP_sha224, false>},
    {"RSA/sigVer/SHA2-256/pkcs1v1.5", 4, RSASigVer<EVP_sha256, false>},
    {"RSA/sigVer/SHA2-384/pkcs1v1.5", 4, RSASigVer<EVP_sha384, false>},
    {"RSA/sigVer/SHA2-512/pkcs1v1.5", 4, RSASigVer<EVP_sha512, false>},
    {"RSA/sigVer/SHA2-512/224/pkcs1v1.5", 4, RSASigVer<EVP_sha512_224, false>},
    {"RSA/sigVer/SHA2-512/256/pkcs1v1.5", 4, RSASigVer<EVP_sha512_256, false>},
    {"RSA/sigVer/SHA-1/pkcs1v1.5", 4, RSASigVer<EVP_sha1, false>},
    {"RSA/sigVer/SHA3-224/pss", 4, RSASigVer<EVP_sha3_224, true>},
    {"RSA/sigVer/SHA3-256/pss", 4, RSASigVer<EVP_sha3_256, true>},
    {"RSA/sigVer/SHA3-384/pss", 4, RSASigVer<EVP_sha3_384, true>},
    {"RSA/sigVer/SHA3-512/pss", 4, RSASigVer<EVP_sha3_512, true>},
    {"RSA/sigVer/SHA2-224/pss", 4, RSASigVer<EVP_sha224, true>},
    {"RSA/sigVer/SHA2-256/pss", 4, RSASigVer<EVP_sha256, true>},
    {"RSA/sigVer/SHA2-384/pss", 4, RSASigVer<EVP_sha384, true>},
    {"RSA/sigVer/SHA2-512/pss", 4, RSASigVer<EVP_sha512, true>},
    {"RSA/sigVer/SHA2-512/224/pss", 4, RSASigVer<EVP_sha512_224, true>},
    {"RSA/sigVer/SHA2-512/256/pss", 4, RSASigVer<EVP_sha512_256, true>},
    {"RSA/sigVer/SHA-1/pss", 4, RSASigVer<EVP_sha1, true>},
    {"RSA/sigVer/SHAKE-128/pss", 4, RSASigVer<EVP_shake128, true>},
    {"RSA/sigVer/SHAKE-256/pss", 4, RSASigVer<EVP_shake256, true>},
    {"TLSKDF/1.0/SHA-1", 5, TLSKDF<EVP_md5_sha1>},
    {"TLSKDF/1.2/SHA2-256", 5, TLSKDF<EVP_sha256>},
    {"TLSKDF/1.2/SHA2-384", 5, TLSKDF<EVP_sha384>},
    {"TLSKDF/1.2/SHA2-512", 5, TLSKDF<EVP_sha512>},
    {"ECDH/P-224", 3, ECDH<NID_secp224r1>},
    {"ECDH/P-256", 3, ECDH<NID_X9_62_prime256v1>},
    {"ECDH/P-384", 3, ECDH<NID_secp384r1>},
    {"ECDH/P-521", 3, ECDH<NID_secp521r1>},
    {"FFDH", 6, FFDH},
    {"PBKDF", 5, PBKDF},
    {"KDA/HKDF/SHA-1", 4, HKDF<EVP_sha1>},
    {"KDA/HKDF/SHA2-224", 4, HKDF<EVP_sha224>},
    {"KDA/HKDF/SHA2-256", 4, HKDF<EVP_sha256>},
    {"KDA/HKDF/SHA2-384", 4, HKDF<EVP_sha384>},
    {"KDA/HKDF/SHA2-512", 4, HKDF<EVP_sha512>},
    {"KDA/OneStep/SHA-1", 3, SSKDF_DIGEST<EVP_sha1>},
    {"KDA/OneStep/SHA2-224", 3, SSKDF_DIGEST<EVP_sha224>},
    {"KDA/OneStep/SHA2-256", 3, SSKDF_DIGEST<EVP_sha256>},
    {"KDA/OneStep/SHA2-384", 3, SSKDF_DIGEST<EVP_sha384>},
    {"KDA/OneStep/SHA2-512", 3, SSKDF_DIGEST<EVP_sha512>},
    {"KDA/OneStep/SHA2-512/224", 3, SSKDF_DIGEST<EVP_sha512_224>},
    {"KDA/OneStep/SHA2-512/256", 3, SSKDF_DIGEST<EVP_sha512_256>},
    {"KDA/OneStep/SHA3-224", 3, SSKDF_DIGEST<EVP_sha3_224>},
    {"KDA/OneStep/SHA3-256", 3, SSKDF_DIGEST<EVP_sha3_256>},
    {"KDA/OneStep/SHA3-384", 3, SSKDF_DIGEST<EVP_sha3_384>},
    {"KDA/OneStep/SHA3-512", 3, SSKDF_DIGEST<EVP_sha3_512>},
    {"KDA/OneStep/HMAC-SHA-1", 4, SSKDF_HMAC<EVP_sha1>},
    {"KDA/OneStep/HMAC-SHA2-224", 4, SSKDF_HMAC<EVP_sha224>},
    {"KDA/OneStep/HMAC-SHA2-256", 4, SSKDF_HMAC<EVP_sha256>},
    {"KDA/OneStep/HMAC-SHA2-384", 4, SSKDF_HMAC<EVP_sha384>},
    {"KDA/OneStep/HMAC-SHA2-512", 4, SSKDF_HMAC<EVP_sha512>},
    {"KDA/OneStep/HMAC-SHA2-512/224", 4, SSKDF_HMAC<EVP_sha512_224>},
    {"KDA/OneStep/HMAC-SHA2-512/256", 4, SSKDF_HMAC<EVP_sha512_256>},
    {"SSHKDF/SHA-1/ivCli", 4,
     SSHKDF<EVP_sha1, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_CLI_TO_SRV>},
    {"SSHKDF/SHA2-224/ivCli", 4,
     SSHKDF<EVP_sha224, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_CLI_TO_SRV>},
    {"SSHKDF/SHA2-256/ivCli", 4,
     SSHKDF<EVP_sha256, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_CLI_TO_SRV>},
    {"SSHKDF/SHA2-384/ivCli", 4,
     SSHKDF<EVP_sha384, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_CLI_TO_SRV>},
    {"SSHKDF/SHA2-512/ivCli", 4,
     SSHKDF<EVP_sha512, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_CLI_TO_SRV>},
    {"SSHKDF/SHA-1/ivServ", 4,
     SSHKDF<EVP_sha1, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_SRV_TO_CLI>},
    {"SSHKDF/SHA2-224/ivServ", 4,
     SSHKDF<EVP_sha224, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_SRV_TO_CLI>},
    {"SSHKDF/SHA2-256/ivServ", 4,
     SSHKDF<EVP_sha256, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_SRV_TO_CLI>},
    {"SSHKDF/SHA2-384/ivServ", 4,
     SSHKDF<EVP_sha384, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_SRV_TO_CLI>},
    {"SSHKDF/SHA2-512/ivServ", 4,
     SSHKDF<EVP_sha512, EVP_KDF_SSHKDF_TYPE_INITIAL_IV_SRV_TO_CLI>},
    {"SSHKDF/SHA-1/encryptCli", 4,
     SSHKDF<EVP_sha1, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA2-224/encryptCli", 4,
     SSHKDF<EVP_sha224, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA2-256/encryptCli", 4,
     SSHKDF<EVP_sha256, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA2-384/encryptCli", 4,
     SSHKDF<EVP_sha384, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA2-512/encryptCli", 4,
     SSHKDF<EVP_sha512, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA-1/encryptServ", 4,
     SSHKDF<EVP_sha1, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_SRV_TO_CLI>},
    {"SSHKDF/SHA2-224/encryptServ", 4,
     SSHKDF<EVP_sha224, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_SRV_TO_CLI>},
    {"SSHKDF/SHA2-256/encryptServ", 4,
     SSHKDF<EVP_sha256, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_SRV_TO_CLI>},
    {"SSHKDF/SHA2-384/encryptServ", 4,
     SSHKDF<EVP_sha384, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_SRV_TO_CLI>},
    {"SSHKDF/SHA2-512/encryptServ", 4,
     SSHKDF<EVP_sha512, EVP_KDF_SSHKDF_TYPE_ENCRYPTION_KEY_SRV_TO_CLI>},
    {"SSHKDF/SHA-1/integCli", 4,
     SSHKDF<EVP_sha1, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA2-224/integCli", 4,
     SSHKDF<EVP_sha224, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA2-256/integCli", 4,
     SSHKDF<EVP_sha256, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA2-384/integCli", 4,
     SSHKDF<EVP_sha384, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA2-512/integCli", 4,
     SSHKDF<EVP_sha512, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_CLI_TO_SRV>},
    {"SSHKDF/SHA-1/integServ", 4,
     SSHKDF<EVP_sha1, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_SRV_TO_CLI>},
    {"SSHKDF/SHA2-224/integServ", 4,
     SSHKDF<EVP_sha224, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_SRV_TO_CLI>},
    {"SSHKDF/SHA2-256/integServ", 4,
     SSHKDF<EVP_sha256, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_SRV_TO_CLI>},
    {"SSHKDF/SHA2-384/integServ", 4,
     SSHKDF<EVP_sha384, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_SRV_TO_CLI>},
    {"SSHKDF/SHA2-512/integServ", 4,
     SSHKDF<EVP_sha512, EVP_KDF_SSHKDF_TYPE_INTEGRITY_KEY_SRV_TO_CLI>},
    {"KDF/Feedback/HMAC-SHA-1", 3, HKDF_expand<EVP_sha1>},
    {"KDF/Feedback/HMAC-SHA2-224", 3, HKDF_expand<EVP_sha224>},
    {"KDF/Feedback/HMAC-SHA2-256", 3, HKDF_expand<EVP_sha256>},
    {"KDF/Feedback/HMAC-SHA2-384", 3, HKDF_expand<EVP_sha384>},
    {"KDF/Feedback/HMAC-SHA2-512", 3, HKDF_expand<EVP_sha512>},
    {"KDF/Feedback/HMAC-SHA2-512/224", 3, HKDF_expand<EVP_sha512_224>},
    {"KDF/Feedback/HMAC-SHA2-512/256", 3, HKDF_expand<EVP_sha512_256>},
    {"KDF/Counter/HMAC-SHA-1", 3, KBKDF_CTR_HMAC<EVP_sha1>},
    {"KDF/Counter/HMAC-SHA2-224", 3, KBKDF_CTR_HMAC<EVP_sha224>},
    {"KDF/Counter/HMAC-SHA2-256", 3, KBKDF_CTR_HMAC<EVP_sha256>},
    {"KDF/Counter/HMAC-SHA2-384", 3, KBKDF_CTR_HMAC<EVP_sha384>},
    {"KDF/Counter/HMAC-SHA2-512", 3, KBKDF_CTR_HMAC<EVP_sha512>},
    {"KDF/Counter/HMAC-SHA2-512/224", 3, KBKDF_CTR_HMAC<EVP_sha512_224>},
    {"KDF/Counter/HMAC-SHA2-512/256", 3, KBKDF_CTR_HMAC<EVP_sha512_256>},
    {"ML-KEM/ML-KEM-512/keyGen", 2, ML_KEM_KEYGEN<NID_MLKEM512>},
    {"ML-KEM/ML-KEM-768/keyGen", 2, ML_KEM_KEYGEN<NID_MLKEM768>},
    {"ML-KEM/ML-KEM-1024/keyGen", 2, ML_KEM_KEYGEN<NID_MLKEM1024>},
    {"ML-KEM/ML-KEM-512/encap", 2, ML_KEM_ENCAP<NID_MLKEM512>},
    {"ML-KEM/ML-KEM-768/encap", 2, ML_KEM_ENCAP<NID_MLKEM768>},
    {"ML-KEM/ML-KEM-1024/encap", 2, ML_KEM_ENCAP<NID_MLKEM1024>},
    {"ML-KEM/ML-KEM-512/decap", 2, ML_KEM_DECAP<NID_MLKEM512>},
    {"ML-KEM/ML-KEM-768/decap", 2, ML_KEM_DECAP<NID_MLKEM768>},
    {"ML-KEM/ML-KEM-1024/decap", 2, ML_KEM_DECAP<NID_MLKEM1024>},
    {"EDDSA/ED-25519/keyGen", 0, ED25519KeyGen},
    {"EDDSA/ED-25519/keyVer", 1, ED25519KeyVer},
    {"EDDSA/ED-25519/sigGen", 2, ED25519SigGen},
    {"EDDSA/ED-25519/sigVer", 3, ED25519SigVer}};

Handler FindHandler(Span<const Span<const uint8_t>> args) {
  const bssl::Span<const uint8_t> algorithm = args[0];
  for (const auto &func : kFunctions) {
    if (algorithm.size() == strlen(func.name) &&
        memcmp(algorithm.data(), func.name, algorithm.size()) == 0) {
      if (args.size() - 1 != func.num_expected_args) {
        LOG_ERROR("\'%s\' operation received %zu arguments but expected %u.\n",
                  func.name, args.size() - 1, func.num_expected_args);
        return nullptr;
      }

      return func.handler;
    }
  }

  const std::string name(reinterpret_cast<const char *>(algorithm.data()),
                         algorithm.size());
  LOG_ERROR("Unknown operation: %s\n", name.c_str());
  return nullptr;
}

}  // namespace acvp
}  // namespace bssl
