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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
46 changes: 38 additions & 8 deletions crypto/evp_extra/evp_asn1.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@
#include "internal.h"
#include "../pqdsa/internal.h"

// check_method_pkey_id returns whether the given method matches the desired |EVP_PKEY_*| type.
// Returns success if |desired_pkey_id| is NULL or if the value matches |method|'s |EVP_PKEY_*| type.
static int check_method_pkey_id(const EVP_PKEY_ASN1_METHOD *method, int *desired_pkey_id) {
GUARD_PTR(method);
if(desired_pkey_id == NULL) {
return 1;
}
return *desired_pkey_id == method->pkey_id;
}

// parse_key_type takes the algorithm cbs sequence |cbs| and extracts the OID.
// The OID is then searched against ASN.1 methods for a method with that OID.
// As the |OID| is read from |cbs| the buffer is advanced.
Expand All @@ -78,7 +88,10 @@
// the OID is not returned (and the |cbs| buffer is advanced) we return the OID
// as |cbs|. (This allows the specific OID, e.g. NID_MLDSA65 to be parsed by
// the type-specific decoding functions within the algorithm parameter.)
static const EVP_PKEY_ASN1_METHOD *parse_key_type(CBS *cbs) {
// |desired_pkey_id| is used to further filter whether a matching |EVP_PKEY_ASN1_METHOD|
// will return |EVP_PKEY| of the desired |EVP_PKEY_*| type. This useful for EVP_PKEY types
// like ED25519 and ED25519PH where the OID is the same.
static const EVP_PKEY_ASN1_METHOD *parse_key_type(CBS *cbs, int *desired_pkey_id) {
CBS oid;
if (!CBS_get_asn1(cbs, &oid, CBS_ASN1_OBJECT)) {
return NULL;
Expand All @@ -89,22 +102,23 @@ static const EVP_PKEY_ASN1_METHOD *parse_key_type(CBS *cbs) {
for (size_t i = 0; i < ASN1_EVP_PKEY_METHODS; i++) {
const EVP_PKEY_ASN1_METHOD *method = asn1_methods[i];
if (CBS_len(&oid) == method->oid_len &&
OPENSSL_memcmp(CBS_data(&oid), method->oid, method->oid_len) == 0) {
OPENSSL_memcmp(CBS_data(&oid), method->oid, method->oid_len) == 0 &&
check_method_pkey_id(method, desired_pkey_id)) {
return method;
}
}

// Special logic to handle the rarer |NID_rsa|.
// https://www.itu.int/ITU-T/formal-language/itu-t/x/x509/2008/AlgorithmObjectIdentifiers.html
if (OBJ_cbs2nid(&oid) == NID_rsa) {
if (OBJ_cbs2nid(&oid) == NID_rsa && check_method_pkey_id(&rsa_asn1_meth, desired_pkey_id)) {
return &rsa_asn1_meth;
}

// The pkey_id for the pqdsa_asn1_meth is EVP_PKEY_PQDSA, as this holds all
// asn1 functions for pqdsa types. However, the incoming CBS has the OID for
// the specific algorithm. So we must search explicitly for the algorithm.
const EVP_PKEY_ASN1_METHOD * ret = PQDSA_find_asn1_by_nid(OBJ_cbs2nid(&oid));
if (ret != NULL) {
if (ret != NULL && check_method_pkey_id(ret, desired_pkey_id)) {
// if |cbs| is empty after parsing |oid| from it, we overwrite the contents
// with |oid| so that we can call pub_decode/priv_decode with the |algorithm|
// populated as |oid|.
Expand All @@ -117,7 +131,7 @@ static const EVP_PKEY_ASN1_METHOD *parse_key_type(CBS *cbs) {
return NULL;
}

EVP_PKEY *EVP_parse_public_key(CBS *cbs) {
static EVP_PKEY *evp_parse_public_key_internal(CBS *cbs, int *desired_pkey_id) {
// Parse the SubjectPublicKeyInfo.
CBS spki, algorithm, key;
uint8_t padding;
Expand All @@ -129,7 +143,7 @@ EVP_PKEY *EVP_parse_public_key(CBS *cbs) {
return NULL;
}

const EVP_PKEY_ASN1_METHOD *method = parse_key_type(&algorithm);
const EVP_PKEY_ASN1_METHOD *method = parse_key_type(&algorithm, desired_pkey_id);
if (method == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_ALGORITHM);
return NULL;
Expand Down Expand Up @@ -165,6 +179,14 @@ EVP_PKEY *EVP_parse_public_key(CBS *cbs) {
return NULL;
}

EVP_PKEY *EVP_parse_public_key(CBS *cbs) {
return evp_parse_public_key_internal(cbs, NULL);
}

EVP_PKEY *EVP_parse_public_key_checked(int type, CBS *cbs) {
return evp_parse_public_key_internal(cbs, &type);
}

int EVP_marshal_public_key(CBB *cbb, const EVP_PKEY *key) {
GUARD_PTR(cbb);
GUARD_PTR(key);
Expand All @@ -182,7 +204,7 @@ static const unsigned kAttributesTag =
static const unsigned kPublicKeyTag =
CBS_ASN1_CONTEXT_SPECIFIC | 1;

EVP_PKEY *EVP_parse_private_key(CBS *cbs) {
static EVP_PKEY *evp_parse_private_key_internal(CBS *cbs, int *desired_type) {
// Parse the PrivateKeyInfo (RFC 5208) or OneAsymmetricKey (RFC 5958).
CBS pkcs8, algorithm, key, public_key;
uint64_t version;
Expand All @@ -195,7 +217,7 @@ EVP_PKEY *EVP_parse_private_key(CBS *cbs) {
return NULL;
}

const EVP_PKEY_ASN1_METHOD *method = parse_key_type(&algorithm);
const EVP_PKEY_ASN1_METHOD *method = parse_key_type(&algorithm, desired_type);
if (method == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_ALGORITHM);
return NULL;
Expand Down Expand Up @@ -248,6 +270,14 @@ EVP_PKEY *EVP_parse_private_key(CBS *cbs) {
return NULL;
}

EVP_PKEY *EVP_parse_private_key(CBS *cbs) {
return evp_parse_private_key_internal(cbs, NULL);
}

EVP_PKEY *EVP_parse_private_key_checked(int type, CBS *cbs) {
return evp_parse_private_key_internal(cbs, &type);
}

int EVP_marshal_private_key(CBB *cbb, const EVP_PKEY *key) {
if (key->ameth == NULL || key->ameth->priv_encode == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_ALGORITHM);
Expand Down
212 changes: 212 additions & 0 deletions crypto/evp_extra/evp_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1529,3 +1529,215 @@ 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};

bssl::UniquePtr<EVP_PKEY> pkey(nullptr);
bssl::UniquePtr<EVP_PKEY> pubkey(nullptr);
bssl::ScopedCBB marshalled_private_key;
bssl::ScopedCBB marshalled_public_key;
uint8_t pure_signature[ED25519_SIGNATURE_LEN] = {0};
size_t pure_signature_len = ED25519_SIGNATURE_LEN;
uint8_t prehash_signature[ED25519_SIGNATURE_LEN] = {0};
size_t prehash_signature_len = ED25519_SIGNATURE_LEN;
uint8_t prehash_ctx_signature[ED25519_SIGNATURE_LEN] = {0};
size_t prehash_ctx_signature_len = ED25519_SIGNATURE_LEN;

{
bssl::UniquePtr<EVP_PKEY_CTX> ctx(
EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519PH, 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()));
}

{
CBS cbs;
CBS_init(&cbs, CBB_data(marshalled_private_key.get()),
CBB_len(marshalled_private_key.get()));
EVP_PKEY *parsed = EVP_parse_private_key_checked(EVP_PKEY_ED25519PH, &cbs);
ASSERT_TRUE(parsed);
pkey.reset(parsed);
// Should have parsed and returned EVP_PKEY_ED25519
ASSERT_EQ(EVP_PKEY_ED25519PH, 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_checked(EVP_PKEY_ED25519PH, &cbs);
ASSERT_TRUE(parsed);
pubkey.reset(parsed);
// Should have parsed and returned EVP_PKEY_ED25519
ASSERT_EQ(EVP_PKEY_ED25519PH, EVP_PKEY_id(pubkey.get()));
}

// 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(), prehash_signature,
&prehash_signature_len));
ASSERT_EQ(prehash_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[0], 3));
ASSERT_TRUE(
EVP_DigestVerifyUpdate(md_ctx.get(), &message[3], sizeof(message) - 3));
ASSERT_TRUE(EVP_DigestVerifyFinal(md_ctx.get(), &prehash_signature[0],
prehash_signature_len));
}

// prehash signature w/ context gen and verify
{
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(), prehash_ctx_signature,
&prehash_ctx_signature_len));
ASSERT_EQ(prehash_ctx_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(), &prehash_ctx_signature[0],
prehash_ctx_signature_len));
}

ASSERT_NE(Bytes(prehash_signature, prehash_signature_len),
Bytes(prehash_ctx_signature, prehash_ctx_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);
// Should have parsed and returned EVP_PKEY_ED25519
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);
// Should have parsed and returned EVP_PKEY_ED25519
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(), pure_signature,
&pure_signature_len, message, sizeof(message)));
ASSERT_EQ(pure_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(), &pure_signature[0],
pure_signature_len, message, sizeof(message)));
}

ASSERT_NE(Bytes(pure_signature, pure_signature_len),
Bytes(prehash_signature, prehash_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));
});
}
2 changes: 2 additions & 0 deletions crypto/evp_extra/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extern const EVP_PKEY_ASN1_METHOD ec_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD rsa_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD rsa_pss_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD ed25519_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD ed25519ph_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD x25519_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD pqdsa_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD kem_asn1_meth;
Expand All @@ -38,6 +39,7 @@ 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