From 0a288ad6ade3f3c9e1df602b70f35bde3eb840cc Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Fri, 24 Apr 2026 15:51:17 +0000 Subject: [PATCH 01/14] Pass SYMCRYPT_FLAG_KEY_NO_FIPS for x25519 import RFC 7748 accepts any 32-byte string as a public key, including low-order and on-twist points. These do not satisfy the FIPS subgroup check performed by SymCryptEckeySetValue. Since X25519 is not a FIPS algorithm, pass SYMCRYPT_FLAG_KEY_NO_FIPS to skip this check in both the import and key-dup paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c | 5 ++++- SymCryptProvider/src/p_scossl_ecc.c | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c index 5681b523..378626fb 100644 --- a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c +++ b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c @@ -1121,12 +1121,15 @@ static SCOSSL_STATUS p_scossl_x25519_keymgmt_import(_Inout_ SCOSSL_ECC_KEY_CTX * pbPrivateKey[cbPrivateKey-1] |= 0x40; } + // RFC 7748 accepts any 32-byte string as a public key, including low-order + // and on-twist points that do not satisfy the FIPS subgroup check. + // X25519 is not a FIPS algorithm, so skip the FIPS check. scError = SymCryptEckeySetValue( pbPrivateKey, cbPrivateKey, pbPublicKey, cbPublicKey, SYMCRYPT_NUMBER_FORMAT_LSB_FIRST, SYMCRYPT_ECPOINT_FORMAT_X, - SYMCRYPT_FLAG_ECKEY_ECDH, + SYMCRYPT_FLAG_ECKEY_ECDH | SYMCRYPT_FLAG_KEY_NO_FIPS, keyCtx->key); if (scError != SYMCRYPT_NO_ERROR) { diff --git a/SymCryptProvider/src/p_scossl_ecc.c b/SymCryptProvider/src/p_scossl_ecc.c index 704c7803..6ca4172d 100644 --- a/SymCryptProvider/src/p_scossl_ecc.c +++ b/SymCryptProvider/src/p_scossl_ecc.c @@ -126,7 +126,7 @@ SCOSSL_ECC_KEY_CTX *p_scossl_ecc_dup_ctx(SCOSSL_ECC_KEY_CTX *keyCtx, int selecti pbPublicKey, cbPublicKey, SYMCRYPT_NUMBER_FORMAT_MSB_FIRST, pointFormat, - SYMCRYPT_FLAG_ECKEY_ECDH, + SYMCRYPT_FLAG_ECKEY_ECDH | (keyCtx->isX25519 ? SYMCRYPT_FLAG_KEY_NO_FIPS : 0), copyCtx->key); if (scError != SYMCRYPT_NO_ERROR) { From 4e78d6a67c97cf87e1743cefa5973d9f6d296a7d Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Fri, 24 Apr 2026 15:51:46 +0000 Subject: [PATCH 02/14] Canonicalize non-canonical x25519 public keys RFC 7748 section 5 specifies that the u-coordinate is decoded by masking the high bit and interpreting the result as a little-endian integer. There are 20 non-canonical encodings where the value is in [p, 2^255-1] (p = 2^255-19). These must be reduced modulo p before being passed to SymCrypt, which rejects out-of-range coordinates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/keymgmt/p_scossl_ecc_keymgmt.c | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c index 378626fb..d4cb533a 100644 --- a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c +++ b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c @@ -1104,6 +1104,33 @@ static SCOSSL_STATUS p_scossl_x25519_keymgmt_import(_Inout_ SCOSSL_ECC_KEY_CTX * } } + // RFC 7748 section 5: mask the high bit and reduce modulo p = 2^255 - 19. + // Non-canonical encodings (values in [p, 2^255-1]) must be accepted and + // treated as their canonical equivalent. + if (cbPublicKey == 32) + { + pbPublicKey[31] &= 0x7f; + + if (pbPublicKey[0] >= 0xed && pbPublicKey[31] == 0x7f) + { + BOOL nonCanonical = TRUE; + for (SIZE_T i = 1; i < 31; i++) + { + if (pbPublicKey[i] != 0xff) + { + nonCanonical = FALSE; + break; + } + } + + if (nonCanonical) + { + pbPublicKey[0] -= 0xed; + memset(&pbPublicKey[1], 0, 31); + } + } + } + if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY)) != NULL) { if (!OSSL_PARAM_get_octet_string(p, (void **)&pbPrivateKey, 0, &cbPrivateKey)) From ebc6202b558b7c68af5d2082ec6460c3f768bd17 Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Fri, 24 Apr 2026 15:52:18 +0000 Subject: [PATCH 03/14] Add tests for low-order and non-canonical x25519 keys Add test vectors for: - Non-canonical public keys (value >= p) that must be reduced mod p and produce correct shared secrets - Low-order public keys (on curve and on twist) where derive must fail because the shared secret would be all zeros - A non-canonical encoding of a low-order point to test both canonicalization and low-order rejection together Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- EvpTestRecipes/3.0/evppkey_ecx.txt | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/EvpTestRecipes/3.0/evppkey_ecx.txt b/EvpTestRecipes/3.0/evppkey_ecx.txt index 9e8144e8..1a830788 100644 --- a/EvpTestRecipes/3.0/evppkey_ecx.txt +++ b/EvpTestRecipes/3.0/evppkey_ecx.txt @@ -89,3 +89,69 @@ Result = KEYPAIR_MISMATCH PrivPubKeyPair = Bob-25519:Alice-25519-PUBLIC Result = KEYPAIR_MISMATCH + +Title = X25519 non-canonical public keys + +PrivateKeyRaw=NonCanonical-Priv-1:X25519:288796bc5aff4b81a37501757bc0753a3c21964790d38699308debc17a6eaf8d + +# f0ff..ff7f has value >= p, reduces to u = 3 mod p +PublicKeyRaw=NonCanonical-Pub-1:X25519:f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=NonCanonical-Priv-1 +PeerKey=NonCanonical-Pub-1 +SharedSecret=b4e0dd76da7b071728b61f856771aa356e57eda78a5b1655cc3820fb5f854c5c + +PrivateKeyRaw=NonCanonical-Priv-2:X25519:60887b3dc72443026ebedbbbb70665f42b87add1440e7768fbd7e8e2ce5f639d + +# f0ff..ffff has high bit set AND value >= p after masking +PublicKeyRaw=NonCanonical-Pub-2:X25519:f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +Derive=NonCanonical-Priv-2 +PeerKey=NonCanonical-Pub-2 +SharedSecret=38d6304c4a7e6d9f7959334fb5245bd2c754525d4c91db950206926234c1f633 + +Title = X25519 low-order public keys + +# u = 1 (low-order point) +PublicKeyRaw=LowOrder-Pub-1:X25519:0100000000000000000000000000000000000000000000000000000000000000 + +Derive=Alice-25519-Raw +PeerKey=LowOrder-Pub-1 +Result=DERIVE_ERROR + +# Wycheproof low-order point on the curve +PublicKeyRaw=LowOrder-Pub-2:X25519:e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800 + +Derive=Alice-25519-Raw +PeerKey=LowOrder-Pub-2 +Result=DERIVE_ERROR + +# Wycheproof low-order point on the twist +PublicKeyRaw=LowOrder-Pub-3:X25519:5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157 + +Derive=Alice-25519-Raw +PeerKey=LowOrder-Pub-3 +Result=DERIVE_ERROR + +# u = p - 1 (low-order) +PublicKeyRaw=LowOrder-Pub-4:X25519:ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=LowOrder-Pub-4 +Result=DERIVE_ERROR + +# Non-canonical encoding of u = 1 (value = 1 + p = eeff..ff7f) +# Tests both canonicalization AND low-order rejection +PublicKeyRaw=LowOrder-Pub-5-NonCanonical:X25519:eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=LowOrder-Pub-5-NonCanonical +Result=DERIVE_ERROR + +# Non-canonical encoding of identity (value = p = edff...ff7f, reduces to 0) +# Acceptance criterion #3: identity must be rejected +PublicKeyRaw=LowOrder-Pub-6-Identity-NonCanonical:X25519:edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=LowOrder-Pub-6-Identity-NonCanonical +Result=DERIVE_ERROR From d28b7251084c26ba4d86bc21e32ef539f4c3833f Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Fri, 24 Apr 2026 18:11:49 +0000 Subject: [PATCH 04/14] Fix SYMCRYPT_FLAG_KEY_NO_FIPS and canonicalization in set_encoded_key The p_scossl_ecc_set_encoded_key function (used by EVP_PKEY_new_raw_public_key and EVP_PKEY_new_raw_private_key) was missing the SYMCRYPT_FLAG_KEY_NO_FIPS flag for X25519 keys and was not canonicalizing X25519 public keys. This is the same class of fix applied to p_scossl_ecc_keymgmt_set_params but for the raw key import path. Changes: - Pass SYMCRYPT_FLAG_KEY_NO_FIPS for X25519 in SymCryptEckeySetValue - Allocate a mutable copy of the public key for X25519 and apply RFC 7748 section 5 canonicalization (mask high bit, reduce mod p = 2^255 - 19) - Update cleanup to free pbPublicKey unconditionally since both X25519 and non-X25519 paths now allocate it Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- SymCryptProvider/src/p_scossl_ecc.c | 45 ++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/SymCryptProvider/src/p_scossl_ecc.c b/SymCryptProvider/src/p_scossl_ecc.c index 6ca4172d..03f5756a 100644 --- a/SymCryptProvider/src/p_scossl_ecc.c +++ b/SymCryptProvider/src/p_scossl_ecc.c @@ -526,8 +526,42 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, { if (keyCtx->isX25519) { - pbPublicKey = (PBYTE) pbEncodedPublicKey; cbPublicKey = cbEncodedPublicKey; + + if ((pbPublicKey = OPENSSL_malloc(cbPublicKey)) == NULL) + { + ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE); + goto cleanup; + } + + memcpy(pbPublicKey, pbEncodedPublicKey, cbPublicKey); + + // RFC 7748 section 5: mask the high bit and reduce modulo p = 2^255 - 19. + // Non-canonical encodings (values in [p, 2^255-1]) must be accepted and + // treated as their canonical equivalent. + if (cbPublicKey == 32) + { + pbPublicKey[31] &= 0x7f; + + if (pbPublicKey[0] >= 0xed && pbPublicKey[31] == 0x7f) + { + BOOL nonCanonical = TRUE; + for (SIZE_T i = 1; i < 31; i++) + { + if (pbPublicKey[i] != 0xff) + { + nonCanonical = FALSE; + break; + } + } + + if (nonCanonical) + { + pbPublicKey[0] -= 0xed; + memset(&pbPublicKey[1], 0, 31); + } + } + } } else { @@ -581,7 +615,7 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, pbPublicKey, cbPublicKey, numFormat, pointFormat, - SYMCRYPT_FLAG_ECKEY_ECDH, + SYMCRYPT_FLAG_ECKEY_ECDH | (keyCtx->isX25519 ? SYMCRYPT_FLAG_KEY_NO_FIPS : 0), keyCtx->key); if (scError != SYMCRYPT_NO_ERROR) { @@ -600,16 +634,11 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, keyCtx->key = NULL; } - // X25519 needs to copy and decode the private key, other ECC needs - // to copy and decode the public key. + OPENSSL_free(pbPublicKey); if (keyCtx->isX25519) { OPENSSL_secure_clear_free(pbPrivateKey, cbEncodedPrivateKey); } - else - { - OPENSSL_free(pbPublicKey); - } EC_GROUP_free(ecGroup); EC_POINT_free(ecPoint); From 30f39159c342abfab5f487d3cec850f247cf4613 Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Fri, 24 Apr 2026 18:20:33 +0000 Subject: [PATCH 05/14] Refactor x25519 canonicalization into helper and fix all call sites Extract the X25519 public key canonicalization logic (RFC 7748 section 5 high-bit masking and modular reduction) into a shared helper function scossl_x25519_canonicalize_public_key, replacing the duplicated inline implementations in p_scossl_x25519_keymgmt_import and p_scossl_ecc_set_encoded_key. Fix p_scossl_ecc_keymgmt_set_params (OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY handler) which was missing both canonicalization and the SYMCRYPT_FLAG_KEY_NO_FIPS flag for X25519 keys. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/keymgmt/p_scossl_ecc_keymgmt.c | 33 +++--------- SymCryptProvider/src/p_scossl_ecc.c | 52 +++++++++++-------- SymCryptProvider/src/p_scossl_ecc.h | 2 + 3 files changed, 39 insertions(+), 48 deletions(-) diff --git a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c index d4cb533a..afc62d31 100644 --- a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c +++ b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c @@ -553,6 +553,11 @@ static SCOSSL_STATUS p_scossl_ecc_keymgmt_set_params(_Inout_ SCOSSL_ECC_KEY_CTX ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER); goto cleanup; } + + if (cbPublicKey == 32) + { + scossl_x25519_canonicalize_public_key(pbPublicKey); + } } else { @@ -585,7 +590,7 @@ static SCOSSL_STATUS p_scossl_ecc_keymgmt_set_params(_Inout_ SCOSSL_ECC_KEY_CTX pbPublicKey, cbPublicKey, numFormat, pointFormat, - SYMCRYPT_FLAG_ECKEY_ECDH, + SYMCRYPT_FLAG_ECKEY_ECDH | (keyCtx->isX25519 ? SYMCRYPT_FLAG_KEY_NO_FIPS : 0), keyCtx->key); if (scError != SYMCRYPT_NO_ERROR) { @@ -1107,28 +1112,9 @@ static SCOSSL_STATUS p_scossl_x25519_keymgmt_import(_Inout_ SCOSSL_ECC_KEY_CTX * // RFC 7748 section 5: mask the high bit and reduce modulo p = 2^255 - 19. // Non-canonical encodings (values in [p, 2^255-1]) must be accepted and // treated as their canonical equivalent. - if (cbPublicKey == 32) + if (pbPublicKey != NULL && cbPublicKey == 32) { - pbPublicKey[31] &= 0x7f; - - if (pbPublicKey[0] >= 0xed && pbPublicKey[31] == 0x7f) - { - BOOL nonCanonical = TRUE; - for (SIZE_T i = 1; i < 31; i++) - { - if (pbPublicKey[i] != 0xff) - { - nonCanonical = FALSE; - break; - } - } - - if (nonCanonical) - { - pbPublicKey[0] -= 0xed; - memset(&pbPublicKey[1], 0, 31); - } - } + scossl_x25519_canonicalize_public_key(pbPublicKey); } if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY)) != NULL) @@ -1148,9 +1134,6 @@ static SCOSSL_STATUS p_scossl_x25519_keymgmt_import(_Inout_ SCOSSL_ECC_KEY_CTX * pbPrivateKey[cbPrivateKey-1] |= 0x40; } - // RFC 7748 accepts any 32-byte string as a public key, including low-order - // and on-twist points that do not satisfy the FIPS subgroup check. - // X25519 is not a FIPS algorithm, so skip the FIPS check. scError = SymCryptEckeySetValue( pbPrivateKey, cbPrivateKey, pbPublicKey, cbPublicKey, diff --git a/SymCryptProvider/src/p_scossl_ecc.c b/SymCryptProvider/src/p_scossl_ecc.c index 03f5756a..a35e11c2 100644 --- a/SymCryptProvider/src/p_scossl_ecc.c +++ b/SymCryptProvider/src/p_scossl_ecc.c @@ -13,6 +13,34 @@ extern "C" { #define SCOSSL_X25519_MAX_SIZE (32) +// Canonicalize an X25519 public key in place per RFC 7748 section 5. +// Masks the high bit (bit 255) and reduces non-canonical values (>= p = 2^255 - 19) +// modulo p. The buffer must be exactly 32 bytes and mutable. +_Use_decl_annotations_ +void scossl_x25519_canonicalize_public_key(PBYTE pbPublicKey) +{ + pbPublicKey[31] &= 0x7f; + + if (pbPublicKey[0] >= 0xed && pbPublicKey[31] == 0x7f) + { + BOOL nonCanonical = TRUE; + for (SIZE_T i = 1; i < 31; i++) + { + if (pbPublicKey[i] != 0xff) + { + nonCanonical = FALSE; + break; + } + } + + if (nonCanonical) + { + pbPublicKey[0] -= 0xed; + memset(&pbPublicKey[1], 0, 31); + } + } +} + _Use_decl_annotations_ SCOSSL_ECC_KEY_CTX *p_scossl_ecc_new_ctx(SCOSSL_PROVCTX *provctx) { @@ -536,31 +564,9 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, memcpy(pbPublicKey, pbEncodedPublicKey, cbPublicKey); - // RFC 7748 section 5: mask the high bit and reduce modulo p = 2^255 - 19. - // Non-canonical encodings (values in [p, 2^255-1]) must be accepted and - // treated as their canonical equivalent. if (cbPublicKey == 32) { - pbPublicKey[31] &= 0x7f; - - if (pbPublicKey[0] >= 0xed && pbPublicKey[31] == 0x7f) - { - BOOL nonCanonical = TRUE; - for (SIZE_T i = 1; i < 31; i++) - { - if (pbPublicKey[i] != 0xff) - { - nonCanonical = FALSE; - break; - } - } - - if (nonCanonical) - { - pbPublicKey[0] -= 0xed; - memset(&pbPublicKey[1], 0, 31); - } - } + scossl_x25519_canonicalize_public_key(pbPublicKey); } } else diff --git a/SymCryptProvider/src/p_scossl_ecc.h b/SymCryptProvider/src/p_scossl_ecc.h index ff02ef64..be794574 100644 --- a/SymCryptProvider/src/p_scossl_ecc.h +++ b/SymCryptProvider/src/p_scossl_ecc.h @@ -52,6 +52,8 @@ SIZE_T p_scossl_ecc_get_max_result_size(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, BOOL is SIZE_T p_scossl_ecc_get_encoded_key_size(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, int selection); SCOSSL_STATUS p_scossl_ecc_get_encoded_key(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, int selection, _Out_writes_bytes_(*pcbKey) PBYTE *ppbKey, _Out_ SIZE_T *pcbKey); +void scossl_x25519_canonicalize_public_key(_Inout_updates_(32) PBYTE pbPublicKey); + SCOSSL_STATUS p_scossl_ecc_set_encoded_key(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, _In_reads_bytes_opt_(cbEncodedPublicKey) PCBYTE pbEncodedPublicKey, SIZE_T cbEncodedPublicKey, _In_reads_bytes_opt_(cbEncodedPrivateKey) PCBYTE pbEncodedPrivateKey, SIZE_T cbEncodedPrivateKey); From 688008ee180c2997f730c2194f7c81fb22acd47e Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Fri, 24 Apr 2026 19:13:50 +0000 Subject: [PATCH 06/14] Cleanup --- .../src/keymgmt/p_scossl_ecc_keymgmt.c | 12 +----------- SymCryptProvider/src/p_scossl_ecc.c | 15 +++++---------- SymCryptProvider/src/p_scossl_ecc.h | 4 +++- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c index afc62d31..8ee85356 100644 --- a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c +++ b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c @@ -13,7 +13,6 @@ extern "C" { #endif -#define SCOSSL_X25519_MAX_SIZE (32) #define SCOSSL_ECC_DEFAULT_DIGEST SN_sha256 #define SCOSSL_ECC_POSSIBLE_SELECTIONS (OSSL_KEYMGMT_SELECT_PUBLIC_KEY | OSSL_KEYMGMT_SELECT_PRIVATE_KEY | OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS) @@ -554,10 +553,7 @@ static SCOSSL_STATUS p_scossl_ecc_keymgmt_set_params(_Inout_ SCOSSL_ECC_KEY_CTX goto cleanup; } - if (cbPublicKey == 32) - { - scossl_x25519_canonicalize_public_key(pbPublicKey); - } + scossl_x25519_canonicalize_public_key(pbPublicKey); } else { @@ -1107,13 +1103,7 @@ static SCOSSL_STATUS p_scossl_x25519_keymgmt_import(_Inout_ SCOSSL_ECC_KEY_CTX * ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER); goto cleanup; } - } - // RFC 7748 section 5: mask the high bit and reduce modulo p = 2^255 - 19. - // Non-canonical encodings (values in [p, 2^255-1]) must be accepted and - // treated as their canonical equivalent. - if (pbPublicKey != NULL && cbPublicKey == 32) - { scossl_x25519_canonicalize_public_key(pbPublicKey); } diff --git a/SymCryptProvider/src/p_scossl_ecc.c b/SymCryptProvider/src/p_scossl_ecc.c index a35e11c2..2a7ae9ea 100644 --- a/SymCryptProvider/src/p_scossl_ecc.c +++ b/SymCryptProvider/src/p_scossl_ecc.c @@ -11,8 +11,6 @@ extern "C" { #endif -#define SCOSSL_X25519_MAX_SIZE (32) - // Canonicalize an X25519 public key in place per RFC 7748 section 5. // Masks the high bit (bit 255) and reduces non-canonical values (>= p = 2^255 - 19) // modulo p. The buffer must be exactly 32 bytes and mutable. @@ -520,6 +518,7 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, PBYTE pbPublicKey = NULL; SIZE_T cbPublicKey = 0; PBYTE pbPrivateKey = NULL; + UINT32 flags = SYMCRYPT_FLAG_ECKEY_ECDH; SYMCRYPT_ERROR scError; SCOSSL_STATUS ret = SCOSSL_FAILURE; @@ -532,6 +531,7 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, { numFormat = SYMCRYPT_NUMBER_FORMAT_LSB_FIRST; pointFormat = SYMCRYPT_ECPOINT_FORMAT_X; + flags |= SYMCRYPT_FLAG_KEY_NO_FIPS; } else { @@ -554,20 +554,15 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, { if (keyCtx->isX25519) { - cbPublicKey = cbEncodedPublicKey; - if ((pbPublicKey = OPENSSL_malloc(cbPublicKey)) == NULL) { ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE); goto cleanup; } + cbPublicKey = cbEncodedPublicKey; memcpy(pbPublicKey, pbEncodedPublicKey, cbPublicKey); - - if (cbPublicKey == 32) - { - scossl_x25519_canonicalize_public_key(pbPublicKey); - } + scossl_x25519_canonicalize_public_key(pbPublicKey); } else { @@ -621,7 +616,7 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, pbPublicKey, cbPublicKey, numFormat, pointFormat, - SYMCRYPT_FLAG_ECKEY_ECDH | (keyCtx->isX25519 ? SYMCRYPT_FLAG_KEY_NO_FIPS : 0), + flags, keyCtx->key); if (scError != SYMCRYPT_NO_ERROR) { diff --git a/SymCryptProvider/src/p_scossl_ecc.h b/SymCryptProvider/src/p_scossl_ecc.h index be794574..89aa48bd 100644 --- a/SymCryptProvider/src/p_scossl_ecc.h +++ b/SymCryptProvider/src/p_scossl_ecc.h @@ -12,6 +12,8 @@ extern "C" { #endif +#define SCOSSL_X25519_MAX_SIZE (32) + typedef struct { OSSL_LIB_CTX *libctx; BOOL initialized; @@ -52,7 +54,7 @@ SIZE_T p_scossl_ecc_get_max_result_size(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, BOOL is SIZE_T p_scossl_ecc_get_encoded_key_size(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, int selection); SCOSSL_STATUS p_scossl_ecc_get_encoded_key(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, int selection, _Out_writes_bytes_(*pcbKey) PBYTE *ppbKey, _Out_ SIZE_T *pcbKey); -void scossl_x25519_canonicalize_public_key(_Inout_updates_(32) PBYTE pbPublicKey); +void scossl_x25519_canonicalize_public_key(_Inout_updates_(SCOSSL_X25519_MAX_SIZE) PBYTE pbPublicKey); SCOSSL_STATUS p_scossl_ecc_set_encoded_key(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, _In_reads_bytes_opt_(cbEncodedPublicKey) PCBYTE pbEncodedPublicKey, SIZE_T cbEncodedPublicKey, From c9509ce6bfe0992788409672c7a30bb7a2c552d6 Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Mon, 27 Apr 2026 16:42:52 +0000 Subject: [PATCH 07/14] Add key length checks for x25519 --- .../src/keymgmt/p_scossl_ecc_keymgmt.c | 23 ++++++++++++++++++- SymCryptProvider/src/p_scossl_ecc.c | 9 +++++++- SymCryptProvider/src/p_scossl_ecc.h | 4 ++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c index 8ee85356..5ce359b9 100644 --- a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c +++ b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c @@ -526,6 +526,7 @@ static SCOSSL_STATUS p_scossl_ecc_keymgmt_set_params(_Inout_ SCOSSL_ECC_KEY_CTX SCOSSL_STATUS ret = SCOSSL_FAILURE; SYMCRYPT_NUMBER_FORMAT numFormat = keyCtx->isX25519 ? SYMCRYPT_NUMBER_FORMAT_LSB_FIRST : SYMCRYPT_NUMBER_FORMAT_MSB_FIRST; SYMCRYPT_ECPOINT_FORMAT pointFormat = keyCtx->isX25519 ? SYMCRYPT_ECPOINT_FORMAT_X : SYMCRYPT_ECPOINT_FORMAT_XY; + int flags = SYMCRYPT_FLAG_ECKEY_ECDH; const OSSL_PARAM *p; if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY)) != NULL) @@ -553,7 +554,15 @@ static SCOSSL_STATUS p_scossl_ecc_keymgmt_set_params(_Inout_ SCOSSL_ECC_KEY_CTX goto cleanup; } + if (cbPublicKey != SCOSSL_X25519_KEY_SIZE) + { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH); + goto cleanup; + } + scossl_x25519_canonicalize_public_key(pbPublicKey); + + flags |= SYMCRYPT_FLAG_KEY_NO_FIPS; } else { @@ -586,7 +595,7 @@ static SCOSSL_STATUS p_scossl_ecc_keymgmt_set_params(_Inout_ SCOSSL_ECC_KEY_CTX pbPublicKey, cbPublicKey, numFormat, pointFormat, - SYMCRYPT_FLAG_ECKEY_ECDH | (keyCtx->isX25519 ? SYMCRYPT_FLAG_KEY_NO_FIPS : 0), + flags, keyCtx->key); if (scError != SYMCRYPT_NO_ERROR) { @@ -1104,6 +1113,12 @@ static SCOSSL_STATUS p_scossl_x25519_keymgmt_import(_Inout_ SCOSSL_ECC_KEY_CTX * goto cleanup; } + if (cbPublicKey != SCOSSL_X25519_KEY_SIZE) + { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH); + goto cleanup; + } + scossl_x25519_canonicalize_public_key(pbPublicKey); } @@ -1115,6 +1130,12 @@ static SCOSSL_STATUS p_scossl_x25519_keymgmt_import(_Inout_ SCOSSL_ECC_KEY_CTX * goto cleanup; } + if (cbPrivateKey != SCOSSL_X25519_KEY_SIZE) + { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH); + goto cleanup; + } + // Preserve original bits for export keyCtx->modifiedPrivateBits = pbPrivateKey[0] & 0x07; keyCtx->modifiedPrivateBits |= pbPrivateKey[cbPrivateKey-1] & 0xc0; diff --git a/SymCryptProvider/src/p_scossl_ecc.c b/SymCryptProvider/src/p_scossl_ecc.c index 2a7ae9ea..a42d199f 100644 --- a/SymCryptProvider/src/p_scossl_ecc.c +++ b/SymCryptProvider/src/p_scossl_ecc.c @@ -260,7 +260,7 @@ SIZE_T p_scossl_ecc_get_max_result_size(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, BOOL is { if (keyCtx->isX25519) { - return SCOSSL_X25519_MAX_SIZE; + return SCOSSL_X25519_KEY_SIZE; } else if (isEcdh) { @@ -532,6 +532,13 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, numFormat = SYMCRYPT_NUMBER_FORMAT_LSB_FIRST; pointFormat = SYMCRYPT_ECPOINT_FORMAT_X; flags |= SYMCRYPT_FLAG_KEY_NO_FIPS; + + if (pbEncodedPublicKey != NULL && cbEncodedPublicKey != SCOSSL_X25519_KEY_SIZE || + pbEncodedPrivateKey != NULL && cbEncodedPrivateKey != SCOSSL_X25519_KEY_SIZE) + { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH); + goto cleanup; + } } else { diff --git a/SymCryptProvider/src/p_scossl_ecc.h b/SymCryptProvider/src/p_scossl_ecc.h index 89aa48bd..f53e000b 100644 --- a/SymCryptProvider/src/p_scossl_ecc.h +++ b/SymCryptProvider/src/p_scossl_ecc.h @@ -12,7 +12,7 @@ extern "C" { #endif -#define SCOSSL_X25519_MAX_SIZE (32) +#define SCOSSL_X25519_KEY_SIZE (32) typedef struct { OSSL_LIB_CTX *libctx; @@ -54,7 +54,7 @@ SIZE_T p_scossl_ecc_get_max_result_size(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, BOOL is SIZE_T p_scossl_ecc_get_encoded_key_size(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, int selection); SCOSSL_STATUS p_scossl_ecc_get_encoded_key(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, int selection, _Out_writes_bytes_(*pcbKey) PBYTE *ppbKey, _Out_ SIZE_T *pcbKey); -void scossl_x25519_canonicalize_public_key(_Inout_updates_(SCOSSL_X25519_MAX_SIZE) PBYTE pbPublicKey); +void scossl_x25519_canonicalize_public_key(_Inout_updates_(SCOSSL_X25519_KEY_SIZE) PBYTE pbPublicKey); SCOSSL_STATUS p_scossl_ecc_set_encoded_key(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, _In_reads_bytes_opt_(cbEncodedPublicKey) PCBYTE pbEncodedPublicKey, SIZE_T cbEncodedPublicKey, From 67221b6e8441350ce9b5904670d03b24ab8a0058 Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Mon, 27 Apr 2026 17:39:09 +0000 Subject: [PATCH 08/14] Add exhaustive X25519 non-canonical and low-order key tests Add complete coverage for all 19 non-canonical public key values (p+0 through p+18) that require reduction modulo p = 2^255-19. Previously only p+3 was tested. New non-canonical tests (NonCanonical-Pub-3 through Pub-18): - p+2 reduces to u=2, p+4 through p+18 reduce to u=4..18 - All derive with Alice's private key and verify expected shared secret - p+0 and p+1 (reducing to low-order u=0 and u=1) were already covered in the low-order section New low-order test: - Canonical u=0 (identity point) - was previously only tested via non-canonical encoding New high-bit (bit 255) variant tests: - Canonical non-low-order key with bit 255 set (Bob's key) - Canonical low-order key (u=0) with bit 255 set - Non-canonical key (p+2) with bit 255 set (success case) - Non-canonical low-order key (p+1) with bit 255 set (failure case) These verify bit 255 is masked before canonicalization and low-order checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- EvpTestRecipes/3.0/evppkey_ecx.txt | 156 +++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/EvpTestRecipes/3.0/evppkey_ecx.txt b/EvpTestRecipes/3.0/evppkey_ecx.txt index 1a830788..d2f66a9b 100644 --- a/EvpTestRecipes/3.0/evppkey_ecx.txt +++ b/EvpTestRecipes/3.0/evppkey_ecx.txt @@ -110,8 +110,164 @@ Derive=NonCanonical-Priv-2 PeerKey=NonCanonical-Pub-2 SharedSecret=38d6304c4a7e6d9f7959334fb5245bd2c754525d4c91db950206926234c1f633 +# efff..ff7f has value >= p, reduces to u = 2 mod p +PublicKeyRaw=NonCanonical-Pub-3:X25519:efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-3 +SharedSecret=E80C0BE9D3A1C5D71EDD6316E8C9115CA35397CD47109BD38E32864F1ADECF4D + +# f1ff..ff7f has value >= p, reduces to u = 4 mod p +PublicKeyRaw=NonCanonical-Pub-4:X25519:f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-4 +SharedSecret=3DC4B23E14B1E04DA492BAEBA54AE9CF9CD5298713903E6238E5128B31FA5E4E + +# f2ff..ff7f has value >= p, reduces to u = 5 mod p +PublicKeyRaw=NonCanonical-Pub-5:X25519:f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-5 +SharedSecret=717EFC3F25E74904CCDF9896B6EB5836E85A45B5A7A26F9E3ACF91F0EB7D3F4A + +# f3ff..ff7f has value >= p, reduces to u = 6 mod p +PublicKeyRaw=NonCanonical-Pub-6:X25519:f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-6 +SharedSecret=0FD3892BF714427086056B360E24A98CF0EE50BA6DC45744EBAA0BB8F9BC1F27 + +# f4ff..ff7f has value >= p, reduces to u = 7 mod p +PublicKeyRaw=NonCanonical-Pub-7:X25519:f4ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-7 +SharedSecret=AF4F41D8794E87F83BEF3BF7B0929D43133911C25C5105AFB221A79FC9946C59 + +# f5ff..ff7f has value >= p, reduces to u = 8 mod p +PublicKeyRaw=NonCanonical-Pub-8:X25519:f5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-8 +SharedSecret=C8F33C10FCF2B405BB6097703DE0D4E706522736FAE188E1EBF7E4C1BCC2211B + +# f6ff..ff7f has value >= p, reduces to u = 9 (the base point) mod p +PublicKeyRaw=NonCanonical-Pub-9:X25519:f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-9 +SharedSecret=8520F0098930A754748B7DDCB43EF75A0DBF3A0D26381AF4EBA4A98EAA9B4E6A + +# f7ff..ff7f has value >= p, reduces to u = 10 mod p +PublicKeyRaw=NonCanonical-Pub-10:X25519:f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-10 +SharedSecret=01EE50445ED8B4AE530677B182359A0E41312B80EA4CC72185D18F352F485065 + +# f8ff..ff7f has value >= p, reduces to u = 11 mod p +PublicKeyRaw=NonCanonical-Pub-11:X25519:f8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-11 +SharedSecret=E087CBDC36A1C45005481BC63B949590DCDC0F538A04C9BBC1C13E3DB0B82A52 + +# f9ff..ff7f has value >= p, reduces to u = 12 mod p +PublicKeyRaw=NonCanonical-Pub-12:X25519:f9ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-12 +SharedSecret=98205C5722464A4FEFA3535D51BB4BFD25E7F1584A4DD7AFA74CE035D058892A + +# faff..ff7f has value >= p, reduces to u = 13 mod p +PublicKeyRaw=NonCanonical-Pub-13:X25519:faffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-13 +SharedSecret=B8042BA97FC573CA543F1D05A56F3DC45BE0CB1B13CB9E01F07386E479980314 + +# fbff..ff7f has value >= p, reduces to u = 14 mod p +PublicKeyRaw=NonCanonical-Pub-14:X25519:fbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-14 +SharedSecret=53446A13586F563B6C32B9F40FA122A5F15AF45C5E750003A27C280CD39BC340 + +# fcff..ff7f has value >= p, reduces to u = 15 mod p +PublicKeyRaw=NonCanonical-Pub-15:X25519:fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-15 +SharedSecret=FD5168B75730FD82D6392D05D53E2346CCD2F0895D4588F68977BE2BD719BA3B + +# fdff..ff7f has value >= p, reduces to u = 16 mod p +PublicKeyRaw=NonCanonical-Pub-16:X25519:fdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-16 +SharedSecret=2FEF9732A4204D308743EC3DF457D9F6AF7AB3601097E4201FF04D773EC4C815 + +# feff..ff7f has value >= p, reduces to u = 17 mod p +PublicKeyRaw=NonCanonical-Pub-17:X25519:feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-17 +SharedSecret=81A02A45014594332261085128959869FC0540C6B12380F51DB4B41380DE2C2C + +# ffff..ff7f has value >= p, reduces to u = 18 mod p +PublicKeyRaw=NonCanonical-Pub-18:X25519:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f + +Derive=Alice-25519-Raw +PeerKey=NonCanonical-Pub-18 +SharedSecret=359668D79A67267A57FFEF8F0F4A9882A7C0E3122CB1999C5626346383F9F811 + +# Non-canonical p+0 (reduces to u = 0, identity) and p+1 (reduces to u = 1, low-order) +# are tested in the low-order section below as LowOrder-Pub-6-Identity-NonCanonical +# and LowOrder-Pub-5-NonCanonical respectively. + +Title = X25519 high bit (bit 255) set + +# Canonical non-low-order key with high bit set: must produce same shared secret +# Tests that bit 255 is masked before use +PublicKeyRaw=HighBit-Pub-1:X25519:de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882bcf + +Derive=Alice-25519-Raw +PeerKey=HighBit-Pub-1 +SharedSecret=4A5D9D5BA4CE2DE1728E3BF480350F25E07E21C947D19E3376F09B3C1E161742 + +# Canonical low-order key (u = 0) with high bit set +PublicKeyRaw=HighBit-LowOrder-Pub-1:X25519:0000000000000000000000000000000000000000000000000000000000000080 + +Derive=Alice-25519-Raw +PeerKey=HighBit-LowOrder-Pub-1 +Result=DERIVE_ERROR + +# Non-canonical (p+2, reduces to u = 2) with high bit set +# Tests that high bit is masked BEFORE non-canonical reduction +PublicKeyRaw=HighBit-NonCanonical-Pub-1:X25519:efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +Derive=Alice-25519-Raw +PeerKey=HighBit-NonCanonical-Pub-1 +SharedSecret=E80C0BE9D3A1C5D71EDD6316E8C9115CA35397CD47109BD38E32864F1ADECF4D + +# Non-canonical (p+1, reduces to u = 1, low-order) with high bit set +# Tests high-bit masking + non-canonical reduction + low-order rejection +PublicKeyRaw=HighBit-NonCanonical-Pub-2:X25519:eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +Derive=Alice-25519-Raw +PeerKey=HighBit-NonCanonical-Pub-2 +Result=DERIVE_ERROR + Title = X25519 low-order public keys +# u = 0 (identity) +PublicKeyRaw=LowOrder-Pub-0:X25519:0000000000000000000000000000000000000000000000000000000000000000 + +Derive=Alice-25519-Raw +PeerKey=LowOrder-Pub-0 +Result=DERIVE_ERROR + # u = 1 (low-order point) PublicKeyRaw=LowOrder-Pub-1:X25519:0100000000000000000000000000000000000000000000000000000000000000 From 63f7e153ee908581ac102af16d1cc38372b59722 Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Mon, 27 Apr 2026 17:40:20 +0000 Subject: [PATCH 09/14] Fix size check --- SymCryptProvider/src/p_scossl_ecc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SymCryptProvider/src/p_scossl_ecc.c b/SymCryptProvider/src/p_scossl_ecc.c index a42d199f..61d1fa8e 100644 --- a/SymCryptProvider/src/p_scossl_ecc.c +++ b/SymCryptProvider/src/p_scossl_ecc.c @@ -533,8 +533,8 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, pointFormat = SYMCRYPT_ECPOINT_FORMAT_X; flags |= SYMCRYPT_FLAG_KEY_NO_FIPS; - if (pbEncodedPublicKey != NULL && cbEncodedPublicKey != SCOSSL_X25519_KEY_SIZE || - pbEncodedPrivateKey != NULL && cbEncodedPrivateKey != SCOSSL_X25519_KEY_SIZE) + if ((pbEncodedPublicKey != NULL && cbEncodedPublicKey != SCOSSL_X25519_KEY_SIZE) || + (pbEncodedPrivateKey != NULL && cbEncodedPrivateKey != SCOSSL_X25519_KEY_SIZE)) { ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH); goto cleanup; From 83d11e451304cb8e3364418fcbc565fa7d4d1219 Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Mon, 27 Apr 2026 20:23:49 +0000 Subject: [PATCH 10/14] Rename function --- SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c | 4 ++-- SymCryptProvider/src/p_scossl_ecc.c | 4 ++-- SymCryptProvider/src/p_scossl_ecc.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c index 5ce359b9..506b7c19 100644 --- a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c +++ b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c @@ -560,7 +560,7 @@ static SCOSSL_STATUS p_scossl_ecc_keymgmt_set_params(_Inout_ SCOSSL_ECC_KEY_CTX goto cleanup; } - scossl_x25519_canonicalize_public_key(pbPublicKey); + p_scossl_x25519_canonicalize_public_key(pbPublicKey); flags |= SYMCRYPT_FLAG_KEY_NO_FIPS; } @@ -1119,7 +1119,7 @@ static SCOSSL_STATUS p_scossl_x25519_keymgmt_import(_Inout_ SCOSSL_ECC_KEY_CTX * goto cleanup; } - scossl_x25519_canonicalize_public_key(pbPublicKey); + p_scossl_x25519_canonicalize_public_key(pbPublicKey); } if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY)) != NULL) diff --git a/SymCryptProvider/src/p_scossl_ecc.c b/SymCryptProvider/src/p_scossl_ecc.c index 61d1fa8e..3b43e843 100644 --- a/SymCryptProvider/src/p_scossl_ecc.c +++ b/SymCryptProvider/src/p_scossl_ecc.c @@ -15,7 +15,7 @@ extern "C" { // Masks the high bit (bit 255) and reduces non-canonical values (>= p = 2^255 - 19) // modulo p. The buffer must be exactly 32 bytes and mutable. _Use_decl_annotations_ -void scossl_x25519_canonicalize_public_key(PBYTE pbPublicKey) +void p_scossl_x25519_canonicalize_public_key(PBYTE pbPublicKey) { pbPublicKey[31] &= 0x7f; @@ -569,7 +569,7 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, cbPublicKey = cbEncodedPublicKey; memcpy(pbPublicKey, pbEncodedPublicKey, cbPublicKey); - scossl_x25519_canonicalize_public_key(pbPublicKey); + p_scossl_x25519_canonicalize_public_key(pbPublicKey); } else { diff --git a/SymCryptProvider/src/p_scossl_ecc.h b/SymCryptProvider/src/p_scossl_ecc.h index f53e000b..3e396b32 100644 --- a/SymCryptProvider/src/p_scossl_ecc.h +++ b/SymCryptProvider/src/p_scossl_ecc.h @@ -54,7 +54,7 @@ SIZE_T p_scossl_ecc_get_max_result_size(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, BOOL is SIZE_T p_scossl_ecc_get_encoded_key_size(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, int selection); SCOSSL_STATUS p_scossl_ecc_get_encoded_key(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, int selection, _Out_writes_bytes_(*pcbKey) PBYTE *ppbKey, _Out_ SIZE_T *pcbKey); -void scossl_x25519_canonicalize_public_key(_Inout_updates_(SCOSSL_X25519_KEY_SIZE) PBYTE pbPublicKey); +void p_scossl_x25519_canonicalize_public_key(_Inout_updates_(SCOSSL_X25519_KEY_SIZE) PBYTE pbPublicKey); SCOSSL_STATUS p_scossl_ecc_set_encoded_key(_In_ SCOSSL_ECC_KEY_CTX *keyCtx, _In_reads_bytes_opt_(cbEncodedPublicKey) PCBYTE pbEncodedPublicKey, SIZE_T cbEncodedPublicKey, From 2a833acb6296bd3af9a90868b959109dbeabb17e Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Mon, 27 Apr 2026 22:19:37 +0000 Subject: [PATCH 11/14] Re-order non-canonical tests --- EvpTestRecipes/3.0/evppkey_ecx.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/EvpTestRecipes/3.0/evppkey_ecx.txt b/EvpTestRecipes/3.0/evppkey_ecx.txt index d2f66a9b..7a8a4bc5 100644 --- a/EvpTestRecipes/3.0/evppkey_ecx.txt +++ b/EvpTestRecipes/3.0/evppkey_ecx.txt @@ -92,30 +92,30 @@ Result = KEYPAIR_MISMATCH Title = X25519 non-canonical public keys -PrivateKeyRaw=NonCanonical-Priv-1:X25519:288796bc5aff4b81a37501757bc0753a3c21964790d38699308debc17a6eaf8d - -# f0ff..ff7f has value >= p, reduces to u = 3 mod p -PublicKeyRaw=NonCanonical-Pub-1:X25519:f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f +# efff..ff7f has value >= p, reduces to u = 2 mod p +PublicKeyRaw=NonCanonical-Pub-1:X25519:efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f -Derive=NonCanonical-Priv-1 +Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-1 -SharedSecret=b4e0dd76da7b071728b61f856771aa356e57eda78a5b1655cc3820fb5f854c5c +SharedSecret=E80C0BE9D3A1C5D71EDD6316E8C9115CA35397CD47109BD38E32864F1ADECF4D -PrivateKeyRaw=NonCanonical-Priv-2:X25519:60887b3dc72443026ebedbbbb70665f42b87add1440e7768fbd7e8e2ce5f639d +PrivateKeyRaw=NonCanonical-Priv-2:X25519:288796bc5aff4b81a37501757bc0753a3c21964790d38699308debc17a6eaf8d -# f0ff..ffff has high bit set AND value >= p after masking -PublicKeyRaw=NonCanonical-Pub-2:X25519:f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +# f0ff..ff7f has value >= p, reduces to u = 3 mod p +PublicKeyRaw=NonCanonical-Pub-2:X25519:f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=NonCanonical-Priv-2 PeerKey=NonCanonical-Pub-2 -SharedSecret=38d6304c4a7e6d9f7959334fb5245bd2c754525d4c91db950206926234c1f633 +SharedSecret=B4E0DD76DA7B071728B61F856771AA356E57EDA78A5B1655CC3820FB5F854C5C -# efff..ff7f has value >= p, reduces to u = 2 mod p -PublicKeyRaw=NonCanonical-Pub-3:X25519:efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f +PrivateKeyRaw=NonCanonical-Priv-3:X25519:60887b3dc72443026ebedbbbb70665f42b87add1440e7768fbd7e8e2ce5f639d -Derive=Alice-25519-Raw +# f0ff..ffff has high bit set AND value >= p after masking, reduces to u = 3 mod p +PublicKeyRaw=NonCanonical-Pub-3:X25519:f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +Derive=NonCanonical-Priv-3 PeerKey=NonCanonical-Pub-3 -SharedSecret=E80C0BE9D3A1C5D71EDD6316E8C9115CA35397CD47109BD38E32864F1ADECF4D +SharedSecret=38D6304C4A7E6D9F7959334FB5245BD2C754525D4C91DB950206926234C1F633 # f1ff..ff7f has value >= p, reduces to u = 4 mod p PublicKeyRaw=NonCanonical-Pub-4:X25519:f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f From 66649d70aa3c8343a11a6df6bdf823a1910d38fe Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Mon, 27 Apr 2026 22:20:07 +0000 Subject: [PATCH 12/14] Clean up test comments --- EvpTestRecipes/3.0/evppkey_ecx.txt | 48 ++++++++++++++---------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/EvpTestRecipes/3.0/evppkey_ecx.txt b/EvpTestRecipes/3.0/evppkey_ecx.txt index 7a8a4bc5..e8a0dd1b 100644 --- a/EvpTestRecipes/3.0/evppkey_ecx.txt +++ b/EvpTestRecipes/3.0/evppkey_ecx.txt @@ -92,130 +92,126 @@ Result = KEYPAIR_MISMATCH Title = X25519 non-canonical public keys -# efff..ff7f has value >= p, reduces to u = 2 mod p +# reduces to u = 2 mod p PublicKeyRaw=NonCanonical-Pub-1:X25519:efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-1 SharedSecret=E80C0BE9D3A1C5D71EDD6316E8C9115CA35397CD47109BD38E32864F1ADECF4D -PrivateKeyRaw=NonCanonical-Priv-2:X25519:288796bc5aff4b81a37501757bc0753a3c21964790d38699308debc17a6eaf8d - -# f0ff..ff7f has value >= p, reduces to u = 3 mod p +# reduces to u = 3 mod p PublicKeyRaw=NonCanonical-Pub-2:X25519:f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f -Derive=NonCanonical-Priv-2 +Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-2 -SharedSecret=B4E0DD76DA7B071728B61F856771AA356E57EDA78A5B1655CC3820FB5F854C5C +SharedSecret=2B6282A3A5AA1380B79D8ED2F73811F182E35E17E25A86D23C4D2F65713A6B7F -PrivateKeyRaw=NonCanonical-Priv-3:X25519:60887b3dc72443026ebedbbbb70665f42b87add1440e7768fbd7e8e2ce5f639d - -# f0ff..ffff has high bit set AND value >= p after masking, reduces to u = 3 mod p +# high bit set AND value >= p after masking, reduces to u = 3 mod p PublicKeyRaw=NonCanonical-Pub-3:X25519:f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -Derive=NonCanonical-Priv-3 +Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-3 -SharedSecret=38D6304C4A7E6D9F7959334FB5245BD2C754525D4C91DB950206926234C1F633 +SharedSecret=2B6282A3A5AA1380B79D8ED2F73811F182E35E17E25A86D23C4D2F65713A6B7F -# f1ff..ff7f has value >= p, reduces to u = 4 mod p +# reduces to u = 4 mod p PublicKeyRaw=NonCanonical-Pub-4:X25519:f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-4 SharedSecret=3DC4B23E14B1E04DA492BAEBA54AE9CF9CD5298713903E6238E5128B31FA5E4E -# f2ff..ff7f has value >= p, reduces to u = 5 mod p +# reduces to u = 5 mod p PublicKeyRaw=NonCanonical-Pub-5:X25519:f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-5 SharedSecret=717EFC3F25E74904CCDF9896B6EB5836E85A45B5A7A26F9E3ACF91F0EB7D3F4A -# f3ff..ff7f has value >= p, reduces to u = 6 mod p +# reduces to u = 6 mod p PublicKeyRaw=NonCanonical-Pub-6:X25519:f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-6 SharedSecret=0FD3892BF714427086056B360E24A98CF0EE50BA6DC45744EBAA0BB8F9BC1F27 -# f4ff..ff7f has value >= p, reduces to u = 7 mod p +# reduces to u = 7 mod p PublicKeyRaw=NonCanonical-Pub-7:X25519:f4ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-7 SharedSecret=AF4F41D8794E87F83BEF3BF7B0929D43133911C25C5105AFB221A79FC9946C59 -# f5ff..ff7f has value >= p, reduces to u = 8 mod p +# reduces to u = 8 mod p PublicKeyRaw=NonCanonical-Pub-8:X25519:f5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-8 SharedSecret=C8F33C10FCF2B405BB6097703DE0D4E706522736FAE188E1EBF7E4C1BCC2211B -# f6ff..ff7f has value >= p, reduces to u = 9 (the base point) mod p +# reduces to u = 9 (the base point) mod p PublicKeyRaw=NonCanonical-Pub-9:X25519:f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-9 SharedSecret=8520F0098930A754748B7DDCB43EF75A0DBF3A0D26381AF4EBA4A98EAA9B4E6A -# f7ff..ff7f has value >= p, reduces to u = 10 mod p +# reduces to u = 10 mod p PublicKeyRaw=NonCanonical-Pub-10:X25519:f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-10 SharedSecret=01EE50445ED8B4AE530677B182359A0E41312B80EA4CC72185D18F352F485065 -# f8ff..ff7f has value >= p, reduces to u = 11 mod p +# reduces to u = 11 mod p PublicKeyRaw=NonCanonical-Pub-11:X25519:f8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-11 SharedSecret=E087CBDC36A1C45005481BC63B949590DCDC0F538A04C9BBC1C13E3DB0B82A52 -# f9ff..ff7f has value >= p, reduces to u = 12 mod p +# reduces to u = 12 mod p PublicKeyRaw=NonCanonical-Pub-12:X25519:f9ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-12 SharedSecret=98205C5722464A4FEFA3535D51BB4BFD25E7F1584A4DD7AFA74CE035D058892A -# faff..ff7f has value >= p, reduces to u = 13 mod p +# reduces to u = 13 mod p PublicKeyRaw=NonCanonical-Pub-13:X25519:faffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-13 SharedSecret=B8042BA97FC573CA543F1D05A56F3DC45BE0CB1B13CB9E01F07386E479980314 -# fbff..ff7f has value >= p, reduces to u = 14 mod p +# reduces to u = 14 mod p PublicKeyRaw=NonCanonical-Pub-14:X25519:fbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-14 SharedSecret=53446A13586F563B6C32B9F40FA122A5F15AF45C5E750003A27C280CD39BC340 -# fcff..ff7f has value >= p, reduces to u = 15 mod p +# reduces to u = 15 mod p PublicKeyRaw=NonCanonical-Pub-15:X25519:fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-15 SharedSecret=FD5168B75730FD82D6392D05D53E2346CCD2F0895D4588F68977BE2BD719BA3B -# fdff..ff7f has value >= p, reduces to u = 16 mod p +# reduces to u = 16 mod p PublicKeyRaw=NonCanonical-Pub-16:X25519:fdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-16 SharedSecret=2FEF9732A4204D308743EC3DF457D9F6AF7AB3601097E4201FF04D773EC4C815 -# feff..ff7f has value >= p, reduces to u = 17 mod p +# reduces to u = 17 mod p PublicKeyRaw=NonCanonical-Pub-17:X25519:feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw PeerKey=NonCanonical-Pub-17 SharedSecret=81A02A45014594332261085128959869FC0540C6B12380F51DB4B41380DE2C2C -# ffff..ff7f has value >= p, reduces to u = 18 mod p +# reduces to u = 18 mod p PublicKeyRaw=NonCanonical-Pub-18:X25519:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f Derive=Alice-25519-Raw From ed60c6133f0739191c6158aea4a6d9c8564fe2dc Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Mon, 27 Apr 2026 22:36:30 +0000 Subject: [PATCH 13/14] Cleanup Co-authored-by: Copilot --- SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c | 2 +- SymCryptProvider/src/p_scossl_ecc.c | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c index 506b7c19..368c79ac 100644 --- a/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c +++ b/SymCryptProvider/src/keymgmt/p_scossl_ecc_keymgmt.c @@ -526,7 +526,7 @@ static SCOSSL_STATUS p_scossl_ecc_keymgmt_set_params(_Inout_ SCOSSL_ECC_KEY_CTX SCOSSL_STATUS ret = SCOSSL_FAILURE; SYMCRYPT_NUMBER_FORMAT numFormat = keyCtx->isX25519 ? SYMCRYPT_NUMBER_FORMAT_LSB_FIRST : SYMCRYPT_NUMBER_FORMAT_MSB_FIRST; SYMCRYPT_ECPOINT_FORMAT pointFormat = keyCtx->isX25519 ? SYMCRYPT_ECPOINT_FORMAT_X : SYMCRYPT_ECPOINT_FORMAT_XY; - int flags = SYMCRYPT_FLAG_ECKEY_ECDH; + UINT32 flags = SYMCRYPT_FLAG_ECKEY_ECDH; const OSSL_PARAM *p; if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY)) != NULL) diff --git a/SymCryptProvider/src/p_scossl_ecc.c b/SymCryptProvider/src/p_scossl_ecc.c index 3b43e843..553bd76d 100644 --- a/SymCryptProvider/src/p_scossl_ecc.c +++ b/SymCryptProvider/src/p_scossl_ecc.c @@ -79,6 +79,7 @@ SCOSSL_ECC_KEY_CTX *p_scossl_ecc_dup_ctx(SCOSSL_ECC_KEY_CTX *keyCtx, int selecti SIZE_T cbPrivateKey = 0; SCOSSL_STATUS success = SCOSSL_FAILURE; SYMCRYPT_ECPOINT_FORMAT pointFormat = keyCtx->isX25519 ? SYMCRYPT_ECPOINT_FORMAT_X : SYMCRYPT_ECPOINT_FORMAT_XY; + UINT32 flags = SYMCRYPT_FLAG_ECKEY_ECDH; SYMCRYPT_ERROR scError = SYMCRYPT_NO_ERROR; SCOSSL_ECC_KEY_CTX *copyCtx = OPENSSL_zalloc(sizeof(SCOSSL_ECC_KEY_CTX)); @@ -146,13 +147,18 @@ SCOSSL_ECC_KEY_CTX *p_scossl_ecc_dup_ctx(SCOSSL_ECC_KEY_CTX *keyCtx, int selecti goto cleanup; } + if (keyCtx->isX25519) + { + flags |= SYMCRYPT_FLAG_KEY_NO_FIPS; + } + // Default ECDH only. If the key is used for ECDSA then we call SymCryptEckeyExtendKeyUsage scError = SymCryptEckeySetValue( pbPrivateKey, cbPrivateKey, pbPublicKey, cbPublicKey, SYMCRYPT_NUMBER_FORMAT_MSB_FIRST, pointFormat, - SYMCRYPT_FLAG_ECKEY_ECDH | (keyCtx->isX25519 ? SYMCRYPT_FLAG_KEY_NO_FIPS : 0), + flags, copyCtx->key); if (scError != SYMCRYPT_NO_ERROR) { From 768f4c5cfa342cbe3bc6f57c39a519edd85f6ee4 Mon Sep 17 00:00:00 2001 From: Maxwell Moyer-McKee Date: Mon, 27 Apr 2026 23:57:24 +0000 Subject: [PATCH 14/14] Use correct variable for malloc --- SymCryptProvider/src/p_scossl_ecc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SymCryptProvider/src/p_scossl_ecc.c b/SymCryptProvider/src/p_scossl_ecc.c index 553bd76d..4e2704c1 100644 --- a/SymCryptProvider/src/p_scossl_ecc.c +++ b/SymCryptProvider/src/p_scossl_ecc.c @@ -567,7 +567,7 @@ SCOSSL_STATUS p_scossl_ecc_set_encoded_key(SCOSSL_ECC_KEY_CTX *keyCtx, { if (keyCtx->isX25519) { - if ((pbPublicKey = OPENSSL_malloc(cbPublicKey)) == NULL) + if ((pbPublicKey = OPENSSL_malloc(cbEncodedPublicKey)) == NULL) { ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE); goto cleanup;