mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +00:00
1247 lines
44 KiB
C++
1247 lines
44 KiB
C++
#include "Crypto.hpp"
|
|
|
|
#include <openssl/bn.h>
|
|
#include <openssl/core_names.h>
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/ec.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/kdf.h>
|
|
#include <openssl/macros.h>
|
|
#include <openssl/obj_mac.h>
|
|
#include <openssl/param_build.h>
|
|
#include <openssl/params.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/rsa.h>
|
|
|
|
#include <climits>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
namespace crypto {
|
|
Error::Error(ErrorCode code, const std::string& message)
|
|
: std::runtime_error(message), errorCode(code) {
|
|
}
|
|
|
|
ErrorCode Error::code() const noexcept {
|
|
return errorCode;
|
|
}
|
|
|
|
const char* Error::codeName() const noexcept {
|
|
switch (errorCode) {
|
|
case ErrorCode::InvalidArgument:
|
|
return "invalid_argument";
|
|
case ErrorCode::InvalidKey:
|
|
return "invalid_key";
|
|
case ErrorCode::InvalidSignature:
|
|
return "invalid_signature";
|
|
case ErrorCode::AuthenticationFailed:
|
|
return "authentication_failed";
|
|
case ErrorCode::UnsupportedAlgorithm:
|
|
return "unsupported_algorithm";
|
|
case ErrorCode::OutputTooLarge:
|
|
return "output_too_large";
|
|
case ErrorCode::InvalidState:
|
|
return "invalid_state";
|
|
case ErrorCode::BackendError:
|
|
return "backend_error";
|
|
}
|
|
return "backend_error";
|
|
}
|
|
|
|
namespace {
|
|
constexpr std::size_t AEAD_TAG_SIZE = 16;
|
|
|
|
template <typename T, void (*Free)(T*)>
|
|
using Handle = std::unique_ptr<T, decltype(Free)>;
|
|
|
|
using BnHandle = Handle<BIGNUM, BN_free>;
|
|
using EcGroupHandle = Handle<EC_GROUP, EC_GROUP_free>;
|
|
using EcPointHandle = Handle<EC_POINT, EC_POINT_free>;
|
|
using EvpCipherCtxHandle = Handle<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
|
|
using EvpMdCtxHandle = Handle<EVP_MD_CTX, EVP_MD_CTX_free>;
|
|
using EvpPkeyHandle = Handle<EVP_PKEY, EVP_PKEY_free>;
|
|
using EvpPkeyCtxHandle = Handle<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
|
|
using ParamBldHandle = Handle<OSSL_PARAM_BLD, OSSL_PARAM_BLD_free>;
|
|
using ParamHandle = Handle<OSSL_PARAM, OSSL_PARAM_free>;
|
|
|
|
const unsigned char* bytes(std::string_view value) {
|
|
return reinterpret_cast<const unsigned char*>(value.data());
|
|
}
|
|
|
|
std::string opensslError(const std::string& operation) {
|
|
const unsigned long code = ERR_get_error();
|
|
if (code == 0) {
|
|
return operation;
|
|
}
|
|
char message[256];
|
|
ERR_error_string_n(code, message, sizeof(message));
|
|
return operation + ": " + message;
|
|
}
|
|
|
|
[[noreturn]] void fail(const std::string& operation) {
|
|
throw Error(ErrorCode::BackendError, opensslError(operation));
|
|
}
|
|
|
|
int checkedSize(std::size_t size, const char* name) {
|
|
if (size > static_cast<std::size_t>(INT_MAX)) {
|
|
throw Error(
|
|
ErrorCode::OutputTooLarge,
|
|
std::string(name) + " is too large"
|
|
);
|
|
}
|
|
return static_cast<int>(size);
|
|
}
|
|
|
|
const EVP_MD* getShaDigest(std::string_view hash) {
|
|
if (hash == "SHA256") return EVP_sha256();
|
|
if (hash == "SHA384") return EVP_sha384();
|
|
if (hash == "SHA512") return EVP_sha512();
|
|
throw Error(
|
|
ErrorCode::UnsupportedAlgorithm,
|
|
"unsupported hash: " + std::string(hash)
|
|
);
|
|
}
|
|
|
|
const EVP_MD* getDigest(std::string_view hash) {
|
|
if (hash == "MD5") return EVP_md5();
|
|
return getShaDigest(hash);
|
|
}
|
|
|
|
const char* curveName(std::string_view curve) {
|
|
if (curve == "P-256") return "prime256v1";
|
|
if (curve == "P-384") return "secp384r1";
|
|
if (curve == "P-521") return "secp521r1";
|
|
throw Error(
|
|
ErrorCode::UnsupportedAlgorithm,
|
|
"unsupported curve: " + std::string(curve)
|
|
);
|
|
}
|
|
|
|
int curveNid(std::string_view curve) {
|
|
if (curve == "P-256") return NID_X9_62_prime256v1;
|
|
if (curve == "P-384") return NID_secp384r1;
|
|
if (curve == "P-521") return NID_secp521r1;
|
|
curveName(curve);
|
|
return NID_undef;
|
|
}
|
|
|
|
std::size_t curvePrivateSize(std::string_view curve) {
|
|
if (curve == "P-256") return 32;
|
|
if (curve == "P-384") return 48;
|
|
if (curve == "P-521") return 66;
|
|
curveName(curve);
|
|
return 0;
|
|
}
|
|
|
|
EvpPkeyHandle fromData(
|
|
const char* algorithm,
|
|
int selection,
|
|
OSSL_PARAM_BLD* builder,
|
|
ErrorCode failureCode = ErrorCode::BackendError
|
|
) {
|
|
ParamHandle params(
|
|
OSSL_PARAM_BLD_to_param(builder), OSSL_PARAM_free
|
|
);
|
|
EvpPkeyCtxHandle ctx(
|
|
EVP_PKEY_CTX_new_from_name(nullptr, algorithm, nullptr),
|
|
EVP_PKEY_CTX_free
|
|
);
|
|
EVP_PKEY* rawKey = nullptr;
|
|
if (!params || !ctx || EVP_PKEY_fromdata_init(ctx.get()) <= 0 ||
|
|
EVP_PKEY_fromdata(
|
|
ctx.get(), &rawKey, selection, params.get()
|
|
) <= 0) {
|
|
throw Error(
|
|
failureCode,
|
|
opensslError(
|
|
std::string("failed to import ") + algorithm + " key"
|
|
)
|
|
);
|
|
}
|
|
return EvpPkeyHandle(rawKey, EVP_PKEY_free);
|
|
}
|
|
|
|
void checkPublicKey(EVP_PKEY* key, const char* name) {
|
|
EvpPkeyCtxHandle ctx(
|
|
EVP_PKEY_CTX_new(key, nullptr), EVP_PKEY_CTX_free
|
|
);
|
|
if (!ctx || EVP_PKEY_public_check(ctx.get()) != 1) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey,
|
|
opensslError(std::string("invalid ") + name + " public key")
|
|
);
|
|
}
|
|
}
|
|
|
|
EvpPkeyHandle makeEcdsaPublicKey(
|
|
std::string_view curve, std::string_view encoded
|
|
) {
|
|
if (encoded.empty() ||
|
|
static_cast<unsigned char>(encoded[0]) != 0x04) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey,
|
|
"ECDSA public key must be an uncompressed SEC1 point"
|
|
);
|
|
}
|
|
ParamBldHandle builder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
|
|
const char* group = curveName(curve);
|
|
if (!builder ||
|
|
OSSL_PARAM_BLD_push_utf8_string(
|
|
builder.get(), OSSL_PKEY_PARAM_GROUP_NAME, group, 0
|
|
) != 1 ||
|
|
OSSL_PARAM_BLD_push_octet_string(
|
|
builder.get(),
|
|
OSSL_PKEY_PARAM_PUB_KEY,
|
|
encoded.data(),
|
|
encoded.size()
|
|
) != 1) {
|
|
fail("failed to prepare ECDSA public key");
|
|
}
|
|
auto result = fromData(
|
|
"EC", EVP_PKEY_PUBLIC_KEY, builder.get(), ErrorCode::InvalidKey
|
|
);
|
|
checkPublicKey(result.get(), "ECDSA");
|
|
return result;
|
|
}
|
|
|
|
EvpPkeyHandle makeRsaPublicKey(
|
|
std::string_view modulus, std::string_view exponent
|
|
) {
|
|
if (modulus.empty() || exponent.empty()) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey,
|
|
"RSA modulus and exponent must not be empty"
|
|
);
|
|
}
|
|
BnHandle n(
|
|
BN_bin2bn(
|
|
bytes(modulus),
|
|
checkedSize(modulus.size(), "modulus"),
|
|
nullptr
|
|
),
|
|
BN_free
|
|
);
|
|
BnHandle e(
|
|
BN_bin2bn(
|
|
bytes(exponent),
|
|
checkedSize(exponent.size(), "exponent"),
|
|
nullptr
|
|
),
|
|
BN_free
|
|
);
|
|
ParamBldHandle builder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
|
|
if (!n || !e || !builder) {
|
|
fail("failed to prepare RSA public key");
|
|
}
|
|
if (BN_is_zero(n.get()) || BN_is_zero(e.get())) {
|
|
throw Error(ErrorCode::InvalidKey, "invalid RSA public key");
|
|
}
|
|
if (OSSL_PARAM_BLD_push_BN(
|
|
builder.get(), OSSL_PKEY_PARAM_RSA_N, n.get()
|
|
) != 1 ||
|
|
OSSL_PARAM_BLD_push_BN(
|
|
builder.get(), OSSL_PKEY_PARAM_RSA_E, e.get()
|
|
) != 1) {
|
|
fail("invalid RSA public key");
|
|
}
|
|
auto result = fromData(
|
|
"RSA", EVP_PKEY_PUBLIC_KEY, builder.get(), ErrorCode::InvalidKey
|
|
);
|
|
checkPublicKey(result.get(), "RSA");
|
|
return result;
|
|
}
|
|
|
|
bool verifyDigestSignature(
|
|
EVP_PKEY* key,
|
|
const EVP_MD* md,
|
|
std::string_view message,
|
|
std::string_view signature,
|
|
int rsaPadding,
|
|
int saltLength
|
|
) {
|
|
EvpMdCtxHandle ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
|
EVP_PKEY_CTX* pkeyCtx = nullptr;
|
|
if (!ctx ||
|
|
EVP_DigestVerifyInit(ctx.get(), &pkeyCtx, md, nullptr, key) !=
|
|
1) {
|
|
fail("signature verification initialization failed");
|
|
}
|
|
if (rsaPadding != 0) {
|
|
if (EVP_PKEY_CTX_set_rsa_padding(pkeyCtx, rsaPadding) <= 0) {
|
|
fail("failed to configure RSA padding");
|
|
}
|
|
if (rsaPadding == RSA_PKCS1_PSS_PADDING &&
|
|
EVP_PKEY_CTX_set_rsa_pss_saltlen(pkeyCtx, saltLength) <=
|
|
0) {
|
|
fail("failed to configure RSA-PSS salt length");
|
|
}
|
|
}
|
|
if (EVP_DigestVerifyUpdate(
|
|
ctx.get(), message.data(), message.size()
|
|
) != 1) {
|
|
fail("signature verification update failed");
|
|
}
|
|
const int result = EVP_DigestVerifyFinal(
|
|
ctx.get(), bytes(signature), signature.size()
|
|
);
|
|
if (result < 0) {
|
|
throw Error(
|
|
ErrorCode::InvalidSignature,
|
|
opensslError("invalid signature encoding")
|
|
);
|
|
}
|
|
if (result == 0) ERR_clear_error();
|
|
return result == 1;
|
|
}
|
|
|
|
EvpPkeyHandle rawPrivateKey(
|
|
int type, std::string_view key, const char* name
|
|
) {
|
|
if (key.size() != 32) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey,
|
|
std::string(name) + " private key must be 32 bytes"
|
|
);
|
|
}
|
|
EvpPkeyHandle result(
|
|
EVP_PKEY_new_raw_private_key(
|
|
type, nullptr, bytes(key), key.size()
|
|
),
|
|
EVP_PKEY_free
|
|
);
|
|
if (!result) fail(std::string("invalid ") + name + " private key");
|
|
return result;
|
|
}
|
|
|
|
EvpPkeyHandle rawPublicKey(
|
|
int type, std::string_view key, const char* name
|
|
) {
|
|
if (key.size() != 32) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey,
|
|
std::string(name) + " public key must be 32 bytes"
|
|
);
|
|
}
|
|
EvpPkeyHandle result(
|
|
EVP_PKEY_new_raw_public_key(
|
|
type, nullptr, bytes(key), key.size()
|
|
),
|
|
EVP_PKEY_free
|
|
);
|
|
if (!result) fail(std::string("invalid ") + name + " public key");
|
|
return result;
|
|
}
|
|
|
|
const EVP_CIPHER* aesGcmCipher(std::size_t keySize) {
|
|
if (keySize == 16) return EVP_aes_128_gcm();
|
|
if (keySize == 32) return EVP_aes_256_gcm();
|
|
throw Error(
|
|
ErrorCode::InvalidKey, "AES-GCM key must be 16 or 32 bytes"
|
|
);
|
|
}
|
|
|
|
Bytes aeadEncrypt(
|
|
const EVP_CIPHER* cipher,
|
|
std::string_view key,
|
|
std::string_view nonce,
|
|
std::string_view aad,
|
|
std::string_view plaintext
|
|
) {
|
|
if (nonce.empty()) {
|
|
throw Error(
|
|
ErrorCode::InvalidArgument, "nonce must not be empty"
|
|
);
|
|
}
|
|
const int nonceSize = checkedSize(nonce.size(), "nonce");
|
|
const int aadSize = checkedSize(aad.size(), "aad");
|
|
const int plaintextSize =
|
|
checkedSize(plaintext.size(), "plaintext");
|
|
EvpCipherCtxHandle ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
|
if (!ctx ||
|
|
EVP_EncryptInit_ex(
|
|
ctx.get(), cipher, nullptr, nullptr, nullptr
|
|
) != 1 ||
|
|
EVP_CIPHER_CTX_ctrl(
|
|
ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, nonceSize, nullptr
|
|
) != 1 ||
|
|
EVP_EncryptInit_ex(
|
|
ctx.get(), nullptr, nullptr, bytes(key), bytes(nonce)
|
|
) != 1) {
|
|
fail("AEAD encryption initialization failed");
|
|
}
|
|
int written = 0;
|
|
if (!aad.empty() &&
|
|
EVP_EncryptUpdate(
|
|
ctx.get(), nullptr, &written, bytes(aad), aadSize
|
|
) != 1) {
|
|
fail("AEAD AAD processing failed");
|
|
}
|
|
Bytes output(plaintext.size() + AEAD_TAG_SIZE);
|
|
int outputSize = 0;
|
|
if (!plaintext.empty() && EVP_EncryptUpdate(
|
|
ctx.get(),
|
|
output.data(),
|
|
&outputSize,
|
|
bytes(plaintext),
|
|
plaintextSize
|
|
) != 1) {
|
|
fail("AEAD encryption failed");
|
|
}
|
|
if (EVP_EncryptFinal_ex(
|
|
ctx.get(), output.data() + outputSize, &written
|
|
) != 1) {
|
|
fail("AEAD encryption finalization failed");
|
|
}
|
|
outputSize += written;
|
|
if (EVP_CIPHER_CTX_ctrl(
|
|
ctx.get(),
|
|
EVP_CTRL_AEAD_GET_TAG,
|
|
AEAD_TAG_SIZE,
|
|
output.data() + outputSize
|
|
) != 1) {
|
|
fail("AEAD tag retrieval failed");
|
|
}
|
|
output.resize(static_cast<std::size_t>(outputSize) + AEAD_TAG_SIZE);
|
|
return output;
|
|
}
|
|
|
|
Bytes aeadDecrypt(
|
|
const EVP_CIPHER* cipher,
|
|
std::string_view key,
|
|
std::string_view nonce,
|
|
std::string_view aad,
|
|
std::string_view ciphertextWithTag
|
|
) {
|
|
if (nonce.empty()) {
|
|
throw Error(
|
|
ErrorCode::InvalidArgument, "nonce must not be empty"
|
|
);
|
|
}
|
|
if (ciphertextWithTag.size() < AEAD_TAG_SIZE) {
|
|
throw Error(
|
|
ErrorCode::AuthenticationFailed,
|
|
"ciphertext is shorter than the authentication tag"
|
|
);
|
|
}
|
|
const std::size_t ciphertextSize =
|
|
ciphertextWithTag.size() - AEAD_TAG_SIZE;
|
|
const int nonceSize = checkedSize(nonce.size(), "nonce");
|
|
const int aadSize = checkedSize(aad.size(), "aad");
|
|
const int inputSize = checkedSize(ciphertextSize, "ciphertext");
|
|
EvpCipherCtxHandle ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
|
if (!ctx ||
|
|
EVP_DecryptInit_ex(
|
|
ctx.get(), cipher, nullptr, nullptr, nullptr
|
|
) != 1 ||
|
|
EVP_CIPHER_CTX_ctrl(
|
|
ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, nonceSize, nullptr
|
|
) != 1 ||
|
|
EVP_DecryptInit_ex(
|
|
ctx.get(), nullptr, nullptr, bytes(key), bytes(nonce)
|
|
) != 1) {
|
|
fail("AEAD decryption initialization failed");
|
|
}
|
|
int written = 0;
|
|
if (!aad.empty() &&
|
|
EVP_DecryptUpdate(
|
|
ctx.get(), nullptr, &written, bytes(aad), aadSize
|
|
) != 1) {
|
|
fail("AEAD AAD processing failed");
|
|
}
|
|
Bytes output(ciphertextSize + AEAD_TAG_SIZE);
|
|
int outputSize = 0;
|
|
if (ciphertextSize != 0 && EVP_DecryptUpdate(
|
|
ctx.get(),
|
|
output.data(),
|
|
&outputSize,
|
|
bytes(ciphertextWithTag),
|
|
inputSize
|
|
) != 1) {
|
|
fail("AEAD decryption failed");
|
|
}
|
|
if (EVP_CIPHER_CTX_ctrl(
|
|
ctx.get(),
|
|
EVP_CTRL_AEAD_SET_TAG,
|
|
AEAD_TAG_SIZE,
|
|
const_cast<unsigned char*>(
|
|
bytes(ciphertextWithTag) + ciphertextSize
|
|
)
|
|
) != 1) {
|
|
fail("AEAD tag setup failed");
|
|
}
|
|
const int result = EVP_DecryptFinal_ex(
|
|
ctx.get(), output.data() + outputSize, &written
|
|
);
|
|
if (result != 1) {
|
|
ERR_clear_error();
|
|
throw Error(
|
|
ErrorCode::AuthenticationFailed, "authentication failed"
|
|
);
|
|
}
|
|
outputSize += written;
|
|
output.resize(static_cast<std::size_t>(outputSize));
|
|
return output;
|
|
}
|
|
|
|
struct EcKey {
|
|
EvpPkeyHandle key;
|
|
Bytes publicKey;
|
|
};
|
|
|
|
EcKey ecPrivateKey(
|
|
std::string_view curve, std::string_view privateKey
|
|
) {
|
|
const std::size_t expectedSize = curvePrivateSize(curve);
|
|
if (privateKey.size() != expectedSize) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey,
|
|
std::string(curve) + " private key must be " +
|
|
std::to_string(expectedSize) + " bytes"
|
|
);
|
|
}
|
|
EcGroupHandle group(
|
|
EC_GROUP_new_by_curve_name(curveNid(curve)), EC_GROUP_free
|
|
);
|
|
BnHandle scalar(
|
|
BN_bin2bn(
|
|
bytes(privateKey),
|
|
checkedSize(privateKey.size(), "private key"),
|
|
nullptr
|
|
),
|
|
BN_free
|
|
);
|
|
BnHandle order(BN_new(), BN_free);
|
|
if (!group || !scalar || !order || BN_is_zero(scalar.get()) ||
|
|
EC_GROUP_get_order(group.get(), order.get(), nullptr) != 1 ||
|
|
BN_cmp(scalar.get(), order.get()) >= 0) {
|
|
throw Error(ErrorCode::InvalidKey, "invalid EC private key");
|
|
}
|
|
EcPointHandle publicPoint(EC_POINT_new(group.get()), EC_POINT_free);
|
|
if (!publicPoint || EC_POINT_mul(
|
|
group.get(),
|
|
publicPoint.get(),
|
|
scalar.get(),
|
|
nullptr,
|
|
nullptr,
|
|
nullptr
|
|
) != 1) {
|
|
fail("EC public key derivation failed");
|
|
}
|
|
const std::size_t publicSize = EC_POINT_point2oct(
|
|
group.get(),
|
|
publicPoint.get(),
|
|
POINT_CONVERSION_UNCOMPRESSED,
|
|
nullptr,
|
|
0,
|
|
nullptr
|
|
);
|
|
Bytes publicKey(publicSize);
|
|
if (publicSize == 0 || EC_POINT_point2oct(
|
|
group.get(),
|
|
publicPoint.get(),
|
|
POINT_CONVERSION_UNCOMPRESSED,
|
|
publicKey.data(),
|
|
publicKey.size(),
|
|
nullptr
|
|
) != publicSize) {
|
|
fail("EC public key encoding failed");
|
|
}
|
|
ParamBldHandle builder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
|
|
if (!builder ||
|
|
OSSL_PARAM_BLD_push_utf8_string(
|
|
builder.get(),
|
|
OSSL_PKEY_PARAM_GROUP_NAME,
|
|
curveName(curve),
|
|
0
|
|
) != 1 ||
|
|
OSSL_PARAM_BLD_push_BN(
|
|
builder.get(), OSSL_PKEY_PARAM_PRIV_KEY, scalar.get()
|
|
) != 1 ||
|
|
OSSL_PARAM_BLD_push_octet_string(
|
|
builder.get(),
|
|
OSSL_PKEY_PARAM_PUB_KEY,
|
|
publicKey.data(),
|
|
publicKey.size()
|
|
) != 1) {
|
|
fail("failed to prepare EC private key");
|
|
}
|
|
return {
|
|
fromData("EC", EVP_PKEY_KEYPAIR, builder.get()),
|
|
std::move(publicKey)
|
|
};
|
|
}
|
|
|
|
KeyPair rawKeyPair(const char* algorithm) {
|
|
EvpPkeyHandle key(
|
|
EVP_PKEY_Q_keygen(nullptr, nullptr, algorithm), EVP_PKEY_free
|
|
);
|
|
if (!key) fail(std::string(algorithm) + " key generation failed");
|
|
KeyPair result {Bytes(32), Bytes(32)};
|
|
std::size_t privateSize = result.privateKey.size();
|
|
std::size_t publicSize = result.publicKey.size();
|
|
if (EVP_PKEY_get_raw_private_key(
|
|
key.get(), result.privateKey.data(), &privateSize
|
|
) != 1 ||
|
|
EVP_PKEY_get_raw_public_key(
|
|
key.get(), result.publicKey.data(), &publicSize
|
|
) != 1) {
|
|
fail(std::string(algorithm) + " key export failed");
|
|
}
|
|
result.privateKey.resize(privateSize);
|
|
result.publicKey.resize(publicSize);
|
|
return result;
|
|
}
|
|
|
|
Bytes rawPublicFromPrivate(
|
|
int type, std::string_view privateKey, const char* name
|
|
) {
|
|
auto key = rawPrivateKey(type, privateKey, name);
|
|
Bytes result(32);
|
|
std::size_t size = result.size();
|
|
if (EVP_PKEY_get_raw_public_key(key.get(), result.data(), &size) !=
|
|
1) {
|
|
fail(std::string(name) + " public key derivation failed");
|
|
}
|
|
result.resize(size);
|
|
return result;
|
|
}
|
|
|
|
Bytes signDigest(
|
|
EVP_PKEY* key, const EVP_MD* md, std::string_view message
|
|
) {
|
|
EvpMdCtxHandle ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
|
if (!ctx ||
|
|
EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, key) != 1 ||
|
|
EVP_DigestSignUpdate(
|
|
ctx.get(), message.data(), message.size()
|
|
) != 1) {
|
|
fail("signature initialization failed");
|
|
}
|
|
std::size_t size = 0;
|
|
if (EVP_DigestSignFinal(ctx.get(), nullptr, &size) != 1) {
|
|
fail("signature size query failed");
|
|
}
|
|
Bytes signature(size);
|
|
if (EVP_DigestSignFinal(ctx.get(), signature.data(), &size) != 1) {
|
|
fail("signing failed");
|
|
}
|
|
signature.resize(size);
|
|
return signature;
|
|
}
|
|
}
|
|
|
|
struct HashContext::Impl {
|
|
explicit Impl(std::string_view name)
|
|
: digest(getDigest(name)), ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free) {
|
|
if (!ctx || EVP_DigestInit_ex(ctx.get(), digest, nullptr) != 1) {
|
|
fail("hash context initialization failed");
|
|
}
|
|
}
|
|
|
|
const EVP_MD* digest;
|
|
EvpMdCtxHandle ctx;
|
|
bool finalized = false;
|
|
};
|
|
|
|
HashContext::HashContext(std::string_view hash)
|
|
: impl(std::make_unique<Impl>(hash)) {
|
|
}
|
|
|
|
HashContext::~HashContext() = default;
|
|
HashContext::HashContext(HashContext&&) noexcept = default;
|
|
HashContext& HashContext::operator=(HashContext&&) noexcept = default;
|
|
|
|
void HashContext::update(std::string_view data) {
|
|
if (impl->finalized) {
|
|
throw Error(ErrorCode::InvalidState, "hash context is finalized");
|
|
}
|
|
if (EVP_DigestUpdate(impl->ctx.get(), data.data(), data.size()) != 1) {
|
|
fail("hash update failed");
|
|
}
|
|
}
|
|
|
|
Bytes HashContext::final() {
|
|
if (impl->finalized) {
|
|
throw Error(ErrorCode::InvalidState, "hash context is finalized");
|
|
}
|
|
Bytes output(static_cast<std::size_t>(EVP_MD_size(impl->digest)));
|
|
unsigned int size = 0;
|
|
if (EVP_DigestFinal_ex(impl->ctx.get(), output.data(), &size) != 1) {
|
|
fail("hash finalization failed");
|
|
}
|
|
impl->finalized = true;
|
|
output.resize(size);
|
|
return output;
|
|
}
|
|
|
|
void HashContext::reset() {
|
|
if (EVP_DigestInit_ex(impl->ctx.get(), impl->digest, nullptr) != 1) {
|
|
fail("hash reset failed");
|
|
}
|
|
impl->finalized = false;
|
|
}
|
|
|
|
Bytes digest(std::string_view hash, std::string_view data) {
|
|
const EVP_MD* md = getDigest(hash);
|
|
Bytes output(static_cast<std::size_t>(EVP_MD_size(md)));
|
|
unsigned int size = 0;
|
|
if (EVP_Digest(
|
|
data.data(), data.size(), output.data(), &size, md, nullptr
|
|
) != 1) {
|
|
fail("digest calculation failed");
|
|
}
|
|
output.resize(size);
|
|
return output;
|
|
}
|
|
|
|
Bytes hmac(
|
|
std::string_view hash, std::string_view key, std::string_view data
|
|
) {
|
|
const EVP_MD* md = getShaDigest(hash);
|
|
Bytes output(static_cast<std::size_t>(EVP_MD_size(md)));
|
|
unsigned int size = 0;
|
|
if (!HMAC(
|
|
md,
|
|
key.data(),
|
|
checkedSize(key.size(), "HMAC key"),
|
|
bytes(data),
|
|
data.size(),
|
|
output.data(),
|
|
&size
|
|
)) {
|
|
fail("HMAC calculation failed");
|
|
}
|
|
output.resize(size);
|
|
return output;
|
|
}
|
|
|
|
KeyPair ed25519KeyPair() {
|
|
return rawKeyPair("ED25519");
|
|
}
|
|
|
|
Bytes ed25519Public(std::string_view privateKey) {
|
|
return rawPublicFromPrivate(EVP_PKEY_ED25519, privateKey, "Ed25519");
|
|
}
|
|
|
|
Bytes ed25519Sign(std::string_view privateKey, std::string_view message) {
|
|
auto key = rawPrivateKey(EVP_PKEY_ED25519, privateKey, "Ed25519");
|
|
EvpMdCtxHandle ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
|
if (!ctx || EVP_DigestSignInit(
|
|
ctx.get(), nullptr, nullptr, nullptr, key.get()
|
|
) != 1) {
|
|
fail("Ed25519 signing initialization failed");
|
|
}
|
|
std::size_t size = 0;
|
|
if (EVP_DigestSign(
|
|
ctx.get(), nullptr, &size, bytes(message), message.size()
|
|
) != 1) {
|
|
fail("Ed25519 signature size query failed");
|
|
}
|
|
Bytes signature(size);
|
|
if (EVP_DigestSign(
|
|
ctx.get(),
|
|
signature.data(),
|
|
&size,
|
|
bytes(message),
|
|
message.size()
|
|
) != 1) {
|
|
fail("Ed25519 signing failed");
|
|
}
|
|
signature.resize(size);
|
|
return signature;
|
|
}
|
|
|
|
bool ed25519Verify(
|
|
std::string_view publicKey,
|
|
std::string_view message,
|
|
std::string_view signature
|
|
) {
|
|
if (signature.size() != 64) {
|
|
throw Error(
|
|
ErrorCode::InvalidSignature,
|
|
"Ed25519 signature must be 64 bytes"
|
|
);
|
|
}
|
|
auto key = rawPublicKey(EVP_PKEY_ED25519, publicKey, "Ed25519");
|
|
EvpMdCtxHandle ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
|
if (!ctx || EVP_DigestVerifyInit(
|
|
ctx.get(), nullptr, nullptr, nullptr, key.get()
|
|
) != 1) {
|
|
fail("Ed25519 verification initialization failed");
|
|
}
|
|
const int result = EVP_DigestVerify(
|
|
ctx.get(),
|
|
bytes(signature),
|
|
signature.size(),
|
|
bytes(message),
|
|
message.size()
|
|
);
|
|
if (result < 0) {
|
|
throw Error(
|
|
ErrorCode::InvalidSignature,
|
|
opensslError("invalid Ed25519 signature")
|
|
);
|
|
}
|
|
if (result == 0) ERR_clear_error();
|
|
return result == 1;
|
|
}
|
|
|
|
KeyPair ecdsaKeyPair(std::string_view curve) {
|
|
const std::size_t privateSize = curvePrivateSize(curve);
|
|
EvpPkeyHandle key(
|
|
EVP_PKEY_Q_keygen(nullptr, nullptr, "EC", curveName(curve)),
|
|
EVP_PKEY_free
|
|
);
|
|
if (!key) fail("ECDSA key generation failed");
|
|
BIGNUM* rawPrivate = nullptr;
|
|
if (EVP_PKEY_get_bn_param(
|
|
key.get(), OSSL_PKEY_PARAM_PRIV_KEY, &rawPrivate
|
|
) != 1) {
|
|
fail("ECDSA private key export failed");
|
|
}
|
|
BnHandle privateKey(rawPrivate, BN_free);
|
|
KeyPair result {Bytes(privateSize), {}};
|
|
if (BN_bn2binpad(
|
|
privateKey.get(),
|
|
result.privateKey.data(),
|
|
checkedSize(privateSize, "private key")
|
|
) != static_cast<int>(privateSize)) {
|
|
fail("ECDSA private key encoding failed");
|
|
}
|
|
result.publicKey = ecPrivateKey(
|
|
curve,
|
|
std::string_view(
|
|
reinterpret_cast<const char*>(result.privateKey.data()),
|
|
result.privateKey.size()
|
|
)
|
|
).publicKey;
|
|
return result;
|
|
}
|
|
|
|
Bytes ecdsaPublic(std::string_view curve, std::string_view privateKey) {
|
|
return ecPrivateKey(curve, privateKey).publicKey;
|
|
}
|
|
|
|
Bytes ecdsaSign(
|
|
std::string_view curve,
|
|
std::string_view privateKey,
|
|
std::string_view message,
|
|
std::string_view hash
|
|
) {
|
|
auto key = ecPrivateKey(curve, privateKey);
|
|
return signDigest(key.key.get(), getShaDigest(hash), message);
|
|
}
|
|
|
|
bool ecdsaVerify(
|
|
std::string_view curve,
|
|
std::string_view publicKey,
|
|
std::string_view message,
|
|
std::string_view signature,
|
|
std::string_view hash
|
|
) {
|
|
auto key = makeEcdsaPublicKey(curve, publicKey);
|
|
return verifyDigestSignature(
|
|
key.get(), getShaDigest(hash), message, signature, 0, 0
|
|
);
|
|
}
|
|
|
|
bool rsaPkcs1Verify(
|
|
std::string_view hash,
|
|
std::string_view modulus,
|
|
std::string_view exponent,
|
|
std::string_view message,
|
|
std::string_view signature
|
|
) {
|
|
auto key = makeRsaPublicKey(modulus, exponent);
|
|
return verifyDigestSignature(
|
|
key.get(),
|
|
getShaDigest(hash),
|
|
message,
|
|
signature,
|
|
RSA_PKCS1_PADDING,
|
|
0
|
|
);
|
|
}
|
|
|
|
bool rsaPssVerify(
|
|
std::string_view hash,
|
|
std::string_view modulus,
|
|
std::string_view exponent,
|
|
std::string_view message,
|
|
std::string_view signature,
|
|
int saltLength
|
|
) {
|
|
if (saltLength < -1) {
|
|
throw Error(
|
|
ErrorCode::InvalidArgument,
|
|
"RSA-PSS salt length must be -1 or non-negative"
|
|
);
|
|
}
|
|
auto key = makeRsaPublicKey(modulus, exponent);
|
|
return verifyDigestSignature(
|
|
key.get(),
|
|
getShaDigest(hash),
|
|
message,
|
|
signature,
|
|
RSA_PKCS1_PSS_PADDING,
|
|
saltLength == -1 ? RSA_PSS_SALTLEN_DIGEST : saltLength
|
|
);
|
|
}
|
|
|
|
KeyPair x25519KeyPair() {
|
|
return rawKeyPair("X25519");
|
|
}
|
|
|
|
Bytes x25519Public(std::string_view privateKey) {
|
|
return rawPublicFromPrivate(EVP_PKEY_X25519, privateKey, "X25519");
|
|
}
|
|
|
|
Bytes x25519(std::string_view privateKey, std::string_view peerPublicKey) {
|
|
auto key = rawPrivateKey(EVP_PKEY_X25519, privateKey, "X25519");
|
|
auto peer = rawPublicKey(EVP_PKEY_X25519, peerPublicKey, "X25519");
|
|
EvpPkeyCtxHandle ctx(
|
|
EVP_PKEY_CTX_new(key.get(), nullptr), EVP_PKEY_CTX_free
|
|
);
|
|
if (!ctx || EVP_PKEY_derive_init(ctx.get()) != 1 ||
|
|
EVP_PKEY_derive_set_peer(ctx.get(), peer.get()) != 1) {
|
|
fail("X25519 key exchange initialization failed");
|
|
}
|
|
Bytes output(32);
|
|
std::size_t size = output.size();
|
|
if (EVP_PKEY_derive(ctx.get(), output.data(), &size) != 1) {
|
|
fail("X25519 key exchange failed");
|
|
}
|
|
output.resize(size);
|
|
const Bytes zero(output.size(), 0);
|
|
if (constantTimeEqual(
|
|
std::string_view(
|
|
reinterpret_cast<const char*>(output.data()), output.size()
|
|
),
|
|
std::string_view(
|
|
reinterpret_cast<const char*>(zero.data()), zero.size()
|
|
)
|
|
)) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey,
|
|
"X25519 produced an all-zero shared secret"
|
|
);
|
|
}
|
|
return output;
|
|
}
|
|
|
|
Bytes p256Public(std::string_view privateKey) {
|
|
return ecPrivateKey("P-256", privateKey).publicKey;
|
|
}
|
|
|
|
Bytes p256Shared(
|
|
std::string_view privateKey, std::string_view peerPublicKey
|
|
) {
|
|
if (peerPublicKey.size() != 65 ||
|
|
static_cast<unsigned char>(peerPublicKey[0]) != 0x04) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey,
|
|
"P-256 public key must be a 65-byte uncompressed SEC1 point"
|
|
);
|
|
}
|
|
auto key = ecPrivateKey("P-256", privateKey);
|
|
auto peer = makeEcdsaPublicKey("P-256", peerPublicKey);
|
|
EvpPkeyCtxHandle ctx(
|
|
EVP_PKEY_CTX_new(key.key.get(), nullptr), EVP_PKEY_CTX_free
|
|
);
|
|
if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0 ||
|
|
EVP_PKEY_derive_set_peer(ctx.get(), peer.get()) <= 0) {
|
|
fail("P-256 key exchange initialization failed");
|
|
}
|
|
std::size_t size = 0;
|
|
if (EVP_PKEY_derive(ctx.get(), nullptr, &size) <= 0) {
|
|
fail("P-256 shared secret size query failed");
|
|
}
|
|
Bytes output(size);
|
|
if (EVP_PKEY_derive(ctx.get(), output.data(), &size) <= 0) {
|
|
fail("P-256 key exchange failed");
|
|
}
|
|
output.resize(size);
|
|
return output;
|
|
}
|
|
|
|
Bytes aesGcmEncrypt(
|
|
std::string_view key,
|
|
std::string_view nonce,
|
|
std::string_view aad,
|
|
std::string_view plaintext
|
|
) {
|
|
return aeadEncrypt(
|
|
aesGcmCipher(key.size()), key, nonce, aad, plaintext
|
|
);
|
|
}
|
|
|
|
Bytes aesGcmDecrypt(
|
|
std::string_view key,
|
|
std::string_view nonce,
|
|
std::string_view aad,
|
|
std::string_view ciphertextWithTag
|
|
) {
|
|
return aeadDecrypt(
|
|
aesGcmCipher(key.size()), key, nonce, aad, ciphertextWithTag
|
|
);
|
|
}
|
|
|
|
Bytes chacha20Poly1305Encrypt(
|
|
std::string_view key,
|
|
std::string_view nonce,
|
|
std::string_view aad,
|
|
std::string_view plaintext
|
|
) {
|
|
if (key.size() != 32) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey, "ChaCha20-Poly1305 key must be 32 bytes"
|
|
);
|
|
}
|
|
if (nonce.size() != 12) {
|
|
throw Error(
|
|
ErrorCode::InvalidArgument,
|
|
"ChaCha20-Poly1305 nonce must be 12 bytes"
|
|
);
|
|
}
|
|
return aeadEncrypt(EVP_chacha20_poly1305(), key, nonce, aad, plaintext);
|
|
}
|
|
|
|
Bytes chacha20Poly1305Decrypt(
|
|
std::string_view key,
|
|
std::string_view nonce,
|
|
std::string_view aad,
|
|
std::string_view ciphertextWithTag
|
|
) {
|
|
if (key.size() != 32) {
|
|
throw Error(
|
|
ErrorCode::InvalidKey, "ChaCha20-Poly1305 key must be 32 bytes"
|
|
);
|
|
}
|
|
if (nonce.size() != 12) {
|
|
throw Error(
|
|
ErrorCode::InvalidArgument,
|
|
"ChaCha20-Poly1305 nonce must be 12 bytes"
|
|
);
|
|
}
|
|
return aeadDecrypt(
|
|
EVP_chacha20_poly1305(), key, nonce, aad, ciphertextWithTag
|
|
);
|
|
}
|
|
|
|
Bytes randomBytes(std::size_t length) {
|
|
checkedSize(length, "random byte count");
|
|
Bytes output(length);
|
|
if (length != 0 &&
|
|
RAND_bytes(output.data(), static_cast<int>(length)) != 1) {
|
|
fail("secure random generation failed");
|
|
}
|
|
return output;
|
|
}
|
|
|
|
bool constantTimeEqual(std::string_view left, std::string_view right) {
|
|
return left.size() == right.size() &&
|
|
(left.empty() ||
|
|
CRYPTO_memcmp(left.data(), right.data(), left.size()) == 0);
|
|
}
|
|
|
|
Bytes hkdfExtract(
|
|
std::string_view hash, std::string_view salt, std::string_view ikm
|
|
) {
|
|
const EVP_MD* md = getShaDigest(hash);
|
|
EvpPkeyCtxHandle ctx(
|
|
EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr), EVP_PKEY_CTX_free
|
|
);
|
|
Bytes output(static_cast<std::size_t>(EVP_MD_size(md)));
|
|
std::size_t size = output.size();
|
|
if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0 ||
|
|
EVP_PKEY_CTX_set_hkdf_mode(
|
|
ctx.get(), EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY
|
|
) <= 0 ||
|
|
EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md) <= 0 ||
|
|
EVP_PKEY_CTX_set1_hkdf_salt(
|
|
ctx.get(), bytes(salt), checkedSize(salt.size(), "HKDF salt")
|
|
) <= 0 ||
|
|
EVP_PKEY_CTX_set1_hkdf_key(
|
|
ctx.get(),
|
|
bytes(ikm),
|
|
checkedSize(ikm.size(), "HKDF input key material")
|
|
) <= 0 ||
|
|
EVP_PKEY_derive(ctx.get(), output.data(), &size) <= 0) {
|
|
fail("HKDF extract failed");
|
|
}
|
|
output.resize(size);
|
|
return output;
|
|
}
|
|
|
|
Bytes hkdfExpand(
|
|
std::string_view hash,
|
|
std::string_view prk,
|
|
std::string_view info,
|
|
std::size_t length
|
|
) {
|
|
const EVP_MD* md = getShaDigest(hash);
|
|
const std::size_t maxLength =
|
|
static_cast<std::size_t>(EVP_MD_size(md)) * 255;
|
|
if (length > maxLength) {
|
|
throw Error(
|
|
ErrorCode::OutputTooLarge,
|
|
"HKDF output exceeds 255 digest blocks"
|
|
);
|
|
}
|
|
EvpPkeyCtxHandle ctx(
|
|
EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr), EVP_PKEY_CTX_free
|
|
);
|
|
Bytes output(length);
|
|
std::size_t size = output.size();
|
|
if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0 ||
|
|
EVP_PKEY_CTX_set_hkdf_mode(
|
|
ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY
|
|
) <= 0 ||
|
|
EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md) <= 0 ||
|
|
EVP_PKEY_CTX_set1_hkdf_key(
|
|
ctx.get(),
|
|
bytes(prk),
|
|
checkedSize(prk.size(), "HKDF pseudorandom key")
|
|
) <= 0 ||
|
|
(!info.empty() &&
|
|
EVP_PKEY_CTX_add1_hkdf_info(
|
|
ctx.get(), bytes(info), checkedSize(info.size(), "HKDF info")
|
|
) <= 0) ||
|
|
(length != 0 &&
|
|
EVP_PKEY_derive(ctx.get(), output.data(), &size) <= 0)) {
|
|
fail("HKDF expand failed");
|
|
}
|
|
output.resize(size);
|
|
return output;
|
|
}
|
|
|
|
Bytes pbkdf2(
|
|
std::string_view hash,
|
|
std::string_view password,
|
|
std::string_view salt,
|
|
std::uint32_t iterations,
|
|
std::size_t length
|
|
) {
|
|
if (iterations == 0 ||
|
|
iterations > static_cast<std::uint32_t>(INT_MAX)) {
|
|
throw Error(
|
|
ErrorCode::InvalidArgument,
|
|
"PBKDF2 iterations must be between 1 and INT_MAX"
|
|
);
|
|
}
|
|
const EVP_MD* md = getShaDigest(hash);
|
|
const int outputSize = checkedSize(length, "PBKDF2 output");
|
|
Bytes output(length);
|
|
if (length != 0 && PKCS5_PBKDF2_HMAC(
|
|
password.data(),
|
|
checkedSize(password.size(), "password"),
|
|
bytes(salt),
|
|
checkedSize(salt.size(), "salt"),
|
|
static_cast<int>(iterations),
|
|
md,
|
|
outputSize,
|
|
output.data()
|
|
) != 1) {
|
|
fail("PBKDF2 failed");
|
|
}
|
|
return output;
|
|
}
|
|
|
|
Bytes scrypt(
|
|
std::string_view password,
|
|
std::string_view salt,
|
|
std::uint64_t n,
|
|
std::uint64_t r,
|
|
std::uint64_t p,
|
|
std::size_t length,
|
|
std::uint64_t maxMemory
|
|
) {
|
|
if (n <= 1 || (n & (n - 1)) != 0 || r == 0 || p == 0) {
|
|
throw Error(
|
|
ErrorCode::InvalidArgument,
|
|
"scrypt requires power-of-two N > 1 and positive r and p"
|
|
);
|
|
}
|
|
checkedSize(length, "scrypt output");
|
|
Bytes output(length);
|
|
if (length != 0 && EVP_PBE_scrypt(
|
|
password.data(),
|
|
password.size(),
|
|
bytes(salt),
|
|
salt.size(),
|
|
n,
|
|
r,
|
|
p,
|
|
maxMemory,
|
|
output.data(),
|
|
output.size()
|
|
) != 1) {
|
|
fail("scrypt failed");
|
|
}
|
|
return output;
|
|
}
|
|
|
|
const char* backendVersion() {
|
|
return OpenSSL_version(OPENSSL_VERSION);
|
|
}
|
|
|
|
Features features() {
|
|
auto hasDigest = [](const char* name) {
|
|
EVP_MD* algorithm = EVP_MD_fetch(nullptr, name, nullptr);
|
|
const bool available = algorithm != nullptr;
|
|
EVP_MD_free(algorithm);
|
|
ERR_clear_error();
|
|
return available;
|
|
};
|
|
auto hasCipher = [](const char* name) {
|
|
EVP_CIPHER* algorithm = EVP_CIPHER_fetch(nullptr, name, nullptr);
|
|
const bool available = algorithm != nullptr;
|
|
EVP_CIPHER_free(algorithm);
|
|
ERR_clear_error();
|
|
return available;
|
|
};
|
|
auto hasKeyAlgorithm = [](const char* name) {
|
|
EvpPkeyCtxHandle ctx(
|
|
EVP_PKEY_CTX_new_from_name(nullptr, name, nullptr),
|
|
EVP_PKEY_CTX_free
|
|
);
|
|
const bool available = ctx != nullptr;
|
|
ERR_clear_error();
|
|
return available;
|
|
};
|
|
auto hasKdf = [](const char* name) {
|
|
EVP_KDF* algorithm = EVP_KDF_fetch(nullptr, name, nullptr);
|
|
const bool available = algorithm != nullptr;
|
|
EVP_KDF_free(algorithm);
|
|
ERR_clear_error();
|
|
return available;
|
|
};
|
|
EVP_MAC* hmacAlgorithm = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
|
|
const bool hasHmac = hmacAlgorithm != nullptr;
|
|
EVP_MAC_free(hmacAlgorithm);
|
|
ERR_clear_error();
|
|
const bool hasEc = hasKeyAlgorithm("EC");
|
|
return {
|
|
hasDigest("SHA256"),
|
|
hasDigest("SHA384"),
|
|
hasDigest("SHA512"),
|
|
hasDigest("MD5"),
|
|
hasHmac,
|
|
hasKeyAlgorithm("ED25519"),
|
|
hasEc,
|
|
hasKeyAlgorithm("RSA"),
|
|
hasKeyAlgorithm("X25519"),
|
|
hasEc,
|
|
hasCipher("AES-128-GCM"),
|
|
hasCipher("AES-256-GCM"),
|
|
hasCipher("CHACHA20-POLY1305"),
|
|
RAND_status() == 1,
|
|
hasKdf("HKDF"),
|
|
hasKdf("PBKDF2"),
|
|
hasKdf("SCRYPT")
|
|
};
|
|
}
|
|
}
|