#include "crypto/Crypto.hpp" #include #include #include #include #include #include #include namespace { using PkeyHandle = std::unique_ptr; using MdCtxHandle = std::unique_ptr; crypto::Bytes hex(const std::string& value) { crypto::Bytes result; result.reserve(value.size() / 2); for (std::size_t i = 0; i < value.size(); i += 2) { result.push_back( static_cast( std::stoul(value.substr(i, 2), nullptr, 16) ) ); } return result; } std::string binary(const crypto::Bytes& value) { return {reinterpret_cast(value.data()), value.size()}; } crypto::Bytes sign( EVP_PKEY* key, const EVP_MD* digest, std::string_view message, int rsaPadding = 0, int saltLength = 0 ) { MdCtxHandle ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); EVP_PKEY_CTX* pkeyCtx = nullptr; EXPECT_NE(ctx, nullptr); EXPECT_EQ( EVP_DigestSignInit(ctx.get(), &pkeyCtx, digest, nullptr, key), 1 ); if (rsaPadding != 0) { EXPECT_GT(EVP_PKEY_CTX_set_rsa_padding(pkeyCtx, rsaPadding), 0); if (rsaPadding == RSA_PKCS1_PSS_PADDING) { EXPECT_GT( EVP_PKEY_CTX_set_rsa_pss_saltlen(pkeyCtx, saltLength), 0 ); } } EXPECT_EQ( EVP_DigestSignUpdate(ctx.get(), message.data(), message.size()), 1 ); std::size_t size = 0; EXPECT_EQ(EVP_DigestSignFinal(ctx.get(), nullptr, &size), 1); crypto::Bytes signature(size); EXPECT_EQ(EVP_DigestSignFinal(ctx.get(), signature.data(), &size), 1); signature.resize(size); return signature; } crypto::Bytes keyInteger(EVP_PKEY* key, const char* name) { BIGNUM* raw = nullptr; EXPECT_EQ(EVP_PKEY_get_bn_param(key, name, &raw), 1); std::unique_ptr value(raw, BN_free); crypto::Bytes result( static_cast(BN_num_bytes(value.get())) ); BN_bn2bin(value.get(), result.data()); return result; } } TEST(Crypto, HashesAndHmacMatchKnownVectors) { EXPECT_EQ( crypto::digest("SHA256", "abc"), hex("ba7816bf8f01cfea414140de5dae2223" "b00361a396177a9cb410ff61f20015ad") ); EXPECT_EQ( crypto::digest("SHA384", "abc"), hex("cb00753f45a35e8bb5a03d699ac65007" "272c32ab0eded1631a8b605a43ff5bed" "8086072ba1e7cc2358baeca134c825a7") ); EXPECT_EQ( crypto::digest("SHA512", "abc"), hex("ddaf35a193617abacc417349ae204131" "12e6fa4e89a97ea20a9eeee64b55d39" "a2192992a274fc1a836ba3c23a3feebbd" "454d4423643ce80e2a9ac94fa54ca49f") ); EXPECT_EQ( crypto::digest("MD5", "abc"), hex("900150983cd24fb0d6963f7d28e17f72") ); const crypto::Bytes key(20, 0x0b); EXPECT_EQ( crypto::hmac("SHA256", binary(key), "Hi There"), hex("b0344c61d8db38535ca8afceaf0bf12b" "881dc200c9833da726e9376c2e32cff7") ); } TEST(Crypto, StreamingHashCanBeResetAndRejectsUpdateAfterFinal) { crypto::HashContext context("SHA256"); context.update("a"); context.update(std::string("b\0c", 3)); EXPECT_EQ( context.final(), crypto::digest("SHA256", std::string("ab\0c", 4)) ); try { context.update("more"); FAIL() << "update after final must fail"; } catch (const crypto::Error& error) { EXPECT_EQ(error.code(), crypto::ErrorCode::InvalidState); } context.reset(); context.update("abc"); EXPECT_EQ(context.final(), crypto::digest("SHA256", "abc")); } TEST(Crypto, Ed25519MatchesRfc8032Vector) { auto publicKey = hex("d75a980182b10ab7d54bfed3c964073a" "0ee172f3daa62325af021a68f707511a"); auto signature = hex("e5564300c360ac729086e2cc806e828a" "84877f1eb8e5d974d873e06522490155" "5fb8821590a33bacc61e39701cf9b46b" "d25bf5f0595bbe24655141438e7a100b"); EXPECT_TRUE( crypto::ed25519Verify(binary(publicKey), "", binary(signature)) ); signature[0] ^= 1; EXPECT_FALSE( crypto::ed25519Verify(binary(publicKey), "", binary(signature)) ); } TEST(Crypto, Ed25519GeneratesKeysAndSigns) { const auto keyPair = crypto::ed25519KeyPair(); ASSERT_EQ(keyPair.privateKey.size(), std::size_t {32}); ASSERT_EQ(keyPair.publicKey.size(), std::size_t {32}); EXPECT_EQ( crypto::ed25519Public(binary(keyPair.privateKey)), keyPair.publicKey ); const auto signature = crypto::ed25519Sign(binary(keyPair.privateKey), std::string("a\0b", 3)); EXPECT_TRUE( crypto::ed25519Verify( binary(keyPair.publicKey), std::string("a\0b", 3), binary(signature) ) ); } TEST(Crypto, EcdsaVerifiesAllSupportedCurves) { struct Case { const char* curve; const char* opensslCurve; const EVP_MD* digest; const char* hash; }; const Case cases[] { {"P-256", "prime256v1", EVP_sha256(), "SHA256"}, {"P-384", "secp384r1", EVP_sha384(), "SHA384"}, {"P-521", "secp521r1", EVP_sha512(), "SHA512"} }; for (const auto& item : cases) { SCOPED_TRACE(item.curve); PkeyHandle key( EVP_PKEY_Q_keygen(nullptr, nullptr, "EC", item.opensslCurve), EVP_PKEY_free ); ASSERT_NE(key, nullptr); std::size_t publicKeySize = 0; ASSERT_EQ( EVP_PKEY_get_octet_string_param( key.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &publicKeySize ), 1 ); crypto::Bytes publicKey(publicKeySize); ASSERT_EQ( EVP_PKEY_get_octet_string_param( key.get(), OSSL_PKEY_PARAM_PUB_KEY, publicKey.data(), publicKey.size(), &publicKeySize ), 1 ); auto signature = sign(key.get(), item.digest, "message"); EXPECT_TRUE( crypto::ecdsaVerify( item.curve, binary(publicKey), "message", binary(signature), item.hash ) ); EXPECT_FALSE( crypto::ecdsaVerify( item.curve, binary(publicKey), "tampered", binary(signature), item.hash ) ); } } TEST(Crypto, EcdsaGeneratesKeysAndSignsOnAllSupportedCurves) { struct Case { const char* curve; const char* hash; std::size_t privateSize; std::size_t publicSize; }; const Case cases[] { {"P-256", "SHA256", 32, 65}, {"P-384", "SHA384", 48, 97}, {"P-521", "SHA512", 66, 133} }; for (const auto& item : cases) { SCOPED_TRACE(item.curve); const auto keyPair = crypto::ecdsaKeyPair(item.curve); EXPECT_EQ(keyPair.privateKey.size(), item.privateSize); EXPECT_EQ(keyPair.publicKey.size(), item.publicSize); EXPECT_EQ( crypto::ecdsaPublic(item.curve, binary(keyPair.privateKey)), keyPair.publicKey ); const auto signature = crypto::ecdsaSign( item.curve, binary(keyPair.privateKey), "message", item.hash ); EXPECT_TRUE( crypto::ecdsaVerify( item.curve, binary(keyPair.publicKey), "message", binary(signature), item.hash ) ); } } TEST(Crypto, RsaVerifiesPkcs1AndPssSignatures) { PkeyHandle key( EVP_PKEY_Q_keygen(nullptr, nullptr, "RSA", 2048), EVP_PKEY_free ); ASSERT_NE(key, nullptr); const auto modulus = keyInteger(key.get(), OSSL_PKEY_PARAM_RSA_N); const auto exponent = keyInteger(key.get(), OSSL_PKEY_PARAM_RSA_E); auto pkcs1 = sign(key.get(), EVP_sha256(), "message", RSA_PKCS1_PADDING); EXPECT_TRUE( crypto::rsaPkcs1Verify( "SHA256", binary(modulus), binary(exponent), "message", binary(pkcs1) ) ); EXPECT_FALSE( crypto::rsaPkcs1Verify( "SHA256", binary(modulus), binary(exponent), "tampered", binary(pkcs1) ) ); auto pss = sign( key.get(), EVP_sha256(), "message", RSA_PKCS1_PSS_PADDING, RSA_PSS_SALTLEN_DIGEST ); EXPECT_TRUE( crypto::rsaPssVerify( "SHA256", binary(modulus), binary(exponent), "message", binary(pss), -1 ) ); EXPECT_FALSE( crypto::rsaPssVerify( "SHA256", binary(modulus), binary(exponent), "tampered", binary(pss), -1 ) ); } TEST(Crypto, X25519MatchesRfc7748PublicKeyVector) { const auto privateKey = hex("77076d0a7318a57d3c16c17251b26645" "df4c2f87ebc0992ab177fba51db92c2a"); EXPECT_EQ( crypto::x25519Public(binary(privateKey)), hex("8520f0098930a754748b7ddcb43ef75a0" "dbf3a0d26381af4eba4a98eaa9b4e6a") ); } TEST(Crypto, X25519GeneratedKeyPairsDeriveTheSameSecret) { const auto alice = crypto::x25519KeyPair(); const auto bob = crypto::x25519KeyPair(); EXPECT_EQ(alice.privateKey.size(), std::size_t {32}); EXPECT_EQ(alice.publicKey.size(), std::size_t {32}); EXPECT_EQ(crypto::x25519Public(binary(alice.privateKey)), alice.publicKey); EXPECT_EQ( crypto::x25519(binary(alice.privateKey), binary(bob.publicKey)), crypto::x25519(binary(bob.privateKey), binary(alice.publicKey)) ); } TEST(Crypto, P256DerivesPublicKeyAndSharedSecret) { crypto::Bytes privateA(32, 0); crypto::Bytes privateB(32, 0); privateA.back() = 1; privateB.back() = 2; const auto publicA = crypto::p256Public(binary(privateA)); const auto publicB = crypto::p256Public(binary(privateB)); EXPECT_EQ( publicA, hex("046b17d1f2e12c4247f8bce6e563a440" "f277037d812deb33a0f4a13945d898c296" "4fe342e2fe1a7f9b8ee7eb4a7c0f9e16" "2bce33576b315ececbb6406837bf51f5") ); EXPECT_EQ( crypto::p256Shared(binary(privateA), binary(publicB)), crypto::p256Shared(binary(privateB), binary(publicA)) ); } TEST(Crypto, AesGcmMatchesKnownVectorAndRejectsTampering) { const crypto::Bytes key(16, 0); const crypto::Bytes nonce(12, 0); const crypto::Bytes plaintext(16, 0); auto ciphertext = crypto::aesGcmEncrypt( binary(key), binary(nonce), "", binary(plaintext) ); EXPECT_EQ( ciphertext, hex("0388dace60b6a392f328c2b971b2fe78" "ab6e47d42cec13bdf53a67b21257bddf") ); EXPECT_EQ( crypto::aesGcmDecrypt( binary(key), binary(nonce), "", binary(ciphertext) ), plaintext ); ciphertext.back() ^= 1; EXPECT_THROW( crypto::aesGcmDecrypt( binary(key), binary(nonce), "", binary(ciphertext) ), std::runtime_error ); } TEST(Crypto, Chacha20Poly1305RoundTripsAndRejectsTampering) { const crypto::Bytes key(32, 0); const crypto::Bytes nonce(12, 0); auto ciphertext = crypto::chacha20Poly1305Encrypt( binary(key), binary(nonce), "aad", "plaintext" ); EXPECT_EQ( binary( crypto::chacha20Poly1305Decrypt( binary(key), binary(nonce), "aad", binary(ciphertext) ) ), "plaintext" ); ciphertext.back() ^= 1; EXPECT_THROW( crypto::chacha20Poly1305Decrypt( binary(key), binary(nonce), "aad", binary(ciphertext) ), std::runtime_error ); } TEST(Crypto, HkdfMatchesRfc5869Vector) { const crypto::Bytes ikm(22, 0x0b); const auto salt = hex("000102030405060708090a0b0c"); const auto info = hex("f0f1f2f3f4f5f6f7f8f9"); const auto prk = crypto::hkdfExtract("SHA256", binary(salt), binary(ikm)); EXPECT_EQ( prk, hex("077709362c2e32df0ddc3f0dc47bba63" "90b6c73bb50f9c3122ec844ad7c2b3e5") ); EXPECT_EQ( crypto::hkdfExpand("SHA256", binary(prk), binary(info), 42), hex("3cb25f25faacd57a90434f64d0362f2a" "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" "34007208d5b887185865") ); } TEST(Crypto, PasswordKdfsMatchKnownVectors) { EXPECT_EQ( crypto::pbkdf2("SHA256", "password", "salt", 1, 32), hex("120fb6cffcf8b32c43e7225256c4f837" "a86548c92ccc35480805987cb70be17b") ); EXPECT_EQ( crypto::scrypt("", "", 16, 1, 1, 64), hex("77d6576238657b203b19ca42c18a0497" "f16b4844e3074ae8dfdffa3fede21442f" "cd0069ded0948f8326a753a0fc81f17e" "8d3e0fb2e0d3628cf35e20c38d18906") ); } TEST(Crypto, ReportsRuntimeCapabilitiesAndStableErrors) { const auto available = crypto::features(); EXPECT_TRUE(available.sha256); EXPECT_TRUE(available.ed25519); EXPECT_TRUE(available.ecdsa); EXPECT_TRUE(available.x25519); EXPECT_TRUE(available.aes128Gcm); EXPECT_TRUE(available.pbkdf2); EXPECT_TRUE(available.scrypt); EXPECT_EQ(crypto::API_VERSION, 1); const crypto::Bytes key(16, 0); const crypto::Bytes nonce(12, 0); auto ciphertext = crypto::aesGcmEncrypt(binary(key), binary(nonce), "", "secret"); ciphertext.back() ^= 1; try { crypto::aesGcmDecrypt( binary(key), binary(nonce), "", binary(ciphertext) ); FAIL() << "tampered ciphertext must fail"; } catch (const crypto::Error& error) { EXPECT_EQ(error.code(), crypto::ErrorCode::AuthenticationFailed); EXPECT_STREQ(error.codeName(), "authentication_failed"); } } TEST(Crypto, UtilitiesHandleBinaryData) { const std::string binaryValue("a\0b", 3); EXPECT_TRUE(crypto::constantTimeEqual(binaryValue, binaryValue)); EXPECT_FALSE(crypto::constantTimeEqual(binaryValue, "a")); EXPECT_FALSE(crypto::constantTimeEqual("left", "lest")); EXPECT_EQ(crypto::randomBytes(32).size(), std::size_t {32}); }