Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EVP API Support for ED25519ph #2144

Merged
merged 3 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ add_library(
evp_extra/p_dsa.c
evp_extra/p_dsa_asn1.c
evp_extra/p_ec_asn1.c
evp_extra/p_ed25519ph.c
evp_extra/p_ed25519_asn1.c
evp_extra/p_hmac_asn1.c
evp_extra/p_kem_asn1.c
Expand Down
277 changes: 277 additions & 0 deletions crypto/evp_extra/evp_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1529,3 +1529,280 @@ TEST(EVPTest, PKEY_asn1_find_str) {
ASSERT_FALSE(ameth);
ASSERT_FALSE(EVP_PKEY_asn1_get0_info(&pkey_id, &pkey_base_id, &pkey_flags, &pinfo, &pem_str, ameth));
}

TEST(EVPTest, ED25519PH) {
const uint8_t message[] = {0x72, 0x61, 0x63, 0x63, 0x6f, 0x6f, 0x6e};
const uint8_t context[] = {0x73, 0x6e, 0x65, 0x61, 0x6b, 0x79};
const uint8_t message_sha512[] = {
0x50, 0xcf, 0x03, 0x79, 0x8c, 0xb2, 0xfb, 0x0f, 0xf1, 0x3d, 0xc6,
0x4c, 0x7c, 0xf0, 0x89, 0x8f, 0xfe, 0x90, 0x9d, 0xfd, 0xa5, 0x22,
0xdd, 0x22, 0xf4, 0x10, 0x8f, 0xa0, 0x1b, 0x8f, 0x29, 0x15, 0x98,
0x60, 0xf2, 0x80, 0x0e, 0x7c, 0x93, 0x3c, 0x7c, 0x6e, 0x4c, 0xb1,
0xf9, 0x3f, 0x33, 0xbe, 0x43, 0xa3, 0xd4, 0x1c, 0x86, 0x92, 0x2b,
0x32, 0xaf, 0x89, 0xa2, 0xa4, 0xa3, 0xe2, 0xf1, 0x92};

bssl::UniquePtr<EVP_PKEY> pkey(nullptr);
bssl::UniquePtr<EVP_PKEY> pubkey(nullptr);
bssl::ScopedCBB marshalled_private_key;
bssl::ScopedCBB marshalled_public_key;
uint8_t signature[ED25519_SIGNATURE_LEN] = {0};
size_t signature_len = ED25519_SIGNATURE_LEN;
uint8_t working_signature[ED25519_SIGNATURE_LEN] = {0};
size_t working_signature_len = ED25519_SIGNATURE_LEN;

{
bssl::UniquePtr<EVP_PKEY_CTX> ctx(
EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519PH, nullptr));
ASSERT_FALSE(EVP_PKEY_keygen_init(ctx.get()));
}

{
bssl::UniquePtr<EVP_PKEY_CTX> ctx(
EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr));
ASSERT_TRUE(EVP_PKEY_keygen_init(ctx.get()));
EVP_PKEY *pkey_ptr = nullptr;
ASSERT_TRUE(EVP_PKEY_keygen(ctx.get(), &pkey_ptr));
ASSERT_NE(pkey_ptr, nullptr);
pkey.reset(pkey_ptr); // now owns pkey_ptr
// marshal the keys
ASSERT_TRUE(CBB_init(marshalled_private_key.get(), 0));
ASSERT_TRUE(CBB_init(marshalled_public_key.get(), 0));
ASSERT_TRUE(
EVP_marshal_private_key(marshalled_private_key.get(), pkey.get()));
ASSERT_TRUE(
EVP_marshal_public_key(marshalled_public_key.get(), pkey.get()));
}

{
uint8_t raw_key[ED25519_PRIVATE_KEY_SEED_LEN];
size_t raw_key_len = sizeof(raw_key);
ASSERT_TRUE(EVP_PKEY_get_raw_private_key(pkey.get(), raw_key, &raw_key_len));

EVP_PKEY *rk = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519PH, nullptr, raw_key, raw_key_len);
ASSERT_TRUE(rk);
pkey.reset(rk);
ASSERT_EQ(EVP_PKEY_ED25519PH, EVP_PKEY_id(pkey.get()));

bssl::ScopedCBB temp;
ASSERT_TRUE(CBB_init(temp.get(), 0));
ASSERT_FALSE(EVP_marshal_private_key(temp.get(), pkey.get()));
}

{
uint8_t raw_key[ED25519_PUBLIC_KEY_LEN];
size_t raw_key_len = sizeof(raw_key);
ASSERT_TRUE(EVP_PKEY_get_raw_public_key(pkey.get(), raw_key, &raw_key_len));

EVP_PKEY *rk = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519PH, nullptr, raw_key, raw_key_len);
ASSERT_TRUE(rk);
pubkey.reset(rk);
ASSERT_EQ(EVP_PKEY_ED25519PH, EVP_PKEY_id(pubkey.get()));

bssl::ScopedCBB temp;
ASSERT_TRUE(CBB_init(temp.get(), 0));
ASSERT_FALSE(EVP_marshal_public_key(temp.get(), pubkey.get()));
}

// prehash signature w/ context gen and verify
skmcgrail marked this conversation as resolved.
Show resolved Hide resolved
{
bssl::UniquePtr<EVP_MD_CTX> md_ctx(EVP_MD_CTX_new());
EVP_PKEY_CTX *pctx = nullptr;

ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pctx, EVP_sha512(), nullptr,
pkey.get()));

ASSERT_TRUE(
EVP_PKEY_CTX_set_signature_context(pctx, context, sizeof(context)));
const uint8_t *sctx = NULL;
size_t sctx_len = 0;
ASSERT_TRUE(EVP_PKEY_CTX_get0_signature_context(pctx, &sctx, &sctx_len));
ASSERT_TRUE(sctx);
ASSERT_NE(sctx, context);
ASSERT_EQ(Bytes(context, sizeof(context)), Bytes(sctx, sctx_len));

ASSERT_TRUE(EVP_DigestSignUpdate(md_ctx.get(), &message[0], 3));
ASSERT_TRUE(
EVP_DigestSignUpdate(md_ctx.get(), &message[3], sizeof(message) - 3));
ASSERT_TRUE(EVP_DigestSignFinal(md_ctx.get(), signature,
&signature_len));
ASSERT_EQ(signature_len, (size_t)ED25519_SIGNATURE_LEN);

ASSERT_TRUE(EVP_DigestVerifyInit(md_ctx.get(), &pctx, EVP_sha512(), nullptr,
pubkey.get()));
ASSERT_TRUE(
EVP_PKEY_CTX_set_signature_context(pctx, context, sizeof(context)));
ASSERT_TRUE(EVP_DigestVerifyUpdate(md_ctx.get(), &message[0], 3));
ASSERT_TRUE(
EVP_DigestVerifyUpdate(md_ctx.get(), &message[3], sizeof(message) - 3));
ASSERT_TRUE(EVP_DigestVerifyFinal(md_ctx.get(),
signature, signature_len));
}

// prehash signature gen and verify w/ context using EVP_PKEY_sign and
// EVP_PKEY_verify directly
{
bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
ASSERT_TRUE(ctx.get());
ASSERT_TRUE(EVP_PKEY_sign_init(ctx.get()));
ASSERT_TRUE(EVP_PKEY_CTX_set_signature_context(ctx.get(), context,
sizeof(context)));
ASSERT_TRUE(EVP_PKEY_sign(ctx.get(), working_signature, &working_signature_len, message_sha512, sizeof(message_sha512)));
ASSERT_EQ(working_signature_len, (size_t)ED25519_SIGNATURE_LEN);

ctx.reset(EVP_PKEY_CTX_new(pubkey.get(), nullptr));
ASSERT_TRUE(ctx.get());
ASSERT_TRUE(EVP_PKEY_verify_init(ctx.get()));
ASSERT_TRUE(EVP_PKEY_CTX_set_signature_context(ctx.get(), context,
sizeof(context)));
ASSERT_TRUE(EVP_PKEY_verify(ctx.get(), working_signature,
working_signature_len, message_sha512,
sizeof(message_sha512)));

ASSERT_EQ(Bytes(signature, signature_len),
Bytes(working_signature, working_signature_len));
}

// prehash signature gen and verify
{
bssl::UniquePtr<EVP_MD_CTX> md_ctx(EVP_MD_CTX_new());
EVP_PKEY_CTX *pctx;

ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pctx, EVP_sha512(), nullptr,
pkey.get()));

const uint8_t *sctx = NULL;
size_t sctx_len = 0;
ASSERT_TRUE(EVP_PKEY_CTX_get0_signature_context(pctx, &sctx, &sctx_len));
ASSERT_EQ(sctx, nullptr);
ASSERT_EQ(sctx_len, (size_t)0);

ASSERT_TRUE(EVP_DigestSignUpdate(md_ctx.get(), &message[0], 3));
ASSERT_TRUE(
EVP_DigestSignUpdate(md_ctx.get(), &message[3], sizeof(message) - 3));
ASSERT_TRUE(EVP_DigestSignFinal(md_ctx.get(), working_signature,
&working_signature_len));
ASSERT_EQ(working_signature_len, (size_t)ED25519_SIGNATURE_LEN);

ASSERT_TRUE(EVP_DigestVerifyInit(md_ctx.get(), nullptr, EVP_sha512(),
nullptr, pubkey.get()));
ASSERT_TRUE(EVP_DigestVerifyUpdate(md_ctx.get(), message, 3));
ASSERT_TRUE(
EVP_DigestVerifyUpdate(md_ctx.get(), &message[3], sizeof(message) - 3));
ASSERT_TRUE(EVP_DigestVerifyFinal(md_ctx.get(), working_signature,
working_signature_len));
}

// Pre-hash signature w/ context should not match Pre-hash signature w/o context
ASSERT_NE(Bytes(signature, signature_len),
Bytes(working_signature, working_signature_len));


// prehash signature gen and verify with EVP_PKEY_sign and EVP_PKEY_verify directly
{
OPENSSL_memcpy(signature, working_signature, working_signature_len);
signature_len = working_signature_len;

bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
ASSERT_TRUE(ctx.get());
ASSERT_TRUE(EVP_PKEY_sign_init(ctx.get()));
ASSERT_TRUE(EVP_PKEY_sign(ctx.get(), working_signature, &working_signature_len, message_sha512, sizeof(message_sha512)));
ASSERT_EQ(working_signature_len, (size_t)ED25519_SIGNATURE_LEN);

ctx.reset(EVP_PKEY_CTX_new(pubkey.get(), nullptr));
ASSERT_TRUE(ctx.get());
ASSERT_TRUE(EVP_PKEY_verify_init(ctx.get()));
ASSERT_TRUE(EVP_PKEY_verify(ctx.get(), working_signature, working_signature_len, message_sha512, sizeof(message_sha512)));

ASSERT_EQ(Bytes(signature, signature_len),
Bytes(working_signature, working_signature_len));
}


{
CBS cbs;
CBS_init(&cbs, CBB_data(marshalled_private_key.get()),
CBB_len(marshalled_private_key.get()));
EVP_PKEY *parsed = EVP_parse_private_key(&cbs);
ASSERT_TRUE(parsed);
pkey.reset(parsed);
ASSERT_EQ(EVP_PKEY_ED25519, EVP_PKEY_id(pkey.get()));
}

{
CBS cbs;
CBS_init(&cbs, CBB_data(marshalled_public_key.get()),
CBB_len(marshalled_public_key.get()));
EVP_PKEY *parsed = EVP_parse_public_key(&cbs);
ASSERT_TRUE(parsed);
pubkey.reset(parsed);
ASSERT_EQ(EVP_PKEY_ED25519, EVP_PKEY_id(pubkey.get()));
}

// pure signature gen and verify
{
bssl::UniquePtr<EVP_MD_CTX> md_ctx(EVP_MD_CTX_new());
ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), nullptr, nullptr, nullptr,
pkey.get()));
ASSERT_TRUE(EVP_DigestSign(md_ctx.get(), working_signature,
&working_signature_len, message, sizeof(message)));
ASSERT_EQ(working_signature_len, (size_t)ED25519_SIGNATURE_LEN);

ASSERT_TRUE(EVP_DigestVerifyInit(md_ctx.get(), nullptr, nullptr, nullptr,
pubkey.get()));
ASSERT_TRUE(EVP_DigestVerify(md_ctx.get(), working_signature,
working_signature_len, message, sizeof(message)));
}

// pure signature shouldn't match a pre-hash signature w/o context
ASSERT_NE(Bytes(signature, signature_len),
Bytes(working_signature, working_signature_len));
}

TEST(EVPTest, Ed25519phTestVectors) {
FileTestGTest("crypto/fipsmodule/curve25519/ed25519ph_tests.txt", [](FileTest *t) {
std::vector<uint8_t> seed, q, message, context, expected_signature;
ASSERT_TRUE(t->GetBytes(&seed, "SEED"));
ASSERT_EQ(32u, seed.size());
ASSERT_TRUE(t->GetBytes(&q, "Q"));
ASSERT_EQ(32u, q.size());
ASSERT_TRUE(t->GetBytes(&message, "MESSAGE"));
ASSERT_TRUE(t->GetBytes(&expected_signature, "SIGNATURE"));
ASSERT_EQ(64u, expected_signature.size());

if (t->HasAttribute("CONTEXT")) {
t->GetBytes(&context, "CONTEXT");
} else {
context = std::vector<uint8_t>();
}

bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519PH, nullptr, seed.data(), seed.size()));
bssl::UniquePtr<EVP_PKEY> pubkey(EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519PH, nullptr, q.data(), q.size()));
ASSERT_TRUE(pkey.get());
ASSERT_TRUE(pubkey.get());
ASSERT_EQ(EVP_PKEY_ED25519PH, EVP_PKEY_id(pkey.get()));
ASSERT_EQ(EVP_PKEY_ED25519PH, EVP_PKEY_id(pubkey.get()));

bssl::UniquePtr<EVP_MD_CTX> md_ctx(EVP_MD_CTX_new());
EVP_PKEY_CTX *pctx = nullptr;
uint8_t signature[ED25519_SIGNATURE_LEN] = {};
size_t signature_len = ED25519_SIGNATURE_LEN;

ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pctx, EVP_sha512(), nullptr,
pkey.get()));
ASSERT_TRUE(
EVP_PKEY_CTX_set_signature_context(pctx, context.data(), context.size()));
ASSERT_TRUE(EVP_DigestSignUpdate(md_ctx.get(), message.data(), message.size()));
ASSERT_TRUE(EVP_DigestSignFinal(md_ctx.get(), signature,
&signature_len));
ASSERT_EQ(signature_len, (size_t)ED25519_SIGNATURE_LEN);
ASSERT_EQ(Bytes(expected_signature), Bytes(signature, signature_len));

ASSERT_TRUE(EVP_DigestVerifyInit(md_ctx.get(), &pctx, EVP_sha512(), nullptr,
pubkey.get()));
ASSERT_TRUE(
EVP_PKEY_CTX_set_signature_context(pctx, context.data(), context.size()));
ASSERT_TRUE(EVP_DigestVerifyUpdate(md_ctx.get(), message.data(), message.size()));
ASSERT_TRUE(EVP_DigestVerifyFinal(md_ctx.get(), signature,
signature_len));
});
}
3 changes: 2 additions & 1 deletion crypto/evp_extra/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

#define PKCS8_VERSION_ONE 0
#define PKCS8_VERSION_TWO 1
#define ED25519_PUBLIC_KEY_OFFSET 32

typedef struct {
uint8_t pub[32];
Expand All @@ -31,13 +30,15 @@ extern const EVP_PKEY_ASN1_METHOD pqdsa_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD kem_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD hmac_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD dh_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD ed25519ph_asn1_meth;

extern const EVP_PKEY_METHOD x25519_pkey_meth;
extern const EVP_PKEY_METHOD hkdf_pkey_meth;
extern const EVP_PKEY_METHOD hmac_pkey_meth;
extern const EVP_PKEY_METHOD dh_pkey_meth;
extern const EVP_PKEY_METHOD dsa_pkey_meth;
extern const EVP_PKEY_METHOD pqdsa_pkey_meth;
extern const EVP_PKEY_METHOD ed25519ph_pkey_meth;

// evp_pkey_set_method behaves like |EVP_PKEY_set_type|, but takes a pointer to
// a method table. This avoids depending on every |EVP_PKEY_ASN1_METHOD|.
Expand Down
Loading
Loading