From d9e6641eff77be93ecd278cf589f05ae216268a3 Mon Sep 17 00:00:00 2001 From: Torben Hansen <50673096+torben-hansen@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:28:30 -0700 Subject: [PATCH 1/6] Refactor ED25519_sign into hw and nohw backend --- crypto/curve25519/curve25519.c | 23 ++++++++++++++++++----- crypto/curve25519/curve25519_nohw.c | 7 +++++++ crypto/curve25519/internal.h | 3 +++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/crypto/curve25519/curve25519.c b/crypto/curve25519/curve25519.c index b87d1fe425..b1a43b5af1 100644 --- a/crypto/curve25519/curve25519.c +++ b/crypto/curve25519/curve25519.c @@ -51,6 +51,11 @@ OPENSSL_INLINE int x25519_s2n_bignum_capable(void) { #endif } +// Return 0 until ED25519 lands in s2n-bignum +OPENSSL_INLINE int ed25519_s2n_bignum_capable(void) { + return 0; +} + // Stub functions if implementations are not compiled. // These functions have to abort, otherwise we risk applications assuming they // did work without actually doing anything. @@ -234,10 +239,15 @@ static void x25519_s2n_bignum_public_from_private( #endif } +// Stub function until ED25519 lands in s2n-bignum +static void ed25519_keypair_from_seed_s2n_bignum(uint8_t out_public_key[32], + uint8_t az[SHA512_DIGEST_LENGTH]) { + abort(); +} void ED25519_keypair_from_seed(uint8_t out_public_key[32], - uint8_t out_private_key[64], - const uint8_t seed[ED25519_SEED_LEN]) { + uint8_t out_private_key[64], const uint8_t seed[ED25519_SEED_LEN]) { + uint8_t az[SHA512_DIGEST_LENGTH]; SHA512(seed, ED25519_SEED_LEN, az); @@ -245,10 +255,13 @@ void ED25519_keypair_from_seed(uint8_t out_public_key[32], az[31] &= 127; az[31] |= 64; - ge_p3 A; - x25519_ge_scalarmult_base(&A, az); - ge_p3_tobytes(out_public_key, &A); + if (ed25519_s2n_bignum_capable() == 1) { + ed25519_keypair_from_seed_s2n_bignum(out_public_key, az); + } else { + ed25519_keypair_from_seed_nohw(out_public_key, az); + } + OPENSSL_STATIC_ASSERT(64 == (ED25519_SEED_LEN + 32), ed25519_parameter_length_mismatch) OPENSSL_memcpy(out_private_key, seed, ED25519_SEED_LEN); OPENSSL_memcpy(out_private_key + ED25519_SEED_LEN, out_public_key, 32); } diff --git a/crypto/curve25519/curve25519_nohw.c b/crypto/curve25519/curve25519_nohw.c index 3776c49c51..a5b12607c4 100644 --- a/crypto/curve25519/curve25519_nohw.c +++ b/crypto/curve25519/curve25519_nohw.c @@ -1968,3 +1968,10 @@ void x25519_public_from_private_nohw(uint8_t out_public_value[32], fe_tobytes(out_public_value, &zminusy_inv); CONSTTIME_DECLASSIFY(out_public_value, 32); } + +void ed25519_keypair_from_seed_nohw(uint8_t out_public_key[32], + uint8_t az[SHA512_DIGEST_LENGTH]) { + ge_p3 A; + x25519_ge_scalarmult_base(&A, az); + ge_p3_tobytes(out_public_key, &A); +} diff --git a/crypto/curve25519/internal.h b/crypto/curve25519/internal.h index 3458c3eb3d..f3f060bc8b 100644 --- a/crypto/curve25519/internal.h +++ b/crypto/curve25519/internal.h @@ -20,6 +20,7 @@ extern "C" { #endif #include +#include #include "../internal.h" @@ -114,6 +115,8 @@ void x25519_scalar_mult_generic_nohw(uint8_t out[32], const uint8_t point[32]); void x25519_public_from_private_nohw(uint8_t out_public_value[32], const uint8_t private_key[32]); +void ed25519_keypair_from_seed_nohw(uint8_t out_public_key[32], + uint8_t az[SHA512_DIGEST_LENGTH]); // Port to internal linkage in curve25519_nohw.c when adding implementation // from s2n-bignum ed25519 From c47dc7f94833ccf99a11c1d840b9bf10408d6cdb Mon Sep 17 00:00:00 2001 From: Torben Hansen <50673096+torben-hansen@users.noreply.github.com> Date: Sat, 28 Oct 2023 12:02:07 -0700 Subject: [PATCH 2/6] Documentation --- crypto/curve25519/curve25519.c | 33 +++++++++++++++++++++++------ crypto/curve25519/curve25519_nohw.c | 2 +- crypto/curve25519/internal.h | 4 ++-- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/crypto/curve25519/curve25519.c b/crypto/curve25519/curve25519.c index b1a43b5af1..fd7b7a33ea 100644 --- a/crypto/curve25519/curve25519.c +++ b/crypto/curve25519/curve25519.c @@ -240,35 +240,54 @@ static void x25519_s2n_bignum_public_from_private( } // Stub function until ED25519 lands in s2n-bignum -static void ed25519_keypair_from_seed_s2n_bignum(uint8_t out_public_key[32], - uint8_t az[SHA512_DIGEST_LENGTH]) { +static void ed25519_public_key_from_hashed_seed_s2n_bignum( + uint8_t out_public_key[32], uint8_t az[SHA512_DIGEST_LENGTH]) { abort(); } -void ED25519_keypair_from_seed(uint8_t out_public_key[32], +void ED25519_keypair_from_seed(uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN], uint8_t out_private_key[64], const uint8_t seed[ED25519_SEED_LEN]) { + // Step: rfc8032 5.1.5.1 + // Compute SHA512(seed). uint8_t az[SHA512_DIGEST_LENGTH]; SHA512(seed, ED25519_SEED_LEN, az); + // Step: rfc8032 5.1.5.2 + // 248 = 11111000_2 clears lowest 3 bits. + // For last octet: clear highest highest bit and set second highest bit, + // respectively. + // 127 = 01111111_2 + // 64 = 01000000_2 az[0] &= 248; az[31] &= 127; az[31] |= 64; + // Step: rfc8032 5.1.5.[3,4] + // Compute [az]B and encode public key to a 32 byte octet. if (ed25519_s2n_bignum_capable() == 1) { - ed25519_keypair_from_seed_s2n_bignum(out_public_key, az); + ed25519_public_key_from_hashed_seed_s2n_bignum(out_public_key, az); } else { - ed25519_keypair_from_seed_nohw(out_public_key, az); + ed25519_public_key_from_hashed_seed_nohw(out_public_key, az); } - OPENSSL_STATIC_ASSERT(64 == (ED25519_SEED_LEN + 32), ed25519_parameter_length_mismatch) + // Encoded public key is a suffix in the private key. Avoids having to + // generate the public key from the private key when signing. + OPENSSL_STATIC_ASSERT(64 == (ED25519_SEED_LEN + ED25519_PUBLIC_KEY_LEN), ed25519_parameter_length_mismatch) OPENSSL_memcpy(out_private_key, seed, ED25519_SEED_LEN); - OPENSSL_memcpy(out_private_key + ED25519_SEED_LEN, out_public_key, 32); + OPENSSL_memcpy(out_private_key + ED25519_SEED_LEN, out_public_key, + ED25519_PUBLIC_KEY_LEN); } void ED25519_keypair(uint8_t out_public_key[32], uint8_t out_private_key[64]) { + + // Ed25519 key generation: rfc8032 5.1.5 + // Private key is 32 octets of random data. uint8_t seed[ED25519_SEED_LEN]; RAND_bytes(seed, ED25519_SEED_LEN); + + // Public key generation is handled in a separate function. See function + // description why this is useful. ED25519_keypair_from_seed(out_public_key, out_private_key, seed); OPENSSL_cleanse(seed, ED25519_SEED_LEN); } diff --git a/crypto/curve25519/curve25519_nohw.c b/crypto/curve25519/curve25519_nohw.c index a5b12607c4..7e2d1545be 100644 --- a/crypto/curve25519/curve25519_nohw.c +++ b/crypto/curve25519/curve25519_nohw.c @@ -1969,7 +1969,7 @@ void x25519_public_from_private_nohw(uint8_t out_public_value[32], CONSTTIME_DECLASSIFY(out_public_value, 32); } -void ed25519_keypair_from_seed_nohw(uint8_t out_public_key[32], +void ed25519_public_key_from_hashed_seed_nohw(uint8_t out_public_key[32], uint8_t az[SHA512_DIGEST_LENGTH]) { ge_p3 A; x25519_ge_scalarmult_base(&A, az); diff --git a/crypto/curve25519/internal.h b/crypto/curve25519/internal.h index f3f060bc8b..d23756db08 100644 --- a/crypto/curve25519/internal.h +++ b/crypto/curve25519/internal.h @@ -115,8 +115,8 @@ void x25519_scalar_mult_generic_nohw(uint8_t out[32], const uint8_t point[32]); void x25519_public_from_private_nohw(uint8_t out_public_value[32], const uint8_t private_key[32]); -void ed25519_keypair_from_seed_nohw(uint8_t out_public_key[32], - uint8_t az[SHA512_DIGEST_LENGTH]); +void ed25519_public_key_from_hashed_seed_nohw(uint8_t out_public_key[32], + uint8_t az[SHA512_DIGEST_LENGTH]); // Port to internal linkage in curve25519_nohw.c when adding implementation // from s2n-bignum ed25519 From 61d96e472c4eb6cd682b39504ad36235d58ded0c Mon Sep 17 00:00:00 2001 From: Torben Hansen <50673096+torben-hansen@users.noreply.github.com> Date: Sat, 28 Oct 2023 12:06:17 -0700 Subject: [PATCH 3/6] Actually, just write-out the binary. That is already pretty clear --- crypto/curve25519/curve25519.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crypto/curve25519/curve25519.c b/crypto/curve25519/curve25519.c index fd7b7a33ea..0de18b3298 100644 --- a/crypto/curve25519/curve25519.c +++ b/crypto/curve25519/curve25519.c @@ -254,14 +254,9 @@ void ED25519_keypair_from_seed(uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN], SHA512(seed, ED25519_SEED_LEN, az); // Step: rfc8032 5.1.5.2 - // 248 = 11111000_2 clears lowest 3 bits. - // For last octet: clear highest highest bit and set second highest bit, - // respectively. - // 127 = 01111111_2 - // 64 = 01000000_2 - az[0] &= 248; - az[31] &= 127; - az[31] |= 64; + az[0] &= 248; // 11111000_2 + az[31] &= 127; // 01111111_2 + az[31] |= 64; // 01000000_2 // Step: rfc8032 5.1.5.[3,4] // Compute [az]B and encode public key to a 32 byte octet. From 31868cd10a6ab5e19e50d9e94e1160cdc6f98c0b Mon Sep 17 00:00:00 2001 From: Torben Hansen <50673096+torben-hansen@users.noreply.github.com> Date: Sat, 28 Oct 2023 12:14:02 -0700 Subject: [PATCH 4/6] Avoid magic values. External interface doesn't use it. But the compiler will verify lengths agree --- crypto/curve25519/curve25519.c | 11 +++++++---- crypto/curve25519/internal.h | 5 +++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crypto/curve25519/curve25519.c b/crypto/curve25519/curve25519.c index 0de18b3298..c932baa042 100644 --- a/crypto/curve25519/curve25519.c +++ b/crypto/curve25519/curve25519.c @@ -241,12 +241,14 @@ static void x25519_s2n_bignum_public_from_private( // Stub function until ED25519 lands in s2n-bignum static void ed25519_public_key_from_hashed_seed_s2n_bignum( - uint8_t out_public_key[32], uint8_t az[SHA512_DIGEST_LENGTH]) { + uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN], + uint8_t az[SHA512_DIGEST_LENGTH]) { abort(); } void ED25519_keypair_from_seed(uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN], - uint8_t out_private_key[64], const uint8_t seed[ED25519_SEED_LEN]) { + uint8_t out_private_key[ED25519_PRIVATE_KEY_LEN], + const uint8_t seed[ED25519_SEED_LEN]) { // Step: rfc8032 5.1.5.1 // Compute SHA512(seed). @@ -268,13 +270,14 @@ void ED25519_keypair_from_seed(uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN], // Encoded public key is a suffix in the private key. Avoids having to // generate the public key from the private key when signing. - OPENSSL_STATIC_ASSERT(64 == (ED25519_SEED_LEN + ED25519_PUBLIC_KEY_LEN), ed25519_parameter_length_mismatch) + OPENSSL_STATIC_ASSERT(ED25519_PRIVATE_KEY_LEN == (ED25519_SEED_LEN + ED25519_PUBLIC_KEY_LEN), ed25519_parameter_length_mismatch) OPENSSL_memcpy(out_private_key, seed, ED25519_SEED_LEN); OPENSSL_memcpy(out_private_key + ED25519_SEED_LEN, out_public_key, ED25519_PUBLIC_KEY_LEN); } -void ED25519_keypair(uint8_t out_public_key[32], uint8_t out_private_key[64]) { +void ED25519_keypair(uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN], + uint8_t out_private_key[ED25519_PRIVATE_KEY_LEN]) { // Ed25519 key generation: rfc8032 5.1.5 // Private key is 32 octets of random data. diff --git a/crypto/curve25519/internal.h b/crypto/curve25519/internal.h index d23756db08..3e9da57b36 100644 --- a/crypto/curve25519/internal.h +++ b/crypto/curve25519/internal.h @@ -115,8 +115,9 @@ void x25519_scalar_mult_generic_nohw(uint8_t out[32], const uint8_t point[32]); void x25519_public_from_private_nohw(uint8_t out_public_value[32], const uint8_t private_key[32]); -void ed25519_public_key_from_hashed_seed_nohw(uint8_t out_public_key[32], - uint8_t az[SHA512_DIGEST_LENGTH]); +void ed25519_public_key_from_hashed_seed_nohw( + uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN], + uint8_t az[SHA512_DIGEST_LENGTH]); // Port to internal linkage in curve25519_nohw.c when adding implementation // from s2n-bignum ed25519 From c26bddafc2c1ff00de5a00a595bf82b2b8b5156c Mon Sep 17 00:00:00 2001 From: Torben Hansen <50673096+torben-hansen@users.noreply.github.com> Date: Sun, 29 Oct 2023 08:35:56 -0700 Subject: [PATCH 5/6] Add general information --- crypto/curve25519/curve25519.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crypto/curve25519/curve25519.c b/crypto/curve25519/curve25519.c index c932baa042..a901e4a687 100644 --- a/crypto/curve25519/curve25519.c +++ b/crypto/curve25519/curve25519.c @@ -31,6 +31,25 @@ #include "../internal.h" #include "../fipsmodule/cpucap/internal.h" +// X25519 [1] and Ed25519 [2] is an ECDHE protocol and signature scheme, +// respectively. This file contains an implementation of both using two +// different backends: +// 1) One backed is a pure C backend that should work on any platform. +// 2) The other backend is machine-optimized using s2n-bignum [3] as backend. +// +// [1]: https://datatracker.ietf.org/doc/html/rfc7748 +// [2]: https://datatracker.ietf.org/doc/html/rfc8032 +// [3]: https://github.com/awslabs/s2n-bignum +// +// "Clamping": +// Both X25519 and Ed25519 contain "clamping" steps; bit-twiddling, masking or +// setting specific bits. Generally, the bit-twiddling is to avoid common +// implementation errors and weak instances. Details can be found through the +// following two references: +// * https://mailarchive.ietf.org/arch/msg/cfrg/pt2bt3fGQbNF8qdEcorp-rJSJrc/ +// * https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about + + // If (1) x86_64 or aarch64, (2) linux or apple, and (3) OPENSSL_NO_ASM is not // set, s2n-bignum path is capable. #if ((defined(OPENSSL_X86_64) && \ From f3dc7b478b3efb364ae3e345613dd38d9db85a7d Mon Sep 17 00:00:00 2001 From: Torben Hansen <50673096+torben-hansen@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:29:15 -0700 Subject: [PATCH 6/6] Correct spelling --- crypto/curve25519/curve25519.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/curve25519/curve25519.c b/crypto/curve25519/curve25519.c index a901e4a687..532baff8df 100644 --- a/crypto/curve25519/curve25519.c +++ b/crypto/curve25519/curve25519.c @@ -34,7 +34,7 @@ // X25519 [1] and Ed25519 [2] is an ECDHE protocol and signature scheme, // respectively. This file contains an implementation of both using two // different backends: -// 1) One backed is a pure C backend that should work on any platform. +// 1) One backend is a pure C backend that should work on any platform. // 2) The other backend is machine-optimized using s2n-bignum [3] as backend. // // [1]: https://datatracker.ietf.org/doc/html/rfc7748