diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 3aed47cbe9..14db47c7bc 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -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 diff --git a/crypto/evp_extra/evp_test.cc b/crypto/evp_extra/evp_test.cc index dc278256bb..ef8f8bb492 100644 --- a/crypto/evp_extra/evp_test.cc +++ b/crypto/evp_extra/evp_test.cc @@ -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 pkey(nullptr); + bssl::UniquePtr 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 ctx( + EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519PH, nullptr)); + ASSERT_FALSE(EVP_PKEY_keygen_init(ctx.get())); + } + + { + bssl::UniquePtr 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 + { + bssl::UniquePtr 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 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 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 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 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 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(); + } + + bssl::UniquePtr pkey(EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519PH, nullptr, seed.data(), seed.size())); + bssl::UniquePtr 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 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)); + }); +} diff --git a/crypto/evp_extra/internal.h b/crypto/evp_extra/internal.h index f8cbff80a5..18e9671924 100644 --- a/crypto/evp_extra/internal.h +++ b/crypto/evp_extra/internal.h @@ -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]; @@ -31,6 +30,7 @@ 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; @@ -38,6 +38,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|. diff --git a/crypto/evp_extra/p_ed25519_asn1.c b/crypto/evp_extra/p_ed25519_asn1.c index 14c4cfdf8e..9d415381c9 100644 --- a/crypto/evp_extra/p_ed25519_asn1.c +++ b/crypto/evp_extra/p_ed25519_asn1.c @@ -66,7 +66,7 @@ static int ed25519_set_priv_raw(EVP_PKEY *pkey, const uint8_t *privkey, } static int ed25519_set_pub_raw(EVP_PKEY *pkey, const uint8_t *in, size_t len) { - if (len != 32) { + if (len != ED25519_PUBLIC_KEY_LEN) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } @@ -76,7 +76,7 @@ static int ed25519_set_pub_raw(EVP_PKEY *pkey, const uint8_t *in, size_t len) { return 0; } - OPENSSL_memcpy(key->key + ED25519_PUBLIC_KEY_OFFSET, in, 32); + OPENSSL_memcpy(key->key + ED25519_PUBLIC_KEY_OFFSET, in, ED25519_PUBLIC_KEY_LEN); key->has_private = 0; ed25519_free(pkey); @@ -93,7 +93,7 @@ static int ed25519_get_priv_raw(const EVP_PKEY *pkey, uint8_t *out, } if (out == NULL) { - *out_len = 32; + *out_len = ED25519_PRIVATE_KEY_SEED_LEN; return 1; } @@ -103,7 +103,7 @@ static int ed25519_get_priv_raw(const EVP_PKEY *pkey, uint8_t *out, } // The raw private key format is the first 32 bytes of the private key. - OPENSSL_memcpy(out, key->key, 32); + OPENSSL_memcpy(out, key->key, ED25519_PRIVATE_KEY_SEED_LEN); *out_len = 32; return 1; } @@ -112,16 +112,16 @@ static int ed25519_get_pub_raw(const EVP_PKEY *pkey, uint8_t *out, size_t *out_len) { const ED25519_KEY *key = pkey->pkey.ptr; if (out == NULL) { - *out_len = 32; + *out_len = ED25519_PUBLIC_KEY_LEN; return 1; } - if (*out_len < 32) { + if (*out_len < ED25519_PUBLIC_KEY_LEN) { OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL); return 0; } - OPENSSL_memcpy(out, key->key + ED25519_PUBLIC_KEY_OFFSET, 32); + OPENSSL_memcpy(out, key->key + ED25519_PUBLIC_KEY_OFFSET, ED25519_PUBLIC_KEY_LEN); *out_len = 32; return 1; } @@ -163,7 +163,7 @@ static int ed25519_pub_cmp(const EVP_PKEY *a, const EVP_PKEY *b) { const ED25519_KEY *a_key = a->pkey.ptr; const ED25519_KEY *b_key = b->pkey.ptr; return OPENSSL_memcmp(a_key->key + ED25519_PUBLIC_KEY_OFFSET, - b_key->key + ED25519_PUBLIC_KEY_OFFSET, 32) == 0; + b_key->key + ED25519_PUBLIC_KEY_OFFSET, ED25519_PUBLIC_KEY_LEN) == 0; } static int ed25519_priv_decode(EVP_PKEY *out, CBS *params, CBS *key, CBS *pubkey) { @@ -281,3 +281,28 @@ const EVP_PKEY_ASN1_METHOD ed25519_asn1_meth = { NULL /* param_cmp */, ed25519_free, }; + +const EVP_PKEY_ASN1_METHOD ed25519ph_asn1_meth = { + EVP_PKEY_ED25519PH, + {}, /* oid */ + 0, /* oid_len */ + "", /* pem_str */ + "", /* info */ + NULL, /* pub_decode */ + NULL, /* pub_encode */ + NULL, /* pub_cmp */ + NULL, /* priv_decode */ + NULL, /* priv_encode */ + NULL, /* priv_encode_v2 */ + ed25519_set_priv_raw, + ed25519_set_pub_raw, + ed25519_get_priv_raw, + ed25519_get_pub_raw, + NULL /* pkey_opaque */, + ed25519_size, + ed25519_bits, + NULL /* param_missing */, + NULL /* param_copy */, + NULL /* param_cmp */, + ed25519_free, +}; diff --git a/crypto/evp_extra/p_ed25519ph.c b/crypto/evp_extra/p_ed25519ph.c new file mode 100644 index 0000000000..eda1306206 --- /dev/null +++ b/crypto/evp_extra/p_ed25519ph.c @@ -0,0 +1,166 @@ +/* Copyright (c) 2017, 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 + +#include +#include +#include + +#include "../internal.h" +#include "internal.h" + +typedef struct { + uint8_t context[255]; + size_t context_len; +} ED25519PH_PKEY_CTX; + +static int pkey_ed25519ph_init(EVP_PKEY_CTX *ctx) { + ED25519PH_PKEY_CTX *dctx = OPENSSL_zalloc(sizeof(ED25519PH_PKEY_CTX)); + if (dctx == NULL) { + return 0; + } + ctx->data = dctx; + return 1; +} + +static void pkey_ed25519ph_cleanup(EVP_PKEY_CTX *ctx) { + ED25519PH_PKEY_CTX *dctx = ctx->data; + if (!dctx) { + return; + } + + OPENSSL_free(dctx); +} + +static int pkey_ed25519ph_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) { + if (!pkey_ed25519ph_init(dst)) { + return 0; + } + + ED25519PH_PKEY_CTX *dctx = dst->data; + ED25519PH_PKEY_CTX *sctx = src->data; + GUARD_PTR(dctx); + GUARD_PTR(sctx); + + OPENSSL_memcpy(dctx->context, sctx->context, sizeof(sctx->context)); + dctx->context_len = sctx->context_len; + + return 1; +} + +static int pkey_ed25519ph_sign(EVP_PKEY_CTX *ctx, uint8_t *sig, size_t *siglen, + const uint8_t *tbs, size_t tbslen) { + ED25519_KEY *key = ctx->pkey->pkey.ptr; + if (!key->has_private) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY); + return 0; + } + + if (sig == NULL) { + *siglen = ED25519_SIGNATURE_LEN; + return 1; + } + + if (*siglen < ED25519_SIGNATURE_LEN) { + OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL); + return 0; + } + + if(tbslen < SHA512_DIGEST_LENGTH) { + OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL); + return 0; + } + + ED25519PH_PKEY_CTX *dctx = ctx->data; + GUARD_PTR(dctx); + + if (!ED25519ph_sign_digest(sig, tbs, key->key, dctx->context, dctx->context_len)) { + return 0; + } + + *siglen = ED25519_SIGNATURE_LEN; + return 1; +} + +static int pkey_ed25519ph_verify(EVP_PKEY_CTX *ctx, const uint8_t *sig, + size_t siglen, const uint8_t *tbs, + size_t tbslen) { + ED25519_KEY *key = ctx->pkey->pkey.ptr; + ED25519PH_PKEY_CTX *dctx = ctx->data; + GUARD_PTR(dctx); + + if (siglen != ED25519_SIGNATURE_LEN || tbslen < SHA512_DIGEST_LENGTH || + !ED25519ph_verify_digest(tbs, sig, + key->key + ED25519_PUBLIC_KEY_OFFSET, dctx->context, dctx->context_len)) { + OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SIGNATURE); + return 0; + } + + return 1; +} + +static int pkey_ed25519ph_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2) { + GUARD_PTR(ctx); + ED25519PH_PKEY_CTX *dctx = (ED25519PH_PKEY_CTX *)ctx->data; + switch (type) { + case EVP_PKEY_CTRL_MD: { + const EVP_MD *md = p2; + int md_type = EVP_MD_type(md); + // MUST be SHA-512 + if (md_type != NID_sha512) { + OPENSSL_PUT_ERROR(EVP, EVP_R_COMMAND_NOT_SUPPORTED); + return 0; + } + break; + } + case EVP_PKEY_CTRL_SIGNING_CONTEXT: { + EVP_PKEY_CTX_SIGNATURE_CONTEXT_PARAMS *params = p2; + if (!params || !dctx || params->context_len > sizeof(dctx->context)) { + return 0; + } + OPENSSL_memcpy(dctx->context, params->context, params->context_len); + dctx->context_len = params->context_len; + break; + } + case EVP_PKEY_CTRL_GET_SIGNING_CONTEXT: { + EVP_PKEY_CTX_SIGNATURE_CONTEXT_PARAMS *params = p2; + if (!params || !dctx) { + return 0; + } + if(dctx->context_len == 0) { + params->context = NULL; + params->context_len = 0; + } else { + params->context = dctx->context; + params->context_len = dctx->context_len; + } + return 1; + } + default: + OPENSSL_PUT_ERROR(EVP, EVP_R_COMMAND_NOT_SUPPORTED); + return 0; + } + return 1; +} + +const EVP_PKEY_METHOD ed25519ph_pkey_meth = { + .pkey_id = EVP_PKEY_ED25519PH, + .init = pkey_ed25519ph_init, + .cleanup = pkey_ed25519ph_cleanup, + .copy = pkey_ed25519ph_copy, + .sign = pkey_ed25519ph_sign, + .verify = pkey_ed25519ph_verify, + .ctrl = pkey_ed25519ph_ctrl +}; diff --git a/crypto/evp_extra/p_methods.c b/crypto/evp_extra/p_methods.c index 6dce228f19..73c7baeb2e 100644 --- a/crypto/evp_extra/p_methods.c +++ b/crypto/evp_extra/p_methods.c @@ -11,7 +11,8 @@ static const EVP_PKEY_METHOD *const non_fips_pkey_evp_methods[] = { &x25519_pkey_meth, &dh_pkey_meth, &dsa_pkey_meth, - &pqdsa_pkey_meth + &pqdsa_pkey_meth, + &ed25519ph_pkey_meth }; const EVP_PKEY_ASN1_METHOD *const asn1_evp_pkey_methods[] = { @@ -24,7 +25,8 @@ const EVP_PKEY_ASN1_METHOD *const asn1_evp_pkey_methods[] = { &pqdsa_asn1_meth, &kem_asn1_meth, &hmac_asn1_meth, - &dh_asn1_meth + &dh_asn1_meth, + &ed25519ph_asn1_meth }; const size_t asn1_evp_pkey_methods_size = sizeof(asn1_evp_pkey_methods)/sizeof(asn1_evp_pkey_methods[0]); diff --git a/crypto/fipsmodule/evp/evp.c b/crypto/fipsmodule/evp/evp.c index 496cb3b681..f241054889 100644 --- a/crypto/fipsmodule/evp/evp.c +++ b/crypto/fipsmodule/evp/evp.c @@ -479,6 +479,9 @@ EVP_PKEY *EVP_PKEY_new_raw_private_key(int type, ENGINE *unused, case EVP_PKEY_ED25519: method = &ed25519_asn1_meth; break; + case EVP_PKEY_ED25519PH: + method = &ed25519ph_asn1_meth; + break; case EVP_PKEY_HMAC: method = &hmac_asn1_meth; break; @@ -516,6 +519,9 @@ EVP_PKEY *EVP_PKEY_new_raw_public_key(int type, ENGINE *unused, case EVP_PKEY_ED25519: method = &ed25519_asn1_meth; break; + case EVP_PKEY_ED25519PH: + method = &ed25519ph_asn1_meth; + break; default: OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_ALGORITHM); return 0; @@ -589,6 +595,30 @@ int EVP_PKEY_CTX_get_signature_md(EVP_PKEY_CTX *ctx, const EVP_MD **out_md) { 0, (void *)out_md); } +int EVP_PKEY_CTX_set_signature_context(EVP_PKEY_CTX *ctx, + const uint8_t *context, + size_t context_len) { + EVP_PKEY_CTX_SIGNATURE_CONTEXT_PARAMS params = {context, context_len}; + return EVP_PKEY_CTX_ctrl(ctx, -1, EVP_PKEY_OP_TYPE_SIG, + EVP_PKEY_CTRL_SIGNING_CONTEXT, 0, ¶ms); +} + +int EVP_PKEY_CTX_get0_signature_context(EVP_PKEY_CTX *ctx, + const uint8_t **context, + size_t *context_len) { + GUARD_PTR(context); + GUARD_PTR(context_len); + EVP_PKEY_CTX_SIGNATURE_CONTEXT_PARAMS params = {NULL, 0}; + if (!EVP_PKEY_CTX_ctrl(ctx, -1, EVP_PKEY_OP_TYPE_SIG, + EVP_PKEY_CTRL_GET_SIGNING_CONTEXT, 0, ¶ms)) { + return 0; + } + *context = params.context; + *context_len = params.context_len; + return 1; +} + + void *EVP_PKEY_get0(const EVP_PKEY *pkey) { SET_DIT_AUTO_RESET; GUARD_PTR(pkey); diff --git a/crypto/fipsmodule/evp/internal.h b/crypto/fipsmodule/evp/internal.h index 46ff161ba1..ff862a6bdf 100644 --- a/crypto/fipsmodule/evp/internal.h +++ b/crypto/fipsmodule/evp/internal.h @@ -207,6 +207,8 @@ int EVP_RSA_PKEY_CTX_ctrl(EVP_PKEY_CTX *ctx, int optype, int cmd, int p1, void * #define EVP_PKEY_CTRL_MD 1 #define EVP_PKEY_CTRL_GET_MD 2 +#define EVP_PKEY_CTRL_SIGNING_CONTEXT 3 +#define EVP_PKEY_CTRL_GET_SIGNING_CONTEXT 4 // EVP_PKEY_CTRL_PEER_KEY is called with different values of |p1|: // 0: Is called from |EVP_PKEY_derive_set_peer| and |p2| contains a peer key. @@ -382,8 +384,8 @@ void evp_pkey_set_cb_translate(BN_GENCB *cb, EVP_PKEY_CTX *ctx); #define ED25519_PUBLIC_KEY_OFFSET 32 #define FIPS_EVP_PKEY_METHODS 7 -#define NON_FIPS_EVP_PKEY_METHODS 4 -#define ASN1_EVP_PKEY_METHODS 10 +#define NON_FIPS_EVP_PKEY_METHODS 5 +#define ASN1_EVP_PKEY_METHODS 11 struct fips_evp_pkey_methods { const EVP_PKEY_METHOD * methods[FIPS_EVP_PKEY_METHODS]; @@ -397,6 +399,12 @@ const EVP_PKEY_METHOD *EVP_PKEY_hmac_pkey_meth(void); const EVP_PKEY_METHOD *EVP_PKEY_ed25519_pkey_meth(void); const EVP_PKEY_METHOD *EVP_PKEY_kem_pkey_meth(void); const EVP_PKEY_METHOD *EVP_PKEY_pqdsa_pkey_meth(void); +const EVP_PKEY_METHOD *EVP_PKEY_ed25519ph_pkey_meth(void); + +struct evp_pkey_ctx_signature_context_params_st { + const uint8_t *context; + size_t context_len; +}; // EVP_PKEY_CTX_SIGNATURE_CONTEXT_PARAMS #if defined(__cplusplus) } // extern C diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h index eb284bb0d0..1f0120f9ca 100644 --- a/crypto/obj/obj_dat.h +++ b/crypto/obj/obj_dat.h @@ -56,7 +56,7 @@ /* This file is generated by crypto/obj/objects.go. */ -#define NUM_NID 997 +#define NUM_NID 998 static const uint8_t kObjectData[] = { /* NID_rsadsi */ @@ -9004,6 +9004,7 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { {"MLDSA44", "MLDSA44", NID_MLDSA44, 9, &kObjectData[6333], 0}, {"MLDSA65", "MLDSA65", NID_MLDSA65, 9, &kObjectData[6342], 0}, {"MLDSA87", "MLDSA87", NID_MLDSA87, 9, &kObjectData[6351], 0}, + {"ED25519ph", "ED25519ph", NID_ED25519ph, 0, NULL, 0}, }; static const uint16_t kNIDsInShortNameOrder[] = { @@ -9101,6 +9102,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { 67 /* DSA-old */, 297 /* DVCS */, 949 /* ED25519 */, + 997 /* ED25519ph */, 960 /* ED448 */, 99 /* GN */, 969 /* HKDF */, @@ -10012,6 +10014,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { 392 /* Domain */, 132 /* E-mail Protection */, 949 /* ED25519 */, + 997 /* ED25519ph */, 960 /* ED448 */, 389 /* Enterprises */, 384 /* Experimental */, diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num index 72782a89e7..b25bf8cb6d 100644 --- a/crypto/obj/obj_mac.num +++ b/crypto/obj/obj_mac.num @@ -984,3 +984,4 @@ PQDSA 993 MLDSA44 994 MLDSA65 995 MLDSA87 996 +ED25519ph 997 diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt index 791704d5a3..c5d4c4e926 100644 --- a/crypto/obj/objects.txt +++ b/crypto/obj/objects.txt @@ -1353,6 +1353,7 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme 1 3 101 111 : X448 1 3 101 112 : ED25519 1 3 101 113 : ED448 + : ED25519ph : ChaCha20-Poly1305 : chacha20-poly1305 diff --git a/include/openssl/base.h b/include/openssl/base.h index 15a7d9e1fa..5e440da1ba 100644 --- a/include/openssl/base.h +++ b/include/openssl/base.h @@ -352,6 +352,7 @@ typedef struct kem_key_st KEM_KEY; typedef struct evp_pkey_ctx_st EVP_PKEY_CTX; typedef struct evp_pkey_asn1_method_st EVP_PKEY_ASN1_METHOD; typedef struct evp_pkey_st EVP_PKEY; +typedef struct evp_pkey_ctx_signature_context_params_st EVP_PKEY_CTX_SIGNATURE_CONTEXT_PARAMS; typedef struct hmac_ctx_st HMAC_CTX; typedef struct md4_state_st MD4_CTX; typedef struct md5_state_st MD5_CTX; diff --git a/include/openssl/evp.h b/include/openssl/evp.h index d7a18271a3..1b33df817d 100644 --- a/include/openssl/evp.h +++ b/include/openssl/evp.h @@ -194,6 +194,7 @@ OPENSSL_EXPORT int EVP_PKEY_CTX_set_dh_paramgen_generator(EVP_PKEY_CTX *ctx, int #define EVP_PKEY_RSA_PSS NID_rsassaPss #define EVP_PKEY_EC NID_X9_62_id_ecPublicKey #define EVP_PKEY_ED25519 NID_ED25519 +#define EVP_PKEY_ED25519PH NID_ED25519ph #define EVP_PKEY_X25519 NID_X25519 #define EVP_PKEY_HKDF NID_hkdf #define EVP_PKEY_HMAC NID_hmac @@ -226,6 +227,18 @@ OPENSSL_EXPORT int EVP_PKEY_cmp_parameters(const EVP_PKEY *a, // or EC curve. OPENSSL_EXPORT EVP_PKEY *EVP_parse_public_key(CBS *cbs); +// EVP_parse_public_key_checked decodes a DER-encoded SubjectPublicKeyInfo structure +// (RFC 5280) from |cbs| and advances |cbs|. It returns a newly-allocated +// |EVP_PKEY| or NULL on error. If the key is an EC key, the curve is guaranteed +// to be set. The EVP_PKEY returned is guaranteed to be of |type| if the key parsed +// from |cbs| is suitable. For example a parsed Ed25519 key can be used for either +// |EVP_PKEY_ED25519| or |EVP_PKEY_ED25519PH| types. +// +// The caller must perform further validation of parsed public key to ensure it is +// suitable and validate other desired key properties such as RSA modulus size +// or EC curve. +OPENSSL_EXPORT EVP_PKEY *EVP_parse_public_key_checked(int type, CBS *cbs); + // EVP_marshal_public_key marshals |key| as a DER-encoded SubjectPublicKeyInfo // structure (RFC 5280) and appends the result to |cbb|. It returns one on // success and zero on error. @@ -246,6 +259,23 @@ OPENSSL_EXPORT int EVP_marshal_public_key(CBB *cbb, const EVP_PKEY *key); // structure. OPENSSL_EXPORT EVP_PKEY *EVP_parse_private_key(CBS *cbs); +// EVP_parse_private_key_checked decodes a DER-encoded PrivateKeyInfo structure +// (RFC 5208) from |cbs| and advances |cbs|. It returns a newly-allocated +// |EVP_PKEY| or NULL on error. The EVP_PKEY returned is guaranteed to be of +// |type| if the key parsed from |cbs| is suitable. For example a parsed Ed25519 +// key can be used for either |EVP_PKEY_ED25519| or |EVP_PKEY_ED25519PH| types. +// +// The caller must check the parsed private key to ensure it is suitable and +// validate other desired key properties such as RSA modulus size or EC curve. +// In particular, RSA private key operations scale cubicly, so applications +// accepting RSA private keys from external sources may need to bound key sizes +// (use |EVP_PKEY_bits| or |RSA_bits|) to avoid a DoS vector. +// +// A PrivateKeyInfo ends with an optional set of attributes. These are not +// processed and so this function will silently ignore any trailing data in the +// structure. +OPENSSL_EXPORT EVP_PKEY *EVP_parse_private_key_checked(int type, CBS *cbs); + // EVP_marshal_private_key marshals |key| as a DER-encoded PrivateKeyInfo // structure (RFC 5208) and appends the result to |cbb|. It returns one on // success and zero on error. @@ -815,6 +845,22 @@ OPENSSL_EXPORT int EVP_PKEY_CTX_get_signature_md(EVP_PKEY_CTX *ctx, const EVP_MD **out_md); +// EVP_PKEY_CTX_set_signature_context sets |context| of length |context_len| to +// be used as the context octet string for the signing operation. |context| will +// be copied to an internal buffer allowing for the caller to free it +// afterwards. It returns one on success or zero on error. +OPENSSL_EXPORT int EVP_PKEY_CTX_set_signature_context(EVP_PKEY_CTX *ctx, + const uint8_t *context, + size_t context_len); + +// EVP_PKEY_CTX_get0_signature_context sets |*context| to point to the internal +// buffer containing the signing context octet string (which may be NULL) and +// writes the length to |*context_len|. It returns one on +// success or zero on error. +OPENSSL_EXPORT int EVP_PKEY_CTX_get0_signature_context(EVP_PKEY_CTX *ctx, + const uint8_t **context, + size_t *context_len); + // RSA specific control functions. // EVP_PKEY_CTX_set_rsa_padding sets the padding type to use. It should be one diff --git a/include/openssl/nid.h b/include/openssl/nid.h index 2ec2a9105b..cefc0f6b95 100644 --- a/include/openssl/nid.h +++ b/include/openssl/nid.h @@ -4379,6 +4379,9 @@ extern "C" { #define NID_MLDSA87 996 #define OBJ_MLDSA87 2L, 16L, 840L, 1L, 101L, 3L, 4L, 3L, 19L +#define SN_ED25519ph "ED25519ph" +#define NID_ED25519ph 997 + #if defined(__cplusplus) } /* extern C */ #endif