From d64dad9712f5577b130781ce1d7f9c658d0ab39b Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Mon, 20 Apr 2026 15:53:57 -0700 Subject: [PATCH 01/15] ec changes --- .../Interop.EcDsa.ImportExport.cs | 113 +++++ .../ECDiffieHellmanOpenSsl.Derive.cs | 27 +- .../ECDiffieHellmanOpenSslPublicKey.cs | 32 +- .../System/Security/Cryptography/ECOpenSsl.cs | 77 +++- .../entrypoints.c | 3 + .../opensslshim.h | 17 + .../pal_ecc_import_export.c | 435 ++++++++++++++++++ .../pal_ecc_import_export.h | 38 ++ 8 files changed, 694 insertions(+), 48 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs index b5cf5295e47872..ea86f87d0fd245 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs @@ -96,6 +96,119 @@ internal static SafeEcKeyHandle EcKeyCreateByExplicitCurve(ECCurve curve) return key; } + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCreateByEcKeyParameters", StringMarshalling = StringMarshalling.Utf8)] + private static partial int EvpPKeyCreateByEcKeyParameters( + out SafeEvpPKeyHandle pkey, + string oid, + byte[]? qx, int qxLength, + byte[]? qy, int qyLength, + byte[]? d, int dLength); + + internal static SafeEvpPKeyHandle? EvpPKeyCreateByEcKeyParameters( + string oid, + byte[]? qx, int qxLength, + byte[]? qy, int qyLength, + byte[]? d, int dLength) + { + SafeEvpPKeyHandle pkey; + int rc = EvpPKeyCreateByEcKeyParameters(out pkey, oid, qx, qxLength, qy, qyLength, d, dLength); + + if (rc == -1) + { + pkey.Dispose(); + ErrClearError(); + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, oid)); + } + + if (rc != 1 || pkey.IsInvalid) + { + pkey.Dispose(); + ErrClearError(); + return null; + } + + return pkey; + } + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCreateByEcExplicitParameters")] + private static partial SafeEvpPKeyHandle CryptoNative_EvpPKeyCreateByEcExplicitParameters( + ECCurve.ECCurveType curveType, + byte[]? qx, int qxLength, + byte[]? qy, int qyLength, + byte[]? d, int dLength, + byte[] p, int pLength, + byte[] a, int aLength, + byte[] b, int bLength, + byte[] gx, int gxLength, + byte[] gy, int gyLength, + byte[] order, int orderLength, + byte[]? cofactor, int cofactorLength, + byte[]? seed, int seedLength); + + internal static SafeEvpPKeyHandle? EvpPKeyCreateByEcExplicitParameters( + ECCurve.ECCurveType curveType, + byte[]? qx, int qxLength, + byte[]? qy, int qyLength, + byte[]? d, int dLength, + byte[] p, int pLength, + byte[] a, int aLength, + byte[] b, int bLength, + byte[] gx, int gxLength, + byte[] gy, int gyLength, + byte[] order, int orderLength, + byte[]? cofactor, int cofactorLength, + byte[]? seed, int seedLength) + { + SafeEvpPKeyHandle pkey = CryptoNative_EvpPKeyCreateByEcExplicitParameters( + curveType, + qx, qxLength, + qy, qyLength, + d, dLength, + p, pLength, + a, aLength, + b, bLength, + gx, gxLength, + gy, gyLength, + order, orderLength, + cofactor, cofactorLength, + seed, seedLength); + + if (pkey.IsInvalid) + { + pkey.Dispose(); + ErrClearError(); + return null; + } + + return pkey; + } + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyGenerateByEcKeyOid", StringMarshalling = StringMarshalling.Utf8)] + private static partial int EvpPKeyGenerateByEcKeyOid( + out SafeEvpPKeyHandle pkey, + string oid); + + internal static SafeEvpPKeyHandle? EvpPKeyGenerateByEcKeyOid(string oid) + { + int rc = EvpPKeyGenerateByEcKeyOid(out SafeEvpPKeyHandle pkey, oid); + + if (rc == -1) + { + pkey.Dispose(); + ErrClearError(); + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, oid)); + } + + if (rc != 1 || pkey.IsInvalid) + { + pkey.Dispose(); + ErrClearError(); + return null; + } + + return pkey; + } + [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_EvpPKeyGetEcGroupNid(SafeEvpPKeyHandle pkey, out int nid); diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index a064f682ca65c6..d58fdde2e761d0 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -88,20 +88,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa Debug.Assert(otherPartyPublicKey != null); Debug.Assert(_key is not null); // Callers should validate prior. - bool thisIsNamed; - - using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) - { - if (ecKey == null || ecKey.IsInvalid) - { - // This may happen when EVP_PKEY was created by provider and getting EC_KEY is not possible. - thisIsNamed = Interop.Crypto.EvpPKeyHasCurveName(_key.Value); - } - else - { - thisIsNamed = Interop.Crypto.EcKeyHasCurveName(ecKey); - } - } + bool thisIsNamed = Interop.Crypto.EvpPKeyHasCurveName(_key.Value); ECDiffieHellmanOpenSslPublicKey? otherKey = otherPartyPublicKey as ECDiffieHellmanOpenSslPublicKey; bool disposeOtherKey = false; @@ -143,10 +130,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa } else if (otherIsNamed) { - using (ECOpenSsl tmp = new ECOpenSsl(otherKey.ExportExplicitParameters())) - { - theirKey = tmp.CreateEvpPKeyHandle(); - } + theirKey = ECOpenSsl.ImportECKey(otherKey.ExportExplicitParameters(), out _); } else { @@ -155,11 +139,8 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa // This is generally not expected to fail except: // - when key can't be accessed but is available (i.e. TPM) // - private key is actually missing - using (ECOpenSsl tmp = new ECOpenSsl(ExportExplicitParameters(true))) - { - ourKey = tmp.CreateEvpPKeyHandle(); - disposeOurKey = true; - } + ourKey = ECOpenSsl.ImportECKey(ExportExplicitParameters(true), out _); + disposeOurKey = true; } catch (CryptographicException) { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs index 8a225c5b4b3951..16aba3f532d63b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs @@ -8,7 +8,7 @@ namespace System.Security.Cryptography { internal sealed class ECDiffieHellmanOpenSslPublicKey : ECDiffieHellmanPublicKey { - private ECOpenSsl? _key; + private SafeEvpPKeyHandle? _key; internal ECDiffieHellmanOpenSslPublicKey(SafeEvpPKeyHandle pkeyHandle) { @@ -17,27 +17,12 @@ internal ECDiffieHellmanOpenSslPublicKey(SafeEvpPKeyHandle pkeyHandle) if (pkeyHandle.IsInvalid) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(pkeyHandle)); - // If ecKey is valid it has already been up-ref'd, so we can just use this handle as-is. - SafeEcKeyHandle key = Interop.Crypto.EvpPkeyGetEcKey(pkeyHandle); - - if (key == null || key.IsInvalid) - { - key?.Dispose(); - - // This may happen when EVP_PKEY was created by provider and getting EC_KEY is not possible. - // Since you cannot mix EC_KEY and EVP_PKEY params API we need to export and re-import the public key. - ECParameters ecParams = ECOpenSsl.ExportParameters(pkeyHandle, includePrivateParameters: false); - _key = new ECOpenSsl(ecParams); - } - else - { - _key = new ECOpenSsl(key); - } + _key = pkeyHandle.DuplicateHandle(); } internal ECDiffieHellmanOpenSslPublicKey(ECParameters parameters) { - _key = new ECOpenSsl(parameters); + _key = ECOpenSsl.ImportECKey(parameters, out _); } #pragma warning disable 0672 // Member overrides an obsolete member. @@ -60,14 +45,14 @@ public override ECParameters ExportExplicitParameters() => public override ECParameters ExportParameters() => ECOpenSsl.ExportParameters(GetKey(), includePrivateParameters: false); - internal bool HasCurveName => Interop.Crypto.EcKeyHasCurveName(GetKey()); + internal bool HasCurveName => Interop.Crypto.EvpPKeyHasCurveName(GetKey()); internal int KeySize { get { ThrowIfDisposed(); - return _key.KeySize; + return Interop.Crypto.EvpPKeyBits(_key); } } @@ -84,8 +69,7 @@ protected override void Dispose(bool disposing) internal SafeEvpPKeyHandle DuplicateKeyHandle() { - SafeEcKeyHandle currentKey = GetKey(); - return Interop.Crypto.CreateEvpPkeyFromEcKey(currentKey); + return GetKey().DuplicateHandle(); } [MemberNotNull(nameof(_key))] @@ -94,10 +78,10 @@ private void ThrowIfDisposed() ObjectDisposedException.ThrowIf(_key is null, this); } - private SafeEcKeyHandle GetKey() + private SafeEvpPKeyHandle GetKey() { ThrowIfDisposed(); - return _key.Value; + return _key; } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index 20fd8788b1053e..ef2b7036bd3bac 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -117,6 +117,22 @@ private void FreeKey() internal static SafeEvpPKeyHandle GenerateECKey(int keySize) { + string oid = keySize switch + { + 256 => ECOpenSsl.ECDSA_P256_OID_VALUE, + 384 => ECOpenSsl.ECDSA_P384_OID_VALUE, + 521 => ECOpenSsl.ECDSA_P521_OID_VALUE, + _ => throw new InvalidOperationException(SR.Cryptography_InvalidKeySize), + }; + + SafeEvpPKeyHandle? pkey = Interop.Crypto.EvpPKeyGenerateByEcKeyOid(oid); + + if (pkey is not null) + { + return pkey; + } + + // Fallback to legacy EC_KEY path SafeEvpPKeyHandle ret = ImportECKeyCore(new ECOpenSsl(keySize), out int createdKeySize); Debug.Assert(keySize == createdKeySize); return ret; @@ -124,11 +140,70 @@ internal static SafeEvpPKeyHandle GenerateECKey(int keySize) internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) { - return ImportECKeyCore(new ECOpenSsl(curve), out keySize); + if (curve.IsNamed) + { + string oid = !string.IsNullOrEmpty(curve.Oid.Value) ? curve.Oid.Value : curve.Oid.FriendlyName!; + + SafeEvpPKeyHandle? pkey = Interop.Crypto.EvpPKeyGenerateByEcKeyOid(oid); + + if (pkey is not null) + { + keySize = Interop.Crypto.EvpPKeyBits(pkey); + return pkey; + } + } + + // Fallback to legacy EC_KEY path (explicit curves or OpenSSL < 3.0) + return ImportECKeyCore(new ECOpenSsl(curve), out keySize); } internal static SafeEvpPKeyHandle ImportECKey(ECParameters parameters, out int keySize) { + parameters.Validate(); + + if (parameters.Curve.IsNamed) + { + string oid = !string.IsNullOrEmpty(parameters.Curve.Oid.Value) ? + parameters.Curve.Oid.Value : parameters.Curve.Oid.FriendlyName!; + + SafeEvpPKeyHandle? pkey = Interop.Crypto.EvpPKeyCreateByEcKeyParameters( + oid, + parameters.Q.X, parameters.Q.X?.Length ?? 0, + parameters.Q.Y, parameters.Q.Y?.Length ?? 0, + parameters.D, parameters.D is null ? 0 : parameters.D.Length); + + if (pkey is not null) + { + keySize = Interop.Crypto.EvpPKeyBits(pkey); + return pkey; + } + } + else if (parameters.Curve.IsPrime || parameters.Curve.IsCharacteristic2) + { + byte[] pField = parameters.Curve.IsPrime ? parameters.Curve.Prime! : parameters.Curve.Polynomial!; + + SafeEvpPKeyHandle? pkey = Interop.Crypto.EvpPKeyCreateByEcExplicitParameters( + parameters.Curve.CurveType, + parameters.Q.X, parameters.Q.X?.Length ?? 0, + parameters.Q.Y, parameters.Q.Y?.Length ?? 0, + parameters.D, parameters.D is null ? 0 : parameters.D.Length, + pField, pField.Length, + parameters.Curve.A!, parameters.Curve.A!.Length, + parameters.Curve.B!, parameters.Curve.B!.Length, + parameters.Curve.G.X!, parameters.Curve.G.X!.Length, + parameters.Curve.G.Y!, parameters.Curve.G.Y!.Length, + parameters.Curve.Order!, parameters.Curve.Order!.Length, + parameters.Curve.Cofactor!, parameters.Curve.Cofactor!.Length, + parameters.Curve.Seed, parameters.Curve.Seed is null ? 0 : parameters.Curve.Seed.Length); + + if (pkey is not null) + { + keySize = Interop.Crypto.EvpPKeyBits(pkey); + return pkey; + } + } + + // Fallback to legacy EC_KEY path return ImportECKeyCore(new ECOpenSsl(parameters), out keySize); } diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index 389504b3265e3f..7b3f323b5d400b 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -189,6 +189,9 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpPKeyGetEcKeyParameters) DllImportEntry(CryptoNative_EvpPKeyGetEcGroupNid) DllImportEntry(CryptoNative_EvpPKeyGetEcCurveParameters) + DllImportEntry(CryptoNative_EvpPKeyCreateByEcKeyParameters) + DllImportEntry(CryptoNative_EvpPKeyCreateByEcExplicitParameters) + DllImportEntry(CryptoNative_EvpPKeyGenerateByEcKeyOid) DllImportEntry(CryptoNative_EvpRC2Cbc) DllImportEntry(CryptoNative_EvpRC2Ecb) DllImportEntry(CryptoNative_EvpSha1) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 22425beb8a4126..c5f37871d5e764 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #endif @@ -535,6 +536,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_PKEY_CTX_new_id) \ LIGHTUP_FUNCTION(EVP_PKEY_CTX_new_from_name) \ LIGHTUP_FUNCTION(EVP_PKEY_CTX_new_from_pkey) \ + LIGHTUP_FUNCTION(EVP_PKEY_CTX_set_group_name) \ LIGHTUP_FUNCTION(EVP_PKEY_CTX_set_params) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_keygen_bits) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_oaep_md) \ @@ -660,6 +662,13 @@ extern bool g_libSslUses32BitTime; LIGHTUP_FUNCTION(OSSL_PARAM_construct_int) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_int32) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_end) \ + LIGHTUP_FUNCTION(OSSL_PARAM_BLD_new) \ + LIGHTUP_FUNCTION(OSSL_PARAM_BLD_free) \ + LIGHTUP_FUNCTION(OSSL_PARAM_BLD_push_utf8_string) \ + LIGHTUP_FUNCTION(OSSL_PARAM_BLD_push_octet_string) \ + LIGHTUP_FUNCTION(OSSL_PARAM_BLD_push_BN) \ + LIGHTUP_FUNCTION(OSSL_PARAM_BLD_to_param) \ + LIGHTUP_FUNCTION(OSSL_PARAM_free) \ REQUIRED_FUNCTION(PKCS8_PRIV_KEY_INFO_free) \ REQUIRED_FUNCTION(PEM_read_bio_PKCS7) \ REQUIRED_FUNCTION(PEM_read_bio_X509) \ @@ -1092,6 +1101,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_CTX_get0_pkey EVP_PKEY_CTX_get0_pkey_ptr #define EVP_PKEY_CTX_new EVP_PKEY_CTX_new_ptr #define EVP_PKEY_CTX_new_id EVP_PKEY_CTX_new_id_ptr +#define EVP_PKEY_CTX_set_group_name EVP_PKEY_CTX_set_group_name_ptr #define EVP_PKEY_CTX_set_params EVP_PKEY_CTX_set_params_ptr #define EVP_PKEY_CTX_set_rsa_keygen_bits EVP_PKEY_CTX_set_rsa_keygen_bits_ptr #define EVP_PKEY_CTX_set_rsa_oaep_md EVP_PKEY_CTX_set_rsa_oaep_md_ptr @@ -1222,6 +1232,13 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define OSSL_PARAM_construct_int OSSL_PARAM_construct_int_ptr #define OSSL_PARAM_construct_int32 OSSL_PARAM_construct_int32_ptr #define OSSL_PARAM_construct_end OSSL_PARAM_construct_end_ptr +#define OSSL_PARAM_BLD_new OSSL_PARAM_BLD_new_ptr +#define OSSL_PARAM_BLD_free OSSL_PARAM_BLD_free_ptr +#define OSSL_PARAM_BLD_push_utf8_string OSSL_PARAM_BLD_push_utf8_string_ptr +#define OSSL_PARAM_BLD_push_octet_string OSSL_PARAM_BLD_push_octet_string_ptr +#define OSSL_PARAM_BLD_push_BN OSSL_PARAM_BLD_push_BN_ptr +#define OSSL_PARAM_BLD_to_param OSSL_PARAM_BLD_to_param_ptr +#define OSSL_PARAM_free OSSL_PARAM_free_ptr #define PKCS8_PRIV_KEY_INFO_free PKCS8_PRIV_KEY_INFO_free_ptr #define PEM_read_bio_PKCS7 PEM_read_bio_PKCS7_ptr #define PEM_read_bio_X509 PEM_read_bio_X509_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index 2dc23d98036760..390c77dae1dc3f 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -939,3 +939,438 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( return 0; #endif } + +int32_t CryptoNative_EvpPKeyGenerateByEcKeyOid( + EVP_PKEY** pkey, + const char* oid) +{ + if (!pkey || !oid) + { + assert(false); + return 0; + } + + *pkey = NULL; + + ERR_clear_error(); + +#ifdef NEED_OPENSSL_3_0 + if (!API_EXISTS(EVP_PKEY_keygen)) + { + return 0; + } + + int nid = OBJ_txt2nid(oid); + if (!nid) + { + return -1; + } + + const char* groupName = OBJ_nid2sn(nid); + if (!groupName) + { + return -1; + } + + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + if (ctx == NULL) + { + goto error; + } + + if (EVP_PKEY_keygen_init(ctx) <= 0) + { + goto error; + } + + if (EVP_PKEY_CTX_set_group_name(ctx, groupName) <= 0) + { + goto error; + } + + if (EVP_PKEY_keygen(ctx, pkey) <= 0) + { + goto error; + } + + EVP_PKEY_CTX_free(ctx); + return 1; + +error: + if (ctx != NULL) + { + EVP_PKEY_CTX_free(ctx); + } + + if (*pkey != NULL) + { + EVP_PKEY_free(*pkey); + *pkey = NULL; + } + + return 0; +#else + (void)oid; + return 0; +#endif +} + +int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( + EVP_PKEY** pkey, + const char* oid, + const uint8_t* qx, int32_t qxLength, + const uint8_t* qy, int32_t qyLength, + const uint8_t* d, int32_t dLength) +{ + if (!pkey || !oid) + { + assert(false); + return 0; + } + + *pkey = NULL; + + ERR_clear_error(); + +#ifdef NEED_OPENSSL_3_0 + if (!API_EXISTS(EVP_PKEY_fromdata) || !API_EXISTS(OSSL_PARAM_BLD_new)) + { + return 0; + } + + // Verify the OID is recognized before doing any work. + int nid = OBJ_txt2nid(oid); + if (!nid) + { + return -1; + } + + // OBJ_nid2sn returns the short name OpenSSL expects for the group name param. + const char* groupName = OBJ_nid2sn(nid); + if (!groupName) + { + return -1; + } + + EVP_PKEY_CTX* ctx = NULL; + uint8_t* pubKeyBuf = NULL; + OSSL_PARAM_BLD* bld = NULL; + OSSL_PARAM* params = NULL; + BIGNUM* dBn = NULL; + + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) + { + goto error; + } + + if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, groupName, 0)) + { + goto error; + } + + if (qx && qy) + { + // Build the uncompressed public key: 0x04 || qx || qy + int32_t coordLen = qxLength > qyLength ? qxLength : qyLength; + int32_t pubKeyLen = 1 + 2 * coordLen; + pubKeyBuf = (uint8_t*)calloc(1, (size_t)pubKeyLen); + + if (pubKeyBuf == NULL) + { + goto error; + } + + pubKeyBuf[0] = 0x04; + memcpy(pubKeyBuf + 1 + (coordLen - qxLength), qx, (size_t)qxLength); + memcpy(pubKeyBuf + 1 + coordLen + (coordLen - qyLength), qy, (size_t)qyLength); + + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, (size_t)pubKeyLen)) + { + goto error; + } + } + + if (d && dLength > 0) + { + dBn = BN_bin2bn(d, dLength, NULL); + if (dBn == NULL) + { + goto error; + } + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, dBn)) + { + goto error; + } + } + + params = OSSL_PARAM_BLD_to_param(bld); + if (params == NULL) + { + goto error; + } + + ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + if (ctx == NULL) + { + goto error; + } + + if (EVP_PKEY_fromdata_init(ctx) != 1) + { + goto error; + } + + { + int selection = (d && dLength > 0) ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; + if (EVP_PKEY_fromdata(ctx, pkey, selection, params) != 1) + { + goto error; + } + } + + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + EVP_PKEY_CTX_free(ctx); + BN_clear_free(dBn); + free(pubKeyBuf); + return 1; + +error: + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + EVP_PKEY_CTX_free(ctx); + BN_clear_free(dBn); + free(pubKeyBuf); + if (*pkey) + { + EVP_PKEY_free(*pkey); + *pkey = NULL; + } + return 0; +#else + (void)oid; + (void)qx; (void)qxLength; + (void)qy; (void)qyLength; + (void)d; (void)dLength; + return 0; +#endif +} + +EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( + ECCurveType curveType, + const uint8_t* qx, int32_t qxLength, + const uint8_t* qy, int32_t qyLength, + const uint8_t* d, int32_t dLength, + const uint8_t* p, int32_t pLength, + const uint8_t* a, int32_t aLength, + const uint8_t* b, int32_t bLength, + const uint8_t* gx, int32_t gxLength, + const uint8_t* gy, int32_t gyLength, + const uint8_t* order, int32_t orderLength, + const uint8_t* cofactor, int32_t cofactorLength, + const uint8_t* seed, int32_t seedLength) +{ + if (!p || !a || !b || !gx || !gy || !order || !cofactor) + { + assert(false); + return NULL; + } + + ERR_clear_error(); + +#ifdef NEED_OPENSSL_3_0 + if (!API_EXISTS(EVP_PKEY_fromdata) || !API_EXISTS(OSSL_PARAM_BLD_new)) + { + return NULL; + } + + EVP_PKEY* pkey = NULL; + EVP_PKEY_CTX* ctx = NULL; + OSSL_PARAM_BLD* bld = NULL; + OSSL_PARAM* params = NULL; + uint8_t* generatorBuf = NULL; + uint8_t* pubKeyBuf = NULL; + BIGNUM* pBn = NULL; + BIGNUM* aBn = NULL; + BIGNUM* bBn = NULL; + BIGNUM* orderBn = NULL; + BIGNUM* cofactorBn = NULL; + BIGNUM* dBn = NULL; + + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) + { + goto error; + } + + const char* fieldType = (curveType == Characteristic2) + ? "characteristic-two-field" + : "prime-field"; + + if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, 0)) + { + goto error; + } + + if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_EC_ENCODING, "explicit", 0)) + { + goto error; + } + + pBn = BN_bin2bn(p, pLength, NULL); + aBn = BN_bin2bn(a, aLength, NULL); + bBn = BN_bin2bn(b, bLength, NULL); + + if (!pBn || !aBn || !bBn) + { + goto error; + } + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_P, pBn) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_A, aBn) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_B, bBn)) + { + goto error; + } + + // Generator as uncompressed point: 0x04 || gx || gy + { + int32_t coordLen = gxLength > gyLength ? gxLength : gyLength; + int32_t genLen = 1 + 2 * coordLen; + generatorBuf = (uint8_t*)calloc(1, (size_t)genLen); + if (generatorBuf == NULL) + { + goto error; + } + + generatorBuf[0] = 0x04; + memcpy(generatorBuf + 1 + (coordLen - gxLength), gx, (size_t)gxLength); + memcpy(generatorBuf + 1 + coordLen + (coordLen - gyLength), gy, (size_t)gyLength); + + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_GENERATOR, generatorBuf, (size_t)genLen)) + { + goto error; + } + } + + orderBn = BN_bin2bn(order, orderLength, NULL); + cofactorBn = BN_bin2bn(cofactor, cofactorLength, NULL); + + if (!orderBn || !cofactorBn) + { + goto error; + } + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_ORDER, orderBn) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_COFACTOR, cofactorBn)) + { + goto error; + } + + if (seed && seedLength > 0) + { + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_SEED, seed, (size_t)seedLength)) + { + goto error; + } + } + + if (qx && qy) + { + int32_t coordLen = qxLength > qyLength ? qxLength : qyLength; + int32_t pubKeyLen = 1 + 2 * coordLen; + pubKeyBuf = (uint8_t*)calloc(1, (size_t)pubKeyLen); + if (pubKeyBuf == NULL) + { + goto error; + } + + pubKeyBuf[0] = 0x04; + memcpy(pubKeyBuf + 1 + (coordLen - qxLength), qx, (size_t)qxLength); + memcpy(pubKeyBuf + 1 + coordLen + (coordLen - qyLength), qy, (size_t)qyLength); + + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, (size_t)pubKeyLen)) + { + goto error; + } + } + + if (d && dLength > 0) + { + dBn = BN_bin2bn(d, dLength, NULL); + if (dBn == NULL) + { + goto error; + } + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, dBn)) + { + goto error; + } + } + + params = OSSL_PARAM_BLD_to_param(bld); + if (params == NULL) + { + goto error; + } + + ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + if (ctx == NULL) + { + goto error; + } + + if (EVP_PKEY_fromdata_init(ctx) != 1) + { + goto error; + } + + { + int selection = (d && dLength > 0) ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; + if (EVP_PKEY_fromdata(ctx, &pkey, selection, params) != 1) + { + goto error; + } + } + + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + EVP_PKEY_CTX_free(ctx); + free(generatorBuf); + free(pubKeyBuf); + BN_free(pBn); + BN_free(aBn); + BN_free(bBn); + BN_free(orderBn); + BN_free(cofactorBn); + BN_clear_free(dBn); + return pkey; + +error: + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + EVP_PKEY_CTX_free(ctx); + free(generatorBuf); + free(pubKeyBuf); + BN_free(pBn); + BN_free(aBn); + BN_free(bBn); + BN_free(orderBn); + BN_free(cofactorBn); + BN_clear_free(dBn); + if (pkey) EVP_PKEY_free(pkey); + return NULL; +#else + (void)curveType; + (void)qx; (void)qxLength; (void)qy; (void)qyLength; + (void)d; (void)dLength; + (void)p; (void)pLength; (void)a; (void)aLength; (void)b; (void)bLength; + (void)gx; (void)gxLength; (void)gy; (void)gyLength; + (void)order; (void)orderLength; (void)cofactor; (void)cofactorLength; + (void)seed; (void)seedLength; + return NULL; +#endif +} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h index ba16b5450c80c8..d0871a1b1cb759 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h @@ -89,6 +89,44 @@ PALEXPORT EC_KEY* CryptoNative_EcKeyCreateByExplicitParameters( uint8_t* cofactor, int32_t hLength, uint8_t* seed, int32_t sLength); +/* +Generates a new EC key pair for a named curve using EVP_PKEY APIs. +Returns 1 upon success, -1 if oid was not found, otherwise 0. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyGenerateByEcKeyOid( + EVP_PKEY** pkey, + const char* oid); + +/* +Creates a new EVP_PKEY for a named EC curve using the provided key parameters. +qx/qy are the public key coordinates, d is the optional private key. +Returns 1 upon success, -1 if oid was not found, otherwise 0. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( + EVP_PKEY** pkey, + const char* oid, + const uint8_t* qx, int32_t qxLength, + const uint8_t* qy, int32_t qyLength, + const uint8_t* d, int32_t dLength); + +/* +Creates a new EVP_PKEY for an EC key with explicit curve parameters. +Returns the new EVP_PKEY instance, or NULL on failure. +*/ +PALEXPORT EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( + ECCurveType curveType, + const uint8_t* qx, int32_t qxLength, + const uint8_t* qy, int32_t qyLength, + const uint8_t* d, int32_t dLength, + const uint8_t* p, int32_t pLength, + const uint8_t* a, int32_t aLength, + const uint8_t* b, int32_t bLength, + const uint8_t* gx, int32_t gxLength, + const uint8_t* gy, int32_t gyLength, + const uint8_t* order, int32_t orderLength, + const uint8_t* cofactor, int32_t cofactorLength, + const uint8_t* seed, int32_t seedLength); + /* Returns the ECC curve parameters of the given EVP_PKEY. */ From 43775ae2210fde3c4cb108857712b44d81739365 Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Mon, 20 Apr 2026 23:08:09 -0700 Subject: [PATCH 02/15] fix pub key computation and key size --- .../Interop.EcDsa.ImportExport.cs | 29 +- .../ECDiffieHellmanOpenSslPublicKey.cs | 14 +- .../Cryptography/ECOpenSsl.ImportExport.cs | 45 +- .../System/Security/Cryptography/ECOpenSsl.cs | 6 +- .../entrypoints.c | 2 + .../opensslshim.h | 6 + .../pal_ecc_import_export.c | 448 ++++++++++++++++-- .../pal_ecc_import_export.h | 12 + 8 files changed, 507 insertions(+), 55 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs index ea86f87d0fd245..0b6813df7e0faa 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs @@ -217,11 +217,12 @@ internal static bool EvpPKeyHasCurveName(SafeEvpPKeyHandle pkey) int rc = CryptoNative_EvpPKeyGetEcGroupNid(pkey, out int nidCurveName); if (rc == 1) { - // Key is invalid or doesn't have a curve - return (nidCurveName != Interop.Crypto.NID_undef); + return nidCurveName != Interop.Crypto.NID_undef; } - throw Interop.Crypto.CreateOpenSslCryptographicException(); + // rc == 0 means the group name could not be retrieved + // (e.g., explicit curve or OpenSSL < 3.0). Treat as no curve name. + return false; } /// @@ -235,7 +236,25 @@ internal static bool EvpPKeyHasCurveName(SafeEvpPKeyHandle pkey) return nidCurveName != Interop.Crypto.NID_undef ? CurveNidToOidValue(nidCurveName) : null; } - throw Interop.Crypto.CreateOpenSslCryptographicException(); + // rc == 0 means the group name could not be retrieved + // (e.g., explicit curve or OpenSSL < 3.0). + return null; + } + + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_EvpPKeyEcHasExplicitEncoding(SafeEvpPKeyHandle pkey); + + internal static bool EvpPKeyEcHasExplicitEncoding(SafeEvpPKeyHandle pkey) + { + return CryptoNative_EvpPKeyEcHasExplicitEncoding(pkey) == 1; + } + + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_EvpPKeyGetEcFieldDegree(SafeEvpPKeyHandle pkey); + + internal static int EvpPKeyGetEcFieldDegree(SafeEvpPKeyHandle pkey) + { + return CryptoNative_EvpPKeyGetEcFieldDegree(pkey); } [LibraryImport(Libraries.CryptoNative)] @@ -282,6 +301,7 @@ internal static ECParameters GetECKeyParameters( } else if (rc != 1) { + System.Console.WriteLine($"error getting params: {rc}"); throw Interop.Crypto.CreateOpenSslCryptographicException(); } @@ -329,6 +349,7 @@ internal static ECParameters EvpPKeyGetEcKeyParameters( } else if (rc != 1) { + Console.WriteLine($"error getting params: {rc}"); throw Interop.Crypto.CreateOpenSslCryptographicException(); } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs index 16aba3f532d63b..7008abec521ed5 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs @@ -52,7 +52,19 @@ internal int KeySize get { ThrowIfDisposed(); - return Interop.Crypto.EvpPKeyBits(_key); + int keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(_key); + if (keySize != 0) + return keySize; + + // For EC_KEY-backed handles, get size through EC_KEY path. + using (SafeEcKeyHandle? ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + { + if (ecKey is not null && !ecKey.IsInvalid) + return Interop.Crypto.EcKeyGetSize(ecKey); + } + + // TODO message + throw new CryptographicException(); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs index 625b532a023015..15c320f0200a24 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs @@ -181,14 +181,18 @@ public static ECParameters ExportParameters(SafeEvpPKeyHandle pkey, bool include { CheckInvalidKey(pkey); - using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(pkey)) + try + { + return ExportECParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); + } + catch (CryptographicException) { - if (ecKey == null || ecKey.IsInvalid) - { - // This may happen when EVP_PKEY was created by provider and getting EC_KEY is not possible. - return ExportECParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); - } + // EVP_PKEY param extraction may fail for EC_KEY-backed keys. + // Fall back to EC_KEY export path. + } + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(pkey)) + { return ECOpenSsl.ExportParameters(ecKey, includePrivateParameters); } } @@ -197,14 +201,18 @@ public static ECParameters ExportExplicitParameters(SafeEvpPKeyHandle pkey, bool { CheckInvalidKey(pkey); - using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(pkey)) + try + { + return ExportExplicitCurveParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); + } + catch (CryptographicException) { - if (ecKey == null || ecKey.IsInvalid) - { - // This may happen when EVP_PKEY was created by provider and getting EC_KEY is not possible. - return ExportExplicitCurveParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); - } + // EVP_PKEY param extraction may fail for EC_KEY-backed keys. + // Fall back to EC_KEY export path. + } + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(pkey)) + { return ECOpenSsl.ExportExplicitParameters(ecKey, includePrivateParameters); } } @@ -216,15 +224,20 @@ public static ECParameters ExportExplicitParameters(SafeEvpPKeyHandle pkey, bool /// private static ECParameters ExportECParametersFromEvpPKeyUsingParams(SafeEvpPKeyHandle pkey, bool includePrivateParameters) { - string? curveName = Interop.Crypto.EvpPKeyGetCurveName(pkey); - if (curveName == null) + // Check encoding first — explicit-encoding keys must be exported with + // explicit curve parameters even if OpenSSL can match a named curve. + if (Interop.Crypto.EvpPKeyEcHasExplicitEncoding(pkey)) { return ExportExplicitCurveParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); } - else + + string? curveName = Interop.Crypto.EvpPKeyGetCurveName(pkey); + if (curveName is null) { - return ExportNamedCurveParametersFromEvpPKeyUsingParams(pkey, curveName, includePrivateParameters); + return ExportExplicitCurveParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); } + + return ExportNamedCurveParametersFromEvpPKeyUsingParams(pkey, curveName, includePrivateParameters); } private static ECParameters ExportNamedCurveParametersFromEvpPKeyUsingParams(SafeEvpPKeyHandle pkey, string curveName, bool includePrivateParameters) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index ef2b7036bd3bac..f03f27c746cefc 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -148,7 +148,7 @@ internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) if (pkey is not null) { - keySize = Interop.Crypto.EvpPKeyBits(pkey); + keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(pkey); return pkey; } } @@ -174,7 +174,7 @@ internal static SafeEvpPKeyHandle ImportECKey(ECParameters parameters, out int k if (pkey is not null) { - keySize = Interop.Crypto.EvpPKeyBits(pkey); + keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(pkey); return pkey; } } @@ -198,7 +198,7 @@ internal static SafeEvpPKeyHandle ImportECKey(ECParameters parameters, out int k if (pkey is not null) { - keySize = Interop.Crypto.EvpPKeyBits(pkey); + keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(pkey); return pkey; } } diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index 7b3f323b5d400b..adc9b080826f62 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -192,6 +192,8 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpPKeyCreateByEcKeyParameters) DllImportEntry(CryptoNative_EvpPKeyCreateByEcExplicitParameters) DllImportEntry(CryptoNative_EvpPKeyGenerateByEcKeyOid) + DllImportEntry(CryptoNative_EvpPKeyEcHasExplicitEncoding) + DllImportEntry(CryptoNative_EvpPKeyGetEcFieldDegree) DllImportEntry(CryptoNative_EvpRC2Cbc) DllImportEntry(CryptoNative_EvpRC2Ecb) DllImportEntry(CryptoNative_EvpSha1) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index c5f37871d5e764..df9e1fc2bdaa6f 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -413,6 +413,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EC_GROUP_method_of) \ REQUIRED_FUNCTION(EC_GROUP_new) \ LIGHTUP_FUNCTION(EC_GROUP_new_by_curve_name) \ + REQUIRED_FUNCTION(EC_GROUP_new_curve_GFp) \ REQUIRED_FUNCTION(EC_GROUP_set_curve_GFp) \ REQUIRED_FUNCTION(EC_GROUP_set_generator) \ REQUIRED_FUNCTION(EC_GROUP_set_seed) \ @@ -434,6 +435,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EC_POINT_get_affine_coordinates_GFp) \ REQUIRED_FUNCTION(EC_POINT_mul) \ REQUIRED_FUNCTION(EC_POINT_new) \ + REQUIRED_FUNCTION(EC_POINT_point2oct) \ REQUIRED_FUNCTION(EC_POINT_set_affine_coordinates_GFp) \ LIGHTUP_FUNCTION(EC_POINT_oct2point) \ LIGHTUP_FUNCTION(ENGINE_by_id) \ @@ -862,6 +864,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(X509_VERIFY_PARAM_set_time) \ LIGHTUP_FUNCTION(EC_GF2m_simple_method) \ LIGHTUP_FUNCTION(EC_GROUP_get_curve_GF2m) \ + LIGHTUP_FUNCTION(EC_GROUP_new_curve_GF2m) \ LIGHTUP_FUNCTION(EC_GROUP_set_curve_GF2m) \ LIGHTUP_FUNCTION(EC_POINT_get_affine_coordinates_GF2m) \ LIGHTUP_FUNCTION(EC_POINT_set_affine_coordinates_GF2m) \ @@ -980,6 +983,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EC_GROUP_method_of EC_GROUP_method_of_ptr #define EC_GROUP_new EC_GROUP_new_ptr #define EC_GROUP_new_by_curve_name EC_GROUP_new_by_curve_name_ptr +#define EC_GROUP_new_curve_GFp EC_GROUP_new_curve_GFp_ptr #define EC_GROUP_set_curve_GFp EC_GROUP_set_curve_GFp_ptr #define EC_GROUP_set_generator EC_GROUP_set_generator_ptr #define EC_GROUP_set_seed EC_GROUP_set_seed_ptr @@ -1001,6 +1005,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EC_POINT_get_affine_coordinates_GFp EC_POINT_get_affine_coordinates_GFp_ptr #define EC_POINT_mul EC_POINT_mul_ptr #define EC_POINT_new EC_POINT_new_ptr +#define EC_POINT_point2oct EC_POINT_point2oct_ptr #define EC_POINT_set_affine_coordinates_GFp EC_POINT_set_affine_coordinates_GFp_ptr #define EC_POINT_oct2point EC_POINT_oct2point_ptr #define ENGINE_by_id ENGINE_by_id_ptr @@ -1434,6 +1439,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define X509_VERIFY_PARAM_set_time X509_VERIFY_PARAM_set_time_ptr #define EC_GF2m_simple_method EC_GF2m_simple_method_ptr #define EC_GROUP_get_curve_GF2m EC_GROUP_get_curve_GF2m_ptr +#define EC_GROUP_new_curve_GF2m EC_GROUP_new_curve_GF2m_ptr #define EC_GROUP_set_curve_GF2m EC_GROUP_set_curve_GF2m_ptr #define EC_POINT_get_affine_coordinates_GF2m EC_POINT_get_affine_coordinates_GF2m_ptr #define EC_POINT_set_affine_coordinates_GF2m EC_POINT_set_affine_coordinates_GF2m_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index 390c77dae1dc3f..200745e6293d51 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -95,17 +95,23 @@ int32_t CryptoNative_GetECKeyParameters( ECCurveType curveType = EcKeyGetCurveType(key); const EC_POINT* Q = EC_KEY_get0_public_key(key); const EC_GROUP* group = EC_KEY_get0_group(key); - if (curveType == Unspecified || !Q || !group) + if (curveType == Unspecified || !Q || !group) { + rc = 5; goto error; + } // Extract qx and qy xBn = BN_new(); yBn = BN_new(); - if (!xBn || !yBn) + if (!xBn || !yBn) { + rc = 6; goto error; + } - if (!EcPointGetAffineCoordinates(group, curveType, Q, xBn, yBn)) + if (!EcPointGetAffineCoordinates(group, curveType, Q, xBn, yBn)) { + rc = 7; goto error; + } // Success; assign variables *qx = xBn; *cbQx = BN_num_bytes(xBn); @@ -449,7 +455,11 @@ int32_t CryptoNative_EvpPKeyGetEcGroupNid(const EVP_PKEY *pkey, int32_t* nidName char curveName[80] = {0}; if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curveName, sizeof(curveName), NULL)) + { + // Clear error queue - this is expected for explicit curve keys. + ERR_clear_error(); return 0; + } *nidName = OBJ_txt2nid(curveName); return 1; @@ -458,6 +468,63 @@ int32_t CryptoNative_EvpPKeyGetEcGroupNid(const EVP_PKEY *pkey, int32_t* nidName #endif } +int32_t CryptoNative_EvpPKeyEcHasExplicitEncoding(const EVP_PKEY* pkey) +{ + if (!pkey || EVP_PKEY_get_base_id(pkey) != EVP_PKEY_EC) + return 0; + +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (!API_EXISTS(EVP_PKEY_get_utf8_string_param)) + { + return 0; + } +#endif + +#ifdef NEED_OPENSSL_3_0 + char encoding[32] = {0}; + if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_EC_ENCODING, encoding, sizeof(encoding), NULL)) + return 0; + + return (strcmp(encoding, "explicit") == 0) ? 1 : 0; +#else + return 0; +#endif +} + +int32_t CryptoNative_EvpPKeyGetEcFieldDegree(const EVP_PKEY* pkey) +{ + if (!pkey || EVP_PKEY_get_base_id(pkey) != EVP_PKEY_EC) + return 0; + +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (!API_EXISTS(EVP_PKEY_get_bn_param)) + { + return 0; + } +#endif + +#ifdef NEED_OPENSSL_3_0 + char fieldType[32] = {0}; + if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, sizeof(fieldType), NULL)) + return 0; + + BIGNUM* p = NULL; + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_P, &p) || !p) + return 0; + + // For GF(2^m): p is the irreducible polynomial, degree = BN_num_bits(p) - 1. + // For GF(p): degree = BN_num_bits(p). + int degree = BN_num_bits(p); + if (strcmp(fieldType, "characteristic-two-field") == 0) + degree--; + + BN_free(p); + return degree; +#else + return 0; +#endif +} + int32_t CryptoNative_EvpPKeyGetEcKeyParameters( const EVP_PKEY* pkey, int32_t includePrivate, @@ -473,7 +540,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( assert(cbD != NULL); #ifdef FEATURE_DISTRO_AGNOSTIC_SSL - if (!API_EXISTS(EVP_PKEY_get_bn_param)) + if (!API_EXISTS(EVP_PKEY_get_octet_string_param)) { *cbQx = *cbQy = 0; *qx = *qy = 0; @@ -487,6 +554,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( BIGNUM *xBn = NULL; BIGNUM *yBn = NULL; BIGNUM *dBn = NULL; + uint8_t* pubKeyBuf = NULL; #ifdef NEED_OPENSSL_3_0 // Ensure we have an EC key @@ -495,20 +563,12 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( ERR_clear_error(); - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &xBn)) - goto error; - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &yBn)) - goto error; - - *qx = xBn; - *cbQx = BN_num_bytes(xBn); - *qy = yBn; - *cbQy = BN_num_bytes(yBn); - if (includePrivate) { - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &dBn)) + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &dBn)) { + rc = 12; goto error; + } *d = dBn; *cbD = BN_num_bytes(dBn); @@ -519,7 +579,138 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( *cbD = 0; } - // success + // Get the public key as an encoded point (may be compressed or uncompressed). + // We use OSSL_PKEY_PARAM_PUB_KEY instead of OSSL_PKEY_PARAM_EC_PUB_X/Y + // because the individual X/Y components may not be materialized yet. + size_t pubKeyLen = 0; + if (!EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, NULL, 0, &pubKeyLen)) + { + rc = 5; + goto error; + } + + pubKeyBuf = (uint8_t*)calloc(pubKeyLen, 1); + if (pubKeyBuf == NULL) + { + rc = 7; + goto error; + } + + if (!EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, pubKeyLen, &pubKeyLen)) + { + rc = 8; + goto error; + } + + { + // Decode the encoded point (compressed or uncompressed) to extract X and Y. + // Build an EC_GROUP from the key's parameters to perform the decoding. + EC_GROUP* group = NULL; + + // Try named curve first. + char curveName[80] = {0}; + if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curveName, sizeof(curveName), NULL)) + { + int nid = OBJ_txt2nid(curveName); + if (nid != NID_undef && API_EXISTS(EC_GROUP_new_by_curve_name)) + { + group = EC_GROUP_new_by_curve_name(nid); + } + } + + if (group == NULL) + { + // Explicit curve — build EC_GROUP from the key's field params. + BIGNUM* ecP = NULL; + BIGNUM* ecA = NULL; + BIGNUM* ecB = NULL; + + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_P, &ecP); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_A, &ecA); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_B, &ecB); + + if (ecP == NULL || ecA == NULL || ecB == NULL) + { + BN_free(ecP); + BN_free(ecA); + BN_free(ecB); + rc = 6; + goto error; + } + + char fieldType[64] = {0}; + int isChar2 = 0; + + if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, sizeof(fieldType), NULL)) + { + isChar2 = (strcmp(fieldType, "characteristic-two-field") == 0); + } + +#if HAVE_OPENSSL_EC2M + if (isChar2 && API_EXISTS(EC_GROUP_new_curve_GF2m)) + { + group = EC_GROUP_new_curve_GF2m(ecP, ecA, ecB, NULL); + } + else +#endif + { + group = EC_GROUP_new_curve_GFp(ecP, ecA, ecB, NULL); + } + + BN_free(ecP); + BN_free(ecA); + BN_free(ecB); + + if (group == NULL) + { + rc = 6; + goto error; + } + } + + EC_POINT* point = EC_POINT_new(group); + if (point == NULL || + !API_EXISTS(EC_POINT_oct2point) || + !EC_POINT_oct2point(group, point, pubKeyBuf, pubKeyLen, NULL)) + { + EC_POINT_free(point); + EC_GROUP_free(group); + rc = 9; + goto error; + } + + xBn = BN_new(); + yBn = BN_new(); + + if (xBn == NULL || yBn == NULL) + { + EC_POINT_free(point); + EC_GROUP_free(group); + rc = 10; + goto error; + } + + const EC_METHOD* groupMethod = EC_GROUP_method_of(group); + ECCurveType groupCurveType = MethodToCurveType(groupMethod); + + if (!EcPointGetAffineCoordinates(group, groupCurveType, point, xBn, yBn)) + { + EC_POINT_free(point); + EC_GROUP_free(group); + rc = 11; + goto error; + } + + EC_POINT_free(point); + EC_GROUP_free(group); + } + + *qx = xBn; + *cbQx = BN_num_bytes(xBn); + *qy = yBn; + *cbQy = BN_num_bytes(yBn); + + free(pubKeyBuf); return 1; error: @@ -533,6 +724,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( if (cbD) *cbD = 0; if (xBn) BN_free(xBn); if (yBn) BN_free(yBn); + free(pubKeyBuf); return rc; } @@ -801,7 +993,26 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( goto error; if (!CryptoNative_EvpPKeyGetEcGroupNid(pkey, &curveTypeNID) || !curveTypeNID) - goto error; + { + // For explicit curves, the group name may not be available. + // Get the field type directly instead. + char fieldTypeStr[32] = {0}; + if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldTypeStr, sizeof(fieldTypeStr), NULL)) + goto error; + + fieldTypeNID = OBJ_txt2nid(fieldTypeStr); + if (fieldTypeNID == NID_undef) + goto error; + } + else + { + // Named curve: create group to get field type. + group = EC_GROUP_new_by_curve_name(curveTypeNID); + if (!group) + goto error; + + fieldTypeNID = EC_GROUP_get_field_type(group); + } // Extract p, a, b if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_P, &pBn)) @@ -813,19 +1024,20 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_B, &bBn)) goto error; - // curveTypeNID will be always NID_X9_62_characteristic_two_field or NID_X9_62_prime_field - group = EC_GROUP_new_by_curve_name(curveTypeNID); - - // In some cases EVP_PKEY_get_field_type can return NID_undef - // and some providers seem to be ignoring OSSL_PKEY_PARAM_EC_FIELD_TYPE. - // This is specifically true for tpm2 provider. - // We can reliably get the field type from the EC_GROUP. - fieldTypeNID = EC_GROUP_get_field_type(group); - *curveType = NIDToCurveType(fieldTypeNID); if (*curveType == Unspecified) goto error; + // For explicit curves where group was not created from a curve name, + // build it from the field parameters to decode the generator point. + if (!group) + { + if (fieldTypeNID == NID_X9_62_characteristic_two_field) + group = EC_GROUP_new_curve_GF2m(pBn, aBn, bBn, NULL); + else + group = EC_GROUP_new_curve_GFp(pBn, aBn, bBn, NULL); + } + if (!group) goto error; @@ -1090,6 +1302,60 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( goto error; } } + else if (d && dLength > 0) + { + // No public key provided, derive Q = d * G using EC_GROUP/EC_POINT (not deprecated). + EC_GROUP* group = EC_GROUP_new_by_curve_name(nid); + if (group == NULL) + { + goto error; + } + + EC_POINT* pubPoint = EC_POINT_new(group); + BIGNUM* dBnTmp = BN_bin2bn(d, dLength, NULL); + + if (pubPoint == NULL || dBnTmp == NULL || + !EC_POINT_mul(group, pubPoint, dBnTmp, NULL, NULL, NULL)) + { + BN_clear_free(dBnTmp); + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + goto error; + } + + BN_clear_free(dBnTmp); + + size_t pubKeyLen = EC_POINT_point2oct(group, pubPoint, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); + if (pubKeyLen == 0) + { + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + goto error; + } + + pubKeyBuf = (uint8_t*)calloc(1, pubKeyLen); + if (pubKeyBuf == NULL) + { + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + goto error; + } + + if (EC_POINT_point2oct(group, pubPoint, POINT_CONVERSION_UNCOMPRESSED, pubKeyBuf, pubKeyLen, NULL) != pubKeyLen) + { + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + goto error; + } + + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, pubKeyLen)) + { + goto error; + } + } if (d && dLength > 0) { @@ -1277,6 +1543,20 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( } } + if (d && dLength > 0) + { + dBn = BN_bin2bn(d, dLength, NULL); + if (dBn == NULL) + { + goto error; + } + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, dBn)) + { + goto error; + } + } + if (qx && qy) { int32_t coordLen = qxLength > qyLength ? qxLength : qyLength; @@ -1296,16 +1576,122 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( goto error; } } - - if (d && dLength > 0) + else if (d && dLength > 0) { - dBn = BN_bin2bn(d, dLength, NULL); - if (dBn == NULL) + // No public key provided, derive Q = d * G from the explicit curve parameters. + const EC_METHOD* curveMethod = CurveTypeToMethod(curveType); + if (!curveMethod) { goto error; } - if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, dBn)) + EC_GROUP* group = EC_GROUP_new(curveMethod); + if (group == NULL) + { + goto error; + } + +#if HAVE_OPENSSL_EC2M + if (API_EXISTS(EC_GROUP_set_curve_GF2m) && (curveType == Characteristic2)) + { + if (!EC_GROUP_set_curve_GF2m(group, pBn, aBn, bBn, NULL)) + { + EC_GROUP_free(group); + goto error; + } + } + else +#endif + { + if (!EC_GROUP_set_curve_GFp(group, pBn, aBn, bBn, NULL)) + { + EC_GROUP_free(group); + goto error; + } + } + + // Set the generator + EC_POINT* G = EC_POINT_new(group); + BIGNUM* gxBn = BN_bin2bn(gx, gxLength, NULL); + BIGNUM* gyBn = BN_bin2bn(gy, gyLength, NULL); + + if (G == NULL || gxBn == NULL || gyBn == NULL) + { + BN_free(gxBn); + BN_free(gyBn); + EC_POINT_free(G); + EC_GROUP_free(group); + goto error; + } + +#if HAVE_OPENSSL_EC2M + if (API_EXISTS(EC_POINT_set_affine_coordinates_GF2m) && (curveType == Characteristic2)) + { + if (!EC_POINT_set_affine_coordinates_GF2m(group, G, gxBn, gyBn, NULL)) + { + BN_free(gxBn); + BN_free(gyBn); + EC_POINT_free(G); + EC_GROUP_free(group); + goto error; + } + } + else +#endif + { + if (!EC_POINT_set_affine_coordinates_GFp(group, G, gxBn, gyBn, NULL)) + { + BN_free(gxBn); + BN_free(gyBn); + EC_POINT_free(G); + EC_GROUP_free(group); + goto error; + } + } + + BN_free(gxBn); + BN_free(gyBn); + + EC_GROUP_set_generator(group, G, orderBn, cofactorBn); + EC_POINT_free(G); + + // Derive Q = d * G + EC_POINT* pubPoint = EC_POINT_new(group); + if (pubPoint == NULL || + !EC_POINT_mul(group, pubPoint, dBn, NULL, NULL, NULL)) + { + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + goto error; + } + + size_t pubKeyLen = EC_POINT_point2oct(group, pubPoint, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); + if (pubKeyLen == 0) + { + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + goto error; + } + + pubKeyBuf = (uint8_t*)calloc(1, pubKeyLen); + if (pubKeyBuf == NULL) + { + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + goto error; + } + + if (EC_POINT_point2oct(group, pubPoint, POINT_CONVERSION_UNCOMPRESSED, pubKeyBuf, pubKeyLen, NULL) != pubKeyLen) + { + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + goto error; + } + + EC_POINT_free(pubPoint); + EC_GROUP_free(group); + + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, pubKeyLen)) { goto error; } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h index d0871a1b1cb759..45341a9de46ee7 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h @@ -97,6 +97,18 @@ PALEXPORT int32_t CryptoNative_EvpPKeyGenerateByEcKeyOid( EVP_PKEY** pkey, const char* oid); +/* +Returns 1 if the EVP_PKEY EC key uses explicit encoding, 0 otherwise. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyEcHasExplicitEncoding(const EVP_PKEY* pkey); + +/* +Returns the field degree (number of bits) of the EC group for the given EVP_PKEY. +For prime fields this is BN_num_bits(p), for binary fields it is BN_num_bits(polynomial) - 1. +Returns 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyGetEcFieldDegree(const EVP_PKEY* pkey); + /* Creates a new EVP_PKEY for a named EC curve using the provided key parameters. qx/qy are the public key coordinates, d is the optional private key. From 6afa0f73d86da54b27a014a70c5e449e2b192cce Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Wed, 22 Apr 2026 14:39:07 -0700 Subject: [PATCH 03/15] missed case with GenerateECKey creating EC_KEY --- .../ECDiffieHellmanOpenSsl.Derive.cs | 4 +- .../ECDiffieHellmanOpenSslPublicKey.cs | 2 + .../System/Security/Cryptography/ECOpenSsl.cs | 25 +++++++++++ .../opensslshim.h | 2 + .../pal_ecc_import_export.c | 44 +++++++++++++++++-- 5 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index d58fdde2e761d0..55aacd66d56788 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -88,7 +88,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa Debug.Assert(otherPartyPublicKey != null); Debug.Assert(_key is not null); // Callers should validate prior. - bool thisIsNamed = Interop.Crypto.EvpPKeyHasCurveName(_key.Value); + bool thisIsNamed = !Interop.Crypto.EvpPKeyEcHasExplicitEncoding(_key.Value); ECDiffieHellmanOpenSslPublicKey? otherKey = otherPartyPublicKey as ECDiffieHellmanOpenSslPublicKey; bool disposeOtherKey = false; @@ -105,7 +105,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa otherKey = new ECDiffieHellmanOpenSslPublicKey(otherParameters); } - bool otherIsNamed = otherKey.HasCurveName; + bool otherIsNamed = !otherKey.HasExplicitEncoding; // We need to always duplicate handle in case this operation is done by multiple threads and one of them disposes the handle SafeEvpPKeyHandle? ourKey = _key.Value; diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs index 7008abec521ed5..f66b497b4f384f 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs @@ -47,6 +47,8 @@ public override ECParameters ExportParameters() => internal bool HasCurveName => Interop.Crypto.EvpPKeyHasCurveName(GetKey()); + internal bool HasExplicitEncoding => Interop.Crypto.EvpPKeyEcHasExplicitEncoding(GetKey()); + internal int KeySize { get diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index f03f27c746cefc..28d8911d46bb5e 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -152,6 +152,31 @@ internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) return pkey; } } + else if (curve.IsPrime || curve.IsCharacteristic2) + { + byte[] pField = curve.IsPrime ? curve.Prime! : curve.Polynomial!; + + // Pass null Q and null D to trigger key generation instead of import. + SafeEvpPKeyHandle? pkey = Interop.Crypto.EvpPKeyCreateByEcExplicitParameters( + curve.CurveType, + null, 0, + null, 0, + null, 0, + pField, pField.Length, + curve.A!, curve.A!.Length, + curve.B!, curve.B!.Length, + curve.G.X!, curve.G.X!.Length, + curve.G.Y!, curve.G.Y!.Length, + curve.Order!, curve.Order!.Length, + curve.Cofactor!, curve.Cofactor!.Length, + curve.Seed, curve.Seed is null ? 0 : curve.Seed.Length); + + if (pkey is not null) + { + keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(pkey); + return pkey; + } + } // Fallback to legacy EC_KEY path (explicit curves or OpenSSL < 3.0) return ImportECKeyCore(new ECOpenSsl(curve), out keySize); diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index df9e1fc2bdaa6f..f96b68c690a919 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -565,6 +565,7 @@ extern bool g_libSslUses32BitTime; LIGHTUP_FUNCTION(EVP_PKEY_get0_RSA) \ LIGHTUP_FUNCTION(EVP_PKEY_get0_type_name) \ REQUIRED_FUNCTION(EVP_PKEY_get1_DSA) \ + LIGHTUP_FUNCTION(EVP_PKEY_generate) \ REQUIRED_FUNCTION(EVP_PKEY_get1_EC_KEY) \ LIGHTUP_FUNCTION(EVP_PKEY_is_a) \ REQUIRED_FUNCTION(EVP_PKEY_keygen) \ @@ -1133,6 +1134,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_get0_RSA EVP_PKEY_get0_RSA_ptr #define EVP_PKEY_get0_type_name EVP_PKEY_get0_type_name_ptr #define EVP_PKEY_get1_DSA EVP_PKEY_get1_DSA_ptr +#define EVP_PKEY_generate EVP_PKEY_generate_ptr #define EVP_PKEY_get1_EC_KEY EVP_PKEY_get1_EC_KEY_ptr #define EVP_PKEY_is_a EVP_PKEY_is_a_ptr #define EVP_PKEY_keygen EVP_PKEY_keygen_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index 200745e6293d51..a42b23b294f272 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -1447,7 +1447,8 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( ERR_clear_error(); #ifdef NEED_OPENSSL_3_0 - if (!API_EXISTS(EVP_PKEY_fromdata) || !API_EXISTS(OSSL_PARAM_BLD_new)) + if (!API_EXISTS(EVP_PKEY_fromdata) || !API_EXISTS(OSSL_PARAM_BLD_new) || + !API_EXISTS(EVP_PKEY_generate) || !API_EXISTS(EVP_PKEY_CTX_new_from_pkey)) { return NULL; } @@ -1709,12 +1710,47 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( goto error; } - if (EVP_PKEY_fromdata_init(ctx) != 1) + if ((!qx || !qy) && (!d || dLength <= 0)) { - goto error; - } + // No key material — generate a new key from the domain parameters. + EVP_PKEY* templateKey = NULL; + + if (EVP_PKEY_fromdata_init(ctx) != 1) + { + goto error; + } + if (EVP_PKEY_fromdata(ctx, &templateKey, EVP_PKEY_KEY_PARAMETERS, params) != 1) + { + goto error; + } + + EVP_PKEY_CTX_free(ctx); + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, templateKey, NULL); + EVP_PKEY_free(templateKey); + + if (ctx == NULL) + { + goto error; + } + + if (EVP_PKEY_keygen_init(ctx) != 1) + { + goto error; + } + + if (EVP_PKEY_generate(ctx, &pkey) != 1) + { + goto error; + } + } + else { + if (EVP_PKEY_fromdata_init(ctx) != 1) + { + goto error; + } + int selection = (d && dLength > 0) ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; if (EVP_PKEY_fromdata(ctx, &pkey, selection, params) != 1) { From a619c892b481d8e95f1f984d7999b82aab5e8da1 Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Wed, 22 Apr 2026 16:15:55 -0700 Subject: [PATCH 04/15] add tests --- .../Interop.EcDsa.ImportExport.cs | 13 +- .../ECDiffieHellmanOpenSsl.Derive.cs | 17 ++- .../ECDiffieHellmanOpenSslPublicKey.cs | 17 ++- .../Cryptography/ECOpenSsl.ImportExport.cs | 2 +- .../tests/EcDiffieHellmanOpenSslTests.cs | 137 ++++++++++++++++++ .../tests/EcDsaOpenSslTests.cs | 120 +++++++++++++++ .../pal_ecc_import_export.c | 6 +- 7 files changed, 304 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs index 0b6813df7e0faa..d135bddd54e6e7 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs @@ -244,9 +244,18 @@ internal static bool EvpPKeyHasCurveName(SafeEvpPKeyHandle pkey) [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_EvpPKeyEcHasExplicitEncoding(SafeEvpPKeyHandle pkey); - internal static bool EvpPKeyEcHasExplicitEncoding(SafeEvpPKeyHandle pkey) + /// + /// Returns true if the key has explicit encoding, false if named, null if indeterminate (pre-3.0). + /// + internal static bool? EvpPKeyEcHasExplicitEncoding(SafeEvpPKeyHandle pkey) { - return CryptoNative_EvpPKeyEcHasExplicitEncoding(pkey) == 1; + int result = CryptoNative_EvpPKeyEcHasExplicitEncoding(pkey); + return result switch + { + 1 => true, + 0 => false, + _ => null, + }; } [LibraryImport(Libraries.CryptoNative)] diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index 55aacd66d56788..ae93353f36402b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -88,7 +88,22 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa Debug.Assert(otherPartyPublicKey != null); Debug.Assert(_key is not null); // Callers should validate prior. - bool thisIsNamed = !Interop.Crypto.EvpPKeyEcHasExplicitEncoding(_key.Value); + bool thisIsNamed; + { + bool? explicitEncoding = Interop.Crypto.EvpPKeyEcHasExplicitEncoding(_key.Value); + if (explicitEncoding.HasValue) + { + thisIsNamed = !explicitEncoding.Value; + } + else + { + // Pre-3.0 fallback: check via EC_KEY. + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + thisIsNamed = Interop.Crypto.EcKeyHasCurveName(ecKey); + } + } + } ECDiffieHellmanOpenSslPublicKey? otherKey = otherPartyPublicKey as ECDiffieHellmanOpenSslPublicKey; bool disposeOtherKey = false; diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs index f66b497b4f384f..6d73a840dbbe2c 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs @@ -47,7 +47,22 @@ public override ECParameters ExportParameters() => internal bool HasCurveName => Interop.Crypto.EvpPKeyHasCurveName(GetKey()); - internal bool HasExplicitEncoding => Interop.Crypto.EvpPKeyEcHasExplicitEncoding(GetKey()); + internal bool HasExplicitEncoding + { + get + { + bool? result = Interop.Crypto.EvpPKeyEcHasExplicitEncoding(GetKey()); + if (result.HasValue) + return result.Value; + + // Pre-3.0 fallback: check via EC_KEY whether the key has a curve name. + // If it doesn't have a curve name, it's explicit. + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(GetKey())) + { + return !Interop.Crypto.EcKeyHasCurveName(ecKey); + } + } + } internal int KeySize { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs index 15c320f0200a24..cf5415b5a8d633 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs @@ -226,7 +226,7 @@ private static ECParameters ExportECParametersFromEvpPKeyUsingParams(SafeEvpPKey { // Check encoding first — explicit-encoding keys must be exported with // explicit curve parameters even if OpenSSL can match a named curve. - if (Interop.Crypto.EvpPKeyEcHasExplicitEncoding(pkey)) + if (Interop.Crypto.EvpPKeyEcHasExplicitEncoding(pkey) == true) { return ExportExplicitCurveParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); } diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslTests.cs b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslTests.cs index 11ea3645092058..8485f4637c1715 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslTests.cs +++ b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslTests.cs @@ -129,6 +129,28 @@ public void CtorHandleDuplicate() } } + [Theory] + [InlineData(ECDSA_P256_OID_VALUE, 256)] + [InlineData(ECDSA_P384_OID_VALUE, 384)] + [InlineData(ECDSA_P521_OID_VALUE, 521)] + public void CtorEvpPKeyHandle(string oid, int expectedKeySize) + { + int rc = Interop.Crypto.EvpPKeyGenerateByEcKeyOid(out SafeEvpPKeyHandle pkey, oid); + + if (rc != 1 || pkey.IsInvalid) + { + pkey.Dispose(); + throw new SkipTestException("EVP_PKEY EC generation not supported"); + } + + using (pkey) + using (var e = new ECDiffieHellmanOpenSsl(pkey)) + { + Assert.Equal(expectedKeySize, e.KeySize); + e.Exercise(); + } + } + [Fact] public void KeySizePropWithExercise() { @@ -278,5 +300,120 @@ public void LookupCurveByOidFriendlyName() Assert.Equal("ECDSA_P521", param.Curve.Oid.FriendlyName); // OpenSsl maps secp521r1 to ECDSA_P521 Assert.Equal(ECDSA_P521_OID_VALUE, param.Curve.Oid.Value); } + + [Theory] + [InlineData(ECDSA_P256_OID_VALUE, 256)] + [InlineData(ECDSA_P384_OID_VALUE, 384)] + [InlineData(ECDSA_P521_OID_VALUE, 521)] + public void EcKeyAndEvpPKeyProduceSameExport(string oid, int expectedKeySize) + { + IntPtr ecKey = Interop.Crypto.EcKeyCreateByOid(oid); + Assert.NotEqual(IntPtr.Zero, ecKey); + + try + { + Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(ecKey)); + + using var ecKeyBacked = new ECDiffieHellmanOpenSsl(ecKey); + Assert.Equal(expectedKeySize, ecKeyBacked.KeySize); + + ECParameters privateParams = ecKeyBacked.ExportParameters(true); + + using ECDiffieHellman evpBacked = ECDiffieHellman.Create(privateParams); + Assert.Equal(expectedKeySize, evpBacked.KeySize); + + ECParameters evpPrivateParams = evpBacked.ExportParameters(true); + + ComparePublicKey(privateParams.Q, evpPrivateParams.Q); + ComparePrivateKey(privateParams, evpPrivateParams); + } + finally + { + Interop.Crypto.EcKeyDestroy(ecKey); + } + } + + [Theory] + [InlineData(ECDSA_P256_OID_VALUE)] + [InlineData(ECDSA_P384_OID_VALUE)] + [InlineData(ECDSA_P521_OID_VALUE)] + public void EcKeyAndEvpPKeyDeriveCrossCompatible(string oid) + { + IntPtr rawKey1 = Interop.Crypto.EcKeyCreateByOid(oid); + Assert.NotEqual(IntPtr.Zero, rawKey1); + IntPtr rawKey2 = Interop.Crypto.EcKeyCreateByOid(oid); + Assert.NotEqual(IntPtr.Zero, rawKey2); + + try + { + Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(rawKey1)); + Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(rawKey2)); + + using var ecKeyBacked1 = new ECDiffieHellmanOpenSsl(rawKey1); + using var ecKeyBacked2 = new ECDiffieHellmanOpenSsl(rawKey2); + + using ECDiffieHellman evpBacked1 = ECDiffieHellman.Create(ecKeyBacked1.ExportParameters(true)); + using ECDiffieHellman evpBacked2 = ECDiffieHellman.Create(ecKeyBacked2.ExportParameters(true)); + + using ECDiffieHellmanPublicKey ecPub2 = ecKeyBacked2.PublicKey; + using ECDiffieHellmanPublicKey evpPub2 = evpBacked2.PublicKey; + + byte[] ecEc = ecKeyBacked1.DeriveKeyFromHash(ecPub2, HashAlgorithmName.SHA256); + byte[] ecEvp = ecKeyBacked1.DeriveKeyFromHash(evpPub2, HashAlgorithmName.SHA256); + byte[] evpEc = evpBacked1.DeriveKeyFromHash(ecPub2, HashAlgorithmName.SHA256); + byte[] evpEvp = evpBacked1.DeriveKeyFromHash(evpPub2, HashAlgorithmName.SHA256); + + Assert.Equal(ecEc, ecEvp); + Assert.Equal(ecEc, evpEc); + Assert.Equal(ecEc, evpEvp); + } + finally + { + Interop.Crypto.EcKeyDestroy(rawKey1); + Interop.Crypto.EcKeyDestroy(rawKey2); + } + } + + [Fact] + public void ExplicitCurveEcKeyAndEvpPKeyProduceSameExport() + { + ECCurve explicitCurve = EccTestData.GetNistP256ExplicitCurve(); + + using ECDiffieHellman ecKeyBacked = ECDiffieHellman.Create(explicitCurve); + ECParameters explicitParams = ecKeyBacked.ExportExplicitParameters(true); + + using ECDiffieHellman evpBacked = ECDiffieHellman.Create(explicitParams); + ECParameters evpExplicitParams = evpBacked.ExportExplicitParameters(true); + + ComparePublicKey(explicitParams.Q, evpExplicitParams.Q); + ComparePrivateKey(explicitParams, evpExplicitParams); + } + + [Fact] + public void ExplicitCurveEcKeyAndEvpPKeyDeriveCrossCompatible() + { + ECCurve explicitCurve = EccTestData.GetNistP256ExplicitCurve(); + + using ECDiffieHellman key1 = ECDiffieHellman.Create(explicitCurve); + using ECDiffieHellman key2 = ECDiffieHellman.Create(explicitCurve); + + ECParameters key1Params = key1.ExportExplicitParameters(true); + ECParameters key2Params = key2.ExportExplicitParameters(true); + + using ECDiffieHellman key1Reimported = ECDiffieHellman.Create(key1Params); + using ECDiffieHellman key2Reimported = ECDiffieHellman.Create(key2Params); + + using ECDiffieHellmanPublicKey pub2 = key2.PublicKey; + using ECDiffieHellmanPublicKey pub2Reimported = key2Reimported.PublicKey; + + byte[] derive1 = key1.DeriveKeyFromHash(pub2, HashAlgorithmName.SHA256); + byte[] derive2 = key1.DeriveKeyFromHash(pub2Reimported, HashAlgorithmName.SHA256); + byte[] derive3 = key1Reimported.DeriveKeyFromHash(pub2, HashAlgorithmName.SHA256); + byte[] derive4 = key1Reimported.DeriveKeyFromHash(pub2Reimported, HashAlgorithmName.SHA256); + + Assert.Equal(derive1, derive2); + Assert.Equal(derive1, derive3); + Assert.Equal(derive1, derive4); + } } } diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs index 6f1139a8630629..213f120420a280 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs +++ b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs @@ -131,6 +131,28 @@ public void CtorHandleDuplicate() } } + [Theory] + [InlineData(ECDSA_P256_OID_VALUE, 256)] + [InlineData(ECDSA_P384_OID_VALUE, 384)] + [InlineData(ECDSA_P521_OID_VALUE, 521)] + public void CtorEvpPKeyHandle(string oid, int expectedKeySize) + { + int rc = Interop.Crypto.EvpPKeyGenerateByEcKeyOid(out SafeEvpPKeyHandle pkey, oid); + + if (rc != 1 || pkey.IsInvalid) + { + pkey.Dispose(); + throw new SkipTestException("EVP_PKEY EC generation not supported"); + } + + using (pkey) + using (ECDsaOpenSsl e = new ECDsaOpenSsl(pkey)) + { + Assert.Equal(expectedKeySize, e.KeySize); + e.Exercise(); + } + } + [Fact] public void KeySizePropWithExercise() { @@ -297,6 +319,101 @@ public void LookupCurveByOidFriendlyName() Assert.Equal("ECDSA_P521", param.Curve.Oid.FriendlyName); // OpenSsl maps secp521r1 to ECDSA_P521 Assert.Equal(ECDSA_P521_OID_VALUE, param.Curve.Oid.Value); } + + [Theory] + [InlineData(ECDSA_P256_OID_VALUE, 256)] + [InlineData(ECDSA_P384_OID_VALUE, 384)] + [InlineData(ECDSA_P521_OID_VALUE, 521)] + public void EcKeyAndEvpPKeyProduceSameExport(string oid, int expectedKeySize) + { + IntPtr ecKey = Interop.Crypto.EcKeyCreateByOid(oid); + Assert.NotEqual(IntPtr.Zero, ecKey); + + try + { + Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(ecKey)); + + using ECDsaOpenSsl ecKeyBacked = new ECDsaOpenSsl(ecKey); + Assert.Equal(expectedKeySize, ecKeyBacked.KeySize); + + ECParameters privateParams = ecKeyBacked.ExportParameters(true); + + using ECDsa evpBacked = ECDsa.Create(privateParams); + Assert.Equal(expectedKeySize, evpBacked.KeySize); + + ECParameters evpPrivateParams = evpBacked.ExportParameters(true); + + ComparePublicKey(privateParams.Q, evpPrivateParams.Q); + ComparePrivateKey(privateParams, evpPrivateParams); + } + finally + { + Interop.Crypto.EcKeyDestroy(ecKey); + } + } + + [Theory] + [InlineData(ECDSA_P256_OID_VALUE)] + [InlineData(ECDSA_P384_OID_VALUE)] + [InlineData(ECDSA_P521_OID_VALUE)] + public void EcKeyAndEvpPKeySignVerifyCrossCompatible(string oid) + { + byte[] data = ByteUtils.RepeatByte(0x42, 64); + + IntPtr ecKey = Interop.Crypto.EcKeyCreateByOid(oid); + Assert.NotEqual(IntPtr.Zero, ecKey); + + try + { + Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(ecKey)); + + using ECDsaOpenSsl ecKeyBacked = new ECDsaOpenSsl(ecKey); + using ECDsa evpBacked = ECDsa.Create(ecKeyBacked.ExportParameters(true)); + + byte[] sig1 = ecKeyBacked.SignData(data, HashAlgorithmName.SHA256); + Assert.True(evpBacked.VerifyData(data, sig1, HashAlgorithmName.SHA256)); + + byte[] sig2 = evpBacked.SignData(data, HashAlgorithmName.SHA256); + Assert.True(ecKeyBacked.VerifyData(data, sig2, HashAlgorithmName.SHA256)); + } + finally + { + Interop.Crypto.EcKeyDestroy(ecKey); + } + } + + [Fact] + public void ExplicitCurveEcKeyAndEvpPKeyProduceSameExport() + { + ECCurve explicitCurve = EccTestData.GetNistP256ExplicitCurve(); + + using ECDsa ecKeyBacked = ECDsa.Create(explicitCurve); + ECParameters explicitParams = ecKeyBacked.ExportExplicitParameters(true); + + using ECDsa evpBacked = ECDsa.Create(explicitParams); + ECParameters evpExplicitParams = evpBacked.ExportExplicitParameters(true); + + ComparePublicKey(explicitParams.Q, evpExplicitParams.Q); + ComparePrivateKey(explicitParams, evpExplicitParams); + } + + [Fact] + public void ExplicitCurveEcKeyAndEvpPKeySignVerifyCrossCompatible() + { + byte[] data = ByteUtils.RepeatByte(0x42, 64); + ECCurve explicitCurve = EccTestData.GetNistP256ExplicitCurve(); + + using ECDsa key1 = ECDsa.Create(explicitCurve); + ECParameters explicitParams = key1.ExportExplicitParameters(true); + + using ECDsa key2 = ECDsa.Create(explicitParams); + + byte[] sig1 = key1.SignData(data, HashAlgorithmName.SHA256); + Assert.True(key2.VerifyData(data, sig1, HashAlgorithmName.SHA256)); + + byte[] sig2 = key2.SignData(data, HashAlgorithmName.SHA256); + Assert.True(key1.VerifyData(data, sig2, HashAlgorithmName.SHA256)); + } } } @@ -315,5 +432,8 @@ internal static partial class Crypto [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_OpenSslVersionNumber")] internal static extern uint OpenSslVersionNumber(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyGenerateByEcKeyOid", CharSet = CharSet.Ansi)] + internal static extern int EvpPKeyGenerateByEcKeyOid(out SafeEvpPKeyHandle pkey, string oid); } } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index a42b23b294f272..818aa1393bfc09 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -476,7 +476,7 @@ int32_t CryptoNative_EvpPKeyEcHasExplicitEncoding(const EVP_PKEY* pkey) #ifdef FEATURE_DISTRO_AGNOSTIC_SSL if (!API_EXISTS(EVP_PKEY_get_utf8_string_param)) { - return 0; + return -1; } #endif @@ -487,7 +487,7 @@ int32_t CryptoNative_EvpPKeyEcHasExplicitEncoding(const EVP_PKEY* pkey) return (strcmp(encoding, "explicit") == 0) ? 1 : 0; #else - return 0; + return -1; #endif } @@ -1167,7 +1167,7 @@ int32_t CryptoNative_EvpPKeyGenerateByEcKeyOid( ERR_clear_error(); #ifdef NEED_OPENSSL_3_0 - if (!API_EXISTS(EVP_PKEY_keygen)) + if (!API_EXISTS(EVP_PKEY_CTX_new_from_name)) { return 0; } From 6535dcc5cae05d00bc4ea4f4818a21a770a39cb3 Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Thu, 23 Apr 2026 16:25:10 -0700 Subject: [PATCH 05/15] remove EC2M paths where possible and cleanup --- .../Interop.EcDsa.ImportExport.cs | 2 - .../opensslshim.h | 8 + .../pal_ecc_import_export.c | 516 ++++++------------ 3 files changed, 172 insertions(+), 354 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs index d135bddd54e6e7..bfc59efc9c8774 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs @@ -310,7 +310,6 @@ internal static ECParameters GetECKeyParameters( } else if (rc != 1) { - System.Console.WriteLine($"error getting params: {rc}"); throw Interop.Crypto.CreateOpenSslCryptographicException(); } @@ -358,7 +357,6 @@ internal static ECParameters EvpPKeyGetEcKeyParameters( } else if (rc != 1) { - Console.WriteLine($"error getting params: {rc}"); throw Interop.Crypto.CreateOpenSslCryptographicException(); } diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index f96b68c690a919..d71780f13b6125 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -405,6 +405,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EC_GROUP_get0_seed) \ REQUIRED_FUNCTION(EC_GROUP_get_cofactor) \ REQUIRED_FUNCTION(EC_GROUP_get_curve_GFp) \ + REQUIRED_FUNCTION(EC_GROUP_get_curve) \ REQUIRED_FUNCTION(EC_GROUP_get_curve_name) \ REQUIRED_FUNCTION(EC_GROUP_get_degree) \ REQUIRED_FUNCTION(EC_GROUP_get_order) \ @@ -415,6 +416,7 @@ extern bool g_libSslUses32BitTime; LIGHTUP_FUNCTION(EC_GROUP_new_by_curve_name) \ REQUIRED_FUNCTION(EC_GROUP_new_curve_GFp) \ REQUIRED_FUNCTION(EC_GROUP_set_curve_GFp) \ + REQUIRED_FUNCTION(EC_GROUP_set_curve) \ REQUIRED_FUNCTION(EC_GROUP_set_generator) \ REQUIRED_FUNCTION(EC_GROUP_set_seed) \ REQUIRED_FUNCTION(EC_KEY_check_key) \ @@ -433,10 +435,12 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EC_METHOD_get_field_type) \ REQUIRED_FUNCTION(EC_POINT_free) \ REQUIRED_FUNCTION(EC_POINT_get_affine_coordinates_GFp) \ + REQUIRED_FUNCTION(EC_POINT_get_affine_coordinates) \ REQUIRED_FUNCTION(EC_POINT_mul) \ REQUIRED_FUNCTION(EC_POINT_new) \ REQUIRED_FUNCTION(EC_POINT_point2oct) \ REQUIRED_FUNCTION(EC_POINT_set_affine_coordinates_GFp) \ + REQUIRED_FUNCTION(EC_POINT_set_affine_coordinates) \ LIGHTUP_FUNCTION(EC_POINT_oct2point) \ LIGHTUP_FUNCTION(ENGINE_by_id) \ LIGHTUP_FUNCTION(ENGINE_finish) \ @@ -976,6 +980,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EC_GROUP_get0_seed EC_GROUP_get0_seed_ptr #define EC_GROUP_get_cofactor EC_GROUP_get_cofactor_ptr #define EC_GROUP_get_curve_GFp EC_GROUP_get_curve_GFp_ptr +#define EC_GROUP_get_curve EC_GROUP_get_curve_ptr #define EC_GROUP_get_curve_name EC_GROUP_get_curve_name_ptr #define EC_GROUP_get_degree EC_GROUP_get_degree_ptr #define EC_GROUP_get_order EC_GROUP_get_order_ptr @@ -986,6 +991,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EC_GROUP_new_by_curve_name EC_GROUP_new_by_curve_name_ptr #define EC_GROUP_new_curve_GFp EC_GROUP_new_curve_GFp_ptr #define EC_GROUP_set_curve_GFp EC_GROUP_set_curve_GFp_ptr +#define EC_GROUP_set_curve EC_GROUP_set_curve_ptr #define EC_GROUP_set_generator EC_GROUP_set_generator_ptr #define EC_GROUP_set_seed EC_GROUP_set_seed_ptr #define EC_KEY_check_key EC_KEY_check_key_ptr @@ -1004,10 +1010,12 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EC_METHOD_get_field_type EC_METHOD_get_field_type_ptr #define EC_POINT_free EC_POINT_free_ptr #define EC_POINT_get_affine_coordinates_GFp EC_POINT_get_affine_coordinates_GFp_ptr +#define EC_POINT_get_affine_coordinates EC_POINT_get_affine_coordinates_ptr #define EC_POINT_mul EC_POINT_mul_ptr #define EC_POINT_new EC_POINT_new_ptr #define EC_POINT_point2oct EC_POINT_point2oct_ptr #define EC_POINT_set_affine_coordinates_GFp EC_POINT_set_affine_coordinates_GFp_ptr +#define EC_POINT_set_affine_coordinates EC_POINT_set_affine_coordinates_ptr #define EC_POINT_oct2point EC_POINT_oct2point_ptr #define ENGINE_by_id ENGINE_by_id_ptr #define ENGINE_finish ENGINE_finish_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index 818aa1393bfc09..1b726714f5f750 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -4,6 +4,33 @@ #include "pal_ecc_import_export.h" #include "pal_utilities.h" +#ifdef NEED_OPENSSL_3_0 +static const char FieldTypeChar2[] = "characteristic-two-field"; +static const char FieldTypePrime[] = "prime-field"; + +// Encode two coordinates as an uncompressed EC point: 0x04 || x || y. +// Coordinates are zero-padded on the left to the larger of the two lengths. +// Returns an OPENSSL_zalloc'd buffer (caller must OPENSSL_free), or NULL on failure. +static uint8_t* EncodeUncompressedPoint( + const uint8_t* x, int32_t xLength, + const uint8_t* y, int32_t yLength, + int32_t* outLength) +{ + int32_t coordLen = xLength > yLength ? xLength : yLength; + int32_t len = 1 + 2 * coordLen; + uint8_t* buf = (uint8_t*)OPENSSL_zalloc((size_t)len); + if (buf == NULL) + return NULL; + + buf[0] = 0x04; + memcpy(buf + 1 + (coordLen - xLength), x, (size_t)xLength); + memcpy(buf + 1 + coordLen + (coordLen - yLength), y, (size_t)yLength); + *outLength = len; + + return buf; +} +#endif + static ECCurveType MethodToCurveType(const EC_METHOD* method) { if (method == EC_GFp_mont_method()) @@ -49,26 +76,9 @@ static ECCurveType EcKeyGetCurveType( return MethodToCurveType(method); } -static int EcPointGetAffineCoordinates(const EC_GROUP *group, ECCurveType curveType, const EC_POINT *p, BIGNUM *x, BIGNUM *y) +static int EcPointGetAffineCoordinates(const EC_GROUP *group, const EC_POINT *p, BIGNUM *x, BIGNUM *y) { -#if HAVE_OPENSSL_EC2M - if (API_EXISTS(EC_POINT_get_affine_coordinates_GF2m) && (curveType == Characteristic2)) - { - if (!EC_POINT_get_affine_coordinates_GF2m(group, p, x, y, NULL)) - return 0; - } - else -#endif - { - if (!EC_POINT_get_affine_coordinates_GFp(group, p, x, y, NULL)) - return 0; - } - -#if !HAVE_OPENSSL_EC2M - (void)curveType; -#endif - - return 1; + return EC_POINT_get_affine_coordinates(group, p, x, y, NULL) ? 1 : 0; } int32_t CryptoNative_GetECKeyParameters( @@ -95,23 +105,17 @@ int32_t CryptoNative_GetECKeyParameters( ECCurveType curveType = EcKeyGetCurveType(key); const EC_POINT* Q = EC_KEY_get0_public_key(key); const EC_GROUP* group = EC_KEY_get0_group(key); - if (curveType == Unspecified || !Q || !group) { - rc = 5; + if (curveType == Unspecified || !Q || !group) goto error; - } // Extract qx and qy xBn = BN_new(); yBn = BN_new(); - if (!xBn || !yBn) { - rc = 6; + if (!xBn || !yBn) goto error; - } - if (!EcPointGetAffineCoordinates(group, curveType, Q, xBn, yBn)) { - rc = 7; + if (!EcPointGetAffineCoordinates(group, Q, xBn, yBn)) goto error; - } // Success; assign variables *qx = xBn; *cbQx = BN_num_bytes(xBn); @@ -231,35 +235,13 @@ int32_t CryptoNative_GetECCurveParameters( goto error; // Extract p, a, b -#if HAVE_OPENSSL_EC2M - if (API_EXISTS(EC_GROUP_get_curve_GF2m) && (*curveType == Characteristic2)) - { - // pBn represents the binary polynomial - if (!EC_GROUP_get_curve_GF2m(group, pBn, aBn, bBn, NULL)) - goto error; - } - else -#endif - { - // pBn represents the prime - if (!EC_GROUP_get_curve_GFp(group, pBn, aBn, bBn, NULL)) - goto error; - } + if (!EC_GROUP_get_curve(group, pBn, aBn, bBn, NULL)) + goto error; // Extract gx and gy G = EC_GROUP_get0_generator(group); -#if HAVE_OPENSSL_EC2M - if (API_EXISTS(EC_POINT_get_affine_coordinates_GF2m) && (*curveType == Characteristic2)) - { - if (!EC_POINT_get_affine_coordinates_GF2m(group, G, xBn, yBn, NULL)) - goto error; - } - else -#endif - { - if (!EC_POINT_get_affine_coordinates_GFp(group, G, xBn, yBn, NULL)) - goto error; - } + if (!EcPointGetAffineCoordinates(group, G, xBn, yBn)) + goto error; // Extract order (n) if (!EC_GROUP_get_order(group, orderBn, NULL)) @@ -455,11 +437,7 @@ int32_t CryptoNative_EvpPKeyGetEcGroupNid(const EVP_PKEY *pkey, int32_t* nidName char curveName[80] = {0}; if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curveName, sizeof(curveName), NULL)) - { - // Clear error queue - this is expected for explicit curve keys. - ERR_clear_error(); return 0; - } *nidName = OBJ_txt2nid(curveName); return 1; @@ -515,7 +493,7 @@ int32_t CryptoNative_EvpPKeyGetEcFieldDegree(const EVP_PKEY* pkey) // For GF(2^m): p is the irreducible polynomial, degree = BN_num_bits(p) - 1. // For GF(p): degree = BN_num_bits(p). int degree = BN_num_bits(p); - if (strcmp(fieldType, "characteristic-two-field") == 0) + if (strcmp(fieldType, FieldTypeChar2) == 0) degree--; BN_free(p); @@ -540,7 +518,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( assert(cbD != NULL); #ifdef FEATURE_DISTRO_AGNOSTIC_SSL - if (!API_EXISTS(EVP_PKEY_get_octet_string_param)) + if (!API_EXISTS(EVP_PKEY_get_bn_param)) { *cbQx = *cbQy = 0; *qx = *qy = 0; @@ -563,154 +541,132 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( ERR_clear_error(); - if (includePrivate) - { - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &dBn)) { - rc = 12; - goto error; - } - - *d = dBn; - *cbD = BN_num_bytes(dBn); - } - else - { - *d = NULL; - *cbD = 0; - } - // Get the public key as an encoded point (may be compressed or uncompressed). // We use OSSL_PKEY_PARAM_PUB_KEY instead of OSSL_PKEY_PARAM_EC_PUB_X/Y // because the individual X/Y components may not be materialized yet. size_t pubKeyLen = 0; if (!EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, NULL, 0, &pubKeyLen)) - { - rc = 5; goto error; - } - pubKeyBuf = (uint8_t*)calloc(pubKeyLen, 1); + pubKeyBuf = (uint8_t*)OPENSSL_zalloc(pubKeyLen); if (pubKeyBuf == NULL) - { - rc = 7; goto error; - } if (!EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, pubKeyLen, &pubKeyLen)) - { - rc = 8; goto error; - } - { - // Decode the encoded point (compressed or uncompressed) to extract X and Y. - // Build an EC_GROUP from the key's parameters to perform the decoding. - EC_GROUP* group = NULL; + // Decode the encoded point (compressed or uncompressed) to extract X and Y. + // Build an EC_GROUP from the key's parameters to perform the decoding. + EC_GROUP* group = NULL; - // Try named curve first. - char curveName[80] = {0}; - if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curveName, sizeof(curveName), NULL)) + // Try named curve first. + char curveName[80] = {0}; + if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curveName, sizeof(curveName), NULL)) + { + int nid = OBJ_txt2nid(curveName); + if (nid != NID_undef && API_EXISTS(EC_GROUP_new_by_curve_name)) { - int nid = OBJ_txt2nid(curveName); - if (nid != NID_undef && API_EXISTS(EC_GROUP_new_by_curve_name)) - { - group = EC_GROUP_new_by_curve_name(nid); - } + group = EC_GROUP_new_by_curve_name(nid); } + } - if (group == NULL) - { - // Explicit curve — build EC_GROUP from the key's field params. - BIGNUM* ecP = NULL; - BIGNUM* ecA = NULL; - BIGNUM* ecB = NULL; - - EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_P, &ecP); - EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_A, &ecA); - EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_B, &ecB); - - if (ecP == NULL || ecA == NULL || ecB == NULL) - { - BN_free(ecP); - BN_free(ecA); - BN_free(ecB); - rc = 6; - goto error; - } - - char fieldType[64] = {0}; - int isChar2 = 0; - - if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, sizeof(fieldType), NULL)) - { - isChar2 = (strcmp(fieldType, "characteristic-two-field") == 0); - } + if (group == NULL) + { + // Explicit curve — build EC_GROUP from the key's field params. + BIGNUM* ecP = NULL; + BIGNUM* ecA = NULL; + BIGNUM* ecB = NULL; -#if HAVE_OPENSSL_EC2M - if (isChar2 && API_EXISTS(EC_GROUP_new_curve_GF2m)) - { - group = EC_GROUP_new_curve_GF2m(ecP, ecA, ecB, NULL); - } - else -#endif - { - group = EC_GROUP_new_curve_GFp(ecP, ecA, ecB, NULL); - } + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_P, &ecP); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_A, &ecA); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_B, &ecB); + if (ecP == NULL || ecA == NULL || ecB == NULL) + { BN_free(ecP); BN_free(ecA); BN_free(ecB); - - if (group == NULL) - { - rc = 6; - goto error; - } + goto error; } - EC_POINT* point = EC_POINT_new(group); - if (point == NULL || - !API_EXISTS(EC_POINT_oct2point) || - !EC_POINT_oct2point(group, point, pubKeyBuf, pubKeyLen, NULL)) + char fieldType[64] = {0}; + int isChar2 = 0; + + if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, sizeof(fieldType), NULL)) { - EC_POINT_free(point); - EC_GROUP_free(group); - rc = 9; - goto error; + isChar2 = (strcmp(fieldType, FieldTypeChar2) == 0); } - xBn = BN_new(); - yBn = BN_new(); - - if (xBn == NULL || yBn == NULL) +#if HAVE_OPENSSL_EC2M + if (isChar2 && API_EXISTS(EC_GROUP_new_curve_GF2m)) { - EC_POINT_free(point); - EC_GROUP_free(group); - rc = 10; - goto error; + group = EC_GROUP_new_curve_GF2m(ecP, ecA, ecB, NULL); + } + else +#endif + { + group = EC_GROUP_new_curve_GFp(ecP, ecA, ecB, NULL); } - const EC_METHOD* groupMethod = EC_GROUP_method_of(group); - ECCurveType groupCurveType = MethodToCurveType(groupMethod); + BN_free(ecP); + BN_free(ecA); + BN_free(ecB); - if (!EcPointGetAffineCoordinates(group, groupCurveType, point, xBn, yBn)) - { - EC_POINT_free(point); - EC_GROUP_free(group); - rc = 11; + if (group == NULL) goto error; - } + } + + EC_POINT* point = EC_POINT_new(group); + if (point == NULL || + !API_EXISTS(EC_POINT_oct2point) || + !EC_POINT_oct2point(group, point, pubKeyBuf, pubKeyLen, NULL)) + { + EC_POINT_free(point); + EC_GROUP_free(group); + goto error; + } + + xBn = BN_new(); + yBn = BN_new(); + + if (xBn == NULL || yBn == NULL) + { + EC_POINT_free(point); + EC_GROUP_free(group); + goto error; + } + if (!EcPointGetAffineCoordinates(group, point, xBn, yBn)) + { EC_POINT_free(point); EC_GROUP_free(group); + goto error; } + EC_POINT_free(point); + EC_GROUP_free(group); + *qx = xBn; *cbQx = BN_num_bytes(xBn); *qy = yBn; *cbQy = BN_num_bytes(yBn); - free(pubKeyBuf); + if (includePrivate) + { + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &dBn)) + goto error; + + *d = dBn; + *cbD = BN_num_bytes(dBn); + } + else + { + *d = NULL; + *cbD = 0; + } + + // success + OPENSSL_free(pubKeyBuf); return 1; error: @@ -724,7 +680,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( if (cbD) *cbD = 0; if (xBn) BN_free(xBn); if (yBn) BN_free(yBn); - free(pubKeyBuf); + if (pubKeyBuf) OPENSSL_free(pubKeyBuf); return rc; } @@ -780,34 +736,15 @@ EC_KEY* CryptoNative_EcKeyCreateByExplicitParameters( aBn = BN_bin2bn(a, aLength, NULL); bBn = BN_bin2bn(b, bLength, NULL); -#if HAVE_OPENSSL_EC2M - if (API_EXISTS(EC_GROUP_set_curve_GF2m) && (curveType == Characteristic2)) - { - if (!EC_GROUP_set_curve_GF2m(group, pBn, aBn, bBn, NULL)) - goto error; - } - else -#endif - { - if (!EC_GROUP_set_curve_GFp(group, pBn, aBn, bBn, NULL)) - goto error; - } + if (!EC_GROUP_set_curve(group, pBn, aBn, bBn, NULL)) + goto error; // Set generator, order and cofactor G = EC_POINT_new(group); gxBn = BN_bin2bn(gx, gxLength, NULL); gyBn = BN_bin2bn(gy, gyLength, NULL); -#if HAVE_OPENSSL_EC2M - if (API_EXISTS(EC_POINT_set_affine_coordinates_GF2m) && (curveType == Characteristic2)) - { - EC_POINT_set_affine_coordinates_GF2m(group, G, gxBn, gyBn, NULL); - } - else -#endif - { - EC_POINT_set_affine_coordinates_GFp(group, G, gxBn, gyBn, NULL); - } + EC_POINT_set_affine_coordinates(group, G, gxBn, gyBn, NULL); orderBn = BN_bin2bn(order, orderLength, NULL); cofactorBn = BN_bin2bn(cofactor, cofactorLength, NULL); @@ -1056,7 +993,7 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( if (!EC_POINT_oct2point(group, G, generatorBuffer, generatorBufferSize, NULL)) goto error; - if (!EcPointGetAffineCoordinates(group, *curveType, G, xBn, yBn)) + if (!EcPointGetAffineCoordinates(group, G, xBn, yBn)) goto error; // Extract order (n) @@ -1186,33 +1123,23 @@ int32_t CryptoNative_EvpPKeyGenerateByEcKeyOid( EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); if (ctx == NULL) - { goto error; - } if (EVP_PKEY_keygen_init(ctx) <= 0) - { goto error; - } if (EVP_PKEY_CTX_set_group_name(ctx, groupName) <= 0) - { goto error; - } if (EVP_PKEY_keygen(ctx, pkey) <= 0) - { goto error; - } EVP_PKEY_CTX_free(ctx); return 1; error: if (ctx != NULL) - { EVP_PKEY_CTX_free(ctx); - } if (*pkey != NULL) { @@ -1272,44 +1199,27 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( bld = OSSL_PARAM_BLD_new(); if (bld == NULL) - { goto error; - } if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, groupName, 0)) - { goto error; - } if (qx && qy) { - // Build the uncompressed public key: 0x04 || qx || qy - int32_t coordLen = qxLength > qyLength ? qxLength : qyLength; - int32_t pubKeyLen = 1 + 2 * coordLen; - pubKeyBuf = (uint8_t*)calloc(1, (size_t)pubKeyLen); - + int32_t pubKeyLen; + pubKeyBuf = EncodeUncompressedPoint(qx, qxLength, qy, qyLength, &pubKeyLen); if (pubKeyBuf == NULL) - { goto error; - } - - pubKeyBuf[0] = 0x04; - memcpy(pubKeyBuf + 1 + (coordLen - qxLength), qx, (size_t)qxLength); - memcpy(pubKeyBuf + 1 + coordLen + (coordLen - qyLength), qy, (size_t)qyLength); if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, (size_t)pubKeyLen)) - { goto error; - } } else if (d && dLength > 0) { // No public key provided, derive Q = d * G using EC_GROUP/EC_POINT (not deprecated). EC_GROUP* group = EC_GROUP_new_by_curve_name(nid); if (group == NULL) - { goto error; - } EC_POINT* pubPoint = EC_POINT_new(group); BIGNUM* dBnTmp = BN_bin2bn(d, dLength, NULL); @@ -1333,7 +1243,7 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( goto error; } - pubKeyBuf = (uint8_t*)calloc(1, pubKeyLen); + pubKeyBuf = (uint8_t*)OPENSSL_zalloc(pubKeyLen); if (pubKeyBuf == NULL) { EC_POINT_free(pubPoint); @@ -1352,55 +1262,41 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( EC_GROUP_free(group); if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, pubKeyLen)) - { goto error; - } } if (d && dLength > 0) { dBn = BN_bin2bn(d, dLength, NULL); if (dBn == NULL) - { goto error; - } if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, dBn)) - { goto error; - } } params = OSSL_PARAM_BLD_to_param(bld); if (params == NULL) - { goto error; - } ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); if (ctx == NULL) - { goto error; - } if (EVP_PKEY_fromdata_init(ctx) != 1) - { goto error; - } { int selection = (d && dLength > 0) ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; if (EVP_PKEY_fromdata(ctx, pkey, selection, params) != 1) - { goto error; - } } OSSL_PARAM_free(params); OSSL_PARAM_BLD_free(bld); EVP_PKEY_CTX_free(ctx); BN_clear_free(dBn); - free(pubKeyBuf); + OPENSSL_free(pubKeyBuf); return 1; error: @@ -1408,7 +1304,8 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( OSSL_PARAM_BLD_free(bld); EVP_PKEY_CTX_free(ctx); BN_clear_free(dBn); - free(pubKeyBuf); + if (pubKeyBuf) OPENSSL_free(pubKeyBuf); + if (*pkey) { EVP_PKEY_free(*pkey); @@ -1468,147 +1365,98 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( bld = OSSL_PARAM_BLD_new(); if (bld == NULL) - { goto error; - } const char* fieldType = (curveType == Characteristic2) - ? "characteristic-two-field" - : "prime-field"; + ? FieldTypeChar2 + : FieldTypePrime; if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, 0)) - { goto error; - } if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_EC_ENCODING, "explicit", 0)) - { goto error; - } pBn = BN_bin2bn(p, pLength, NULL); aBn = BN_bin2bn(a, aLength, NULL); bBn = BN_bin2bn(b, bLength, NULL); if (!pBn || !aBn || !bBn) - { goto error; - } - if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_P, pBn) || - !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_A, aBn) || - !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_B, bBn)) - { + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_P, pBn)) + goto error; + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_A, aBn)) + goto error; + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_B, bBn)) goto error; - } // Generator as uncompressed point: 0x04 || gx || gy { - int32_t coordLen = gxLength > gyLength ? gxLength : gyLength; - int32_t genLen = 1 + 2 * coordLen; - generatorBuf = (uint8_t*)calloc(1, (size_t)genLen); + int32_t genLen; + generatorBuf = EncodeUncompressedPoint(gx, gxLength, gy, gyLength, &genLen); if (generatorBuf == NULL) - { goto error; - } - - generatorBuf[0] = 0x04; - memcpy(generatorBuf + 1 + (coordLen - gxLength), gx, (size_t)gxLength); - memcpy(generatorBuf + 1 + coordLen + (coordLen - gyLength), gy, (size_t)gyLength); if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_GENERATOR, generatorBuf, (size_t)genLen)) - { goto error; - } } orderBn = BN_bin2bn(order, orderLength, NULL); cofactorBn = BN_bin2bn(cofactor, cofactorLength, NULL); if (!orderBn || !cofactorBn) - { goto error; - } - if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_ORDER, orderBn) || - !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_COFACTOR, cofactorBn)) - { + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_ORDER, orderBn)) + goto error; + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_COFACTOR, cofactorBn)) goto error; - } if (seed && seedLength > 0) { if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_SEED, seed, (size_t)seedLength)) - { goto error; - } } if (d && dLength > 0) { dBn = BN_bin2bn(d, dLength, NULL); if (dBn == NULL) - { goto error; - } if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, dBn)) - { goto error; - } } if (qx && qy) { - int32_t coordLen = qxLength > qyLength ? qxLength : qyLength; - int32_t pubKeyLen = 1 + 2 * coordLen; - pubKeyBuf = (uint8_t*)calloc(1, (size_t)pubKeyLen); + int32_t pubKeyLen; + pubKeyBuf = EncodeUncompressedPoint(qx, qxLength, qy, qyLength, &pubKeyLen); if (pubKeyBuf == NULL) - { goto error; - } - - pubKeyBuf[0] = 0x04; - memcpy(pubKeyBuf + 1 + (coordLen - qxLength), qx, (size_t)qxLength); - memcpy(pubKeyBuf + 1 + coordLen + (coordLen - qyLength), qy, (size_t)qyLength); if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, (size_t)pubKeyLen)) - { goto error; - } } else if (d && dLength > 0) { // No public key provided, derive Q = d * G from the explicit curve parameters. const EC_METHOD* curveMethod = CurveTypeToMethod(curveType); if (!curveMethod) - { goto error; - } EC_GROUP* group = EC_GROUP_new(curveMethod); if (group == NULL) - { goto error; - } -#if HAVE_OPENSSL_EC2M - if (API_EXISTS(EC_GROUP_set_curve_GF2m) && (curveType == Characteristic2)) - { - if (!EC_GROUP_set_curve_GF2m(group, pBn, aBn, bBn, NULL)) - { - EC_GROUP_free(group); - goto error; - } - } - else -#endif + if (!EC_GROUP_set_curve(group, pBn, aBn, bBn, NULL)) { - if (!EC_GROUP_set_curve_GFp(group, pBn, aBn, bBn, NULL)) - { - EC_GROUP_free(group); - goto error; - } + EC_GROUP_free(group); + goto error; } // Set the generator @@ -1625,29 +1473,13 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( goto error; } -#if HAVE_OPENSSL_EC2M - if (API_EXISTS(EC_POINT_set_affine_coordinates_GF2m) && (curveType == Characteristic2)) + if (!EC_POINT_set_affine_coordinates(group, G, gxBn, gyBn, NULL)) { - if (!EC_POINT_set_affine_coordinates_GF2m(group, G, gxBn, gyBn, NULL)) - { - BN_free(gxBn); - BN_free(gyBn); - EC_POINT_free(G); - EC_GROUP_free(group); - goto error; - } - } - else -#endif - { - if (!EC_POINT_set_affine_coordinates_GFp(group, G, gxBn, gyBn, NULL)) - { - BN_free(gxBn); - BN_free(gyBn); - EC_POINT_free(G); - EC_GROUP_free(group); - goto error; - } + BN_free(gxBn); + BN_free(gyBn); + EC_POINT_free(G); + EC_GROUP_free(group); + goto error; } BN_free(gxBn); @@ -1674,7 +1506,7 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( goto error; } - pubKeyBuf = (uint8_t*)calloc(1, pubKeyLen); + pubKeyBuf = (uint8_t*)OPENSSL_zalloc(pubKeyLen); if (pubKeyBuf == NULL) { EC_POINT_free(pubPoint); @@ -1693,22 +1525,16 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( EC_GROUP_free(group); if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, pubKeyLen)) - { goto error; - } } params = OSSL_PARAM_BLD_to_param(bld); if (params == NULL) - { goto error; - } ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); if (ctx == NULL) - { goto error; - } if ((!qx || !qy) && (!d || dLength <= 0)) { @@ -1716,53 +1542,39 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( EVP_PKEY* templateKey = NULL; if (EVP_PKEY_fromdata_init(ctx) != 1) - { goto error; - } if (EVP_PKEY_fromdata(ctx, &templateKey, EVP_PKEY_KEY_PARAMETERS, params) != 1) - { goto error; - } EVP_PKEY_CTX_free(ctx); ctx = EVP_PKEY_CTX_new_from_pkey(NULL, templateKey, NULL); EVP_PKEY_free(templateKey); if (ctx == NULL) - { goto error; - } if (EVP_PKEY_keygen_init(ctx) != 1) - { goto error; - } if (EVP_PKEY_generate(ctx, &pkey) != 1) - { goto error; - } } else { if (EVP_PKEY_fromdata_init(ctx) != 1) - { goto error; - } int selection = (d && dLength > 0) ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; if (EVP_PKEY_fromdata(ctx, &pkey, selection, params) != 1) - { goto error; - } } OSSL_PARAM_free(params); OSSL_PARAM_BLD_free(bld); EVP_PKEY_CTX_free(ctx); - free(generatorBuf); - free(pubKeyBuf); + OPENSSL_free(generatorBuf); + OPENSSL_free(pubKeyBuf); BN_free(pBn); BN_free(aBn); BN_free(bBn); @@ -1775,8 +1587,8 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( OSSL_PARAM_free(params); OSSL_PARAM_BLD_free(bld); EVP_PKEY_CTX_free(ctx); - free(generatorBuf); - free(pubKeyBuf); + OPENSSL_free(generatorBuf); + OPENSSL_free(pubKeyBuf); BN_free(pBn); BN_free(aBn); BN_free(bBn); From 3bdb9d768326a965df631724b33aa68a3d9d9293 Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Thu, 23 Apr 2026 22:55:28 -0700 Subject: [PATCH 06/15] some more cleanup --- .../opensslshim.h | 4 +- .../pal_ecc_import_export.c | 302 ++++++++---------- 2 files changed, 137 insertions(+), 169 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index d71780f13b6125..8dd02a3be8fa81 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -413,7 +413,7 @@ extern bool g_libSslUses32BitTime; LIGHTUP_FUNCTION(EC_GROUP_get_field_type) \ REQUIRED_FUNCTION(EC_GROUP_method_of) \ REQUIRED_FUNCTION(EC_GROUP_new) \ - LIGHTUP_FUNCTION(EC_GROUP_new_by_curve_name) \ + REQUIRED_FUNCTION(EC_GROUP_new_by_curve_name) \ REQUIRED_FUNCTION(EC_GROUP_new_curve_GFp) \ REQUIRED_FUNCTION(EC_GROUP_set_curve_GFp) \ REQUIRED_FUNCTION(EC_GROUP_set_curve) \ @@ -441,7 +441,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EC_POINT_point2oct) \ REQUIRED_FUNCTION(EC_POINT_set_affine_coordinates_GFp) \ REQUIRED_FUNCTION(EC_POINT_set_affine_coordinates) \ - LIGHTUP_FUNCTION(EC_POINT_oct2point) \ + REQUIRED_FUNCTION(EC_POINT_oct2point) \ LIGHTUP_FUNCTION(ENGINE_by_id) \ LIGHTUP_FUNCTION(ENGINE_finish) \ LIGHTUP_FUNCTION(ENGINE_free) \ diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index 1b726714f5f750..55a2d311757e32 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -5,13 +5,11 @@ #include "pal_utilities.h" #ifdef NEED_OPENSSL_3_0 -static const char FieldTypeChar2[] = "characteristic-two-field"; -static const char FieldTypePrime[] = "prime-field"; // Encode two coordinates as an uncompressed EC point: 0x04 || x || y. // Coordinates are zero-padded on the left to the larger of the two lengths. // Returns an OPENSSL_zalloc'd buffer (caller must OPENSSL_free), or NULL on failure. -static uint8_t* EncodeUncompressedPoint( +static uint8_t* EncodeEcPointFromCoordinates( const uint8_t* x, int32_t xLength, const uint8_t* y, int32_t yLength, int32_t* outLength) @@ -29,6 +27,28 @@ static uint8_t* EncodeUncompressedPoint( return buf; } + +// Serialize an EC_POINT to uncompressed octet format. +// Returns an OPENSSL_zalloc'd buffer (caller must OPENSSL_free), or NULL on failure. +static uint8_t* EncodeEcPointFromPoint(const EC_GROUP* group, const EC_POINT* point, size_t* outLength) +{ + size_t len = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); + if (len == 0) + return NULL; + + uint8_t* buf = (uint8_t*)OPENSSL_zalloc(len); + if (buf == NULL) + return NULL; + + if (EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, buf, len, NULL) != len) + { + OPENSSL_free(buf); + return NULL; + } + + *outLength = len; + return buf; +} #endif static ECCurveType MethodToCurveType(const EC_METHOD* method) @@ -493,7 +513,7 @@ int32_t CryptoNative_EvpPKeyGetEcFieldDegree(const EVP_PKEY* pkey) // For GF(2^m): p is the irreducible polynomial, degree = BN_num_bits(p) - 1. // For GF(p): degree = BN_num_bits(p). int degree = BN_num_bits(p); - if (strcmp(fieldType, FieldTypeChar2) == 0) + if (strcmp(fieldType, SN_X9_62_characteristic_two_field) == 0) degree--; BN_free(p); @@ -564,7 +584,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curveName, sizeof(curveName), NULL)) { int nid = OBJ_txt2nid(curveName); - if (nid != NID_undef && API_EXISTS(EC_GROUP_new_by_curve_name)) + if (nid != NID_undef) { group = EC_GROUP_new_by_curve_name(nid); } @@ -594,7 +614,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, sizeof(fieldType), NULL)) { - isChar2 = (strcmp(fieldType, FieldTypeChar2) == 0); + isChar2 = (strcmp(fieldType, SN_X9_62_characteristic_two_field) == 0); } #if HAVE_OPENSSL_EC2M @@ -604,6 +624,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( } else #endif + if (!isChar2) { group = EC_GROUP_new_curve_GFp(ecP, ecA, ecB, NULL); } @@ -618,7 +639,6 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( EC_POINT* point = EC_POINT_new(group); if (point == NULL || - !API_EXISTS(EC_POINT_oct2point) || !EC_POINT_oct2point(group, point, pubKeyBuf, pubKeyLen, NULL)) { EC_POINT_free(point); @@ -666,7 +686,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( } // success - OPENSSL_free(pubKeyBuf); + if (pubKeyBuf) OPENSSL_free(pubKeyBuf); return 1; error: @@ -891,10 +911,8 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( assert(cbSeed != NULL); #ifdef FEATURE_DISTRO_AGNOSTIC_SSL - if (!API_EXISTS(EC_GROUP_new_by_curve_name) || - !API_EXISTS(EC_GROUP_get_field_type) || - !API_EXISTS(EVP_PKEY_get_octet_string_param) || - !API_EXISTS(EC_POINT_oct2point)) + if (!API_EXISTS(EC_GROUP_get_field_type) || + !API_EXISTS(EVP_PKEY_get_octet_string_param)) { return 0; } @@ -969,10 +987,17 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( // build it from the field parameters to decode the generator point. if (!group) { +#if HAVE_OPENSSL_EC2M if (fieldTypeNID == NID_X9_62_characteristic_two_field) + { group = EC_GROUP_new_curve_GF2m(pBn, aBn, bBn, NULL); + } else +#endif + if (fieldTypeNID == NID_X9_62_prime_field) + { group = EC_GROUP_new_curve_GFp(pBn, aBn, bBn, NULL); + } } if (!group) @@ -1191,11 +1216,17 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( return -1; } + int ret = 0; EVP_PKEY_CTX* ctx = NULL; uint8_t* pubKeyBuf = NULL; OSSL_PARAM_BLD* bld = NULL; OSSL_PARAM* params = NULL; BIGNUM* dBn = NULL; + EC_GROUP* group = NULL; + EC_POINT* pubPoint = NULL; + + const int hasPublicKey = (qx != NULL && qy != NULL); + const int hasPrivateKey = (d != NULL && dLength > 0); bld = OSSL_PARAM_BLD_new(); if (bld == NULL) @@ -1204,73 +1235,47 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, groupName, 0)) goto error; - if (qx && qy) + if (hasPrivateKey) + { + dBn = BN_bin2bn(d, dLength, NULL); + if (dBn == NULL) + goto error; + } + + // Push public key, deriving it from the private key if unavailable. + if (hasPublicKey) { int32_t pubKeyLen; - pubKeyBuf = EncodeUncompressedPoint(qx, qxLength, qy, qyLength, &pubKeyLen); + pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, &pubKeyLen); if (pubKeyBuf == NULL) goto error; if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, (size_t)pubKeyLen)) goto error; } - else if (d && dLength > 0) + else if (hasPrivateKey) { // No public key provided, derive Q = d * G using EC_GROUP/EC_POINT (not deprecated). - EC_GROUP* group = EC_GROUP_new_by_curve_name(nid); + group = EC_GROUP_new_by_curve_name(nid); if (group == NULL) goto error; - EC_POINT* pubPoint = EC_POINT_new(group); - BIGNUM* dBnTmp = BN_bin2bn(d, dLength, NULL); - - if (pubPoint == NULL || dBnTmp == NULL || - !EC_POINT_mul(group, pubPoint, dBnTmp, NULL, NULL, NULL)) - { - BN_clear_free(dBnTmp); - EC_POINT_free(pubPoint); - EC_GROUP_free(group); - goto error; - } - - BN_clear_free(dBnTmp); - - size_t pubKeyLen = EC_POINT_point2oct(group, pubPoint, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); - if (pubKeyLen == 0) - { - EC_POINT_free(pubPoint); - EC_GROUP_free(group); + pubPoint = EC_POINT_new(group); + if (pubPoint == NULL || + !EC_POINT_mul(group, pubPoint, dBn, NULL, NULL, NULL)) goto error; - } - pubKeyBuf = (uint8_t*)OPENSSL_zalloc(pubKeyLen); + size_t pubKeyLen; + pubKeyBuf = EncodeEcPointFromPoint(group, pubPoint, &pubKeyLen); if (pubKeyBuf == NULL) - { - EC_POINT_free(pubPoint); - EC_GROUP_free(group); - goto error; - } - - if (EC_POINT_point2oct(group, pubPoint, POINT_CONVERSION_UNCOMPRESSED, pubKeyBuf, pubKeyLen, NULL) != pubKeyLen) - { - EC_POINT_free(pubPoint); - EC_GROUP_free(group); goto error; - } - - EC_POINT_free(pubPoint); - EC_GROUP_free(group); if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, pubKeyLen)) goto error; } - if (d && dLength > 0) + if (hasPrivateKey) { - dBn = BN_bin2bn(d, dLength, NULL); - if (dBn == NULL) - goto error; - if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, dBn)) goto error; } @@ -1287,31 +1292,30 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( goto error; { - int selection = (d && dLength > 0) ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; + int selection = hasPrivateKey ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; if (EVP_PKEY_fromdata(ctx, pkey, selection, params) != 1) goto error; } - OSSL_PARAM_free(params); - OSSL_PARAM_BLD_free(bld); - EVP_PKEY_CTX_free(ctx); - BN_clear_free(dBn); - OPENSSL_free(pubKeyBuf); - return 1; + ret = 1; + goto exit; error: - OSSL_PARAM_free(params); - OSSL_PARAM_BLD_free(bld); - EVP_PKEY_CTX_free(ctx); - BN_clear_free(dBn); - if (pubKeyBuf) OPENSSL_free(pubKeyBuf); - if (*pkey) { EVP_PKEY_free(*pkey); *pkey = NULL; } - return 0; + +exit: + if (params) OSSL_PARAM_free(params); + if (bld) OSSL_PARAM_BLD_free(bld); + if (ctx) EVP_PKEY_CTX_free(ctx); + if (dBn) BN_clear_free(dBn); + if (pubPoint) EC_POINT_free(pubPoint); + if (group) EC_GROUP_free(group); + if (pubKeyBuf) OPENSSL_free(pubKeyBuf); + return ret; #else (void)oid; (void)qx; (void)qxLength; @@ -1362,14 +1366,22 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( BIGNUM* orderBn = NULL; BIGNUM* cofactorBn = NULL; BIGNUM* dBn = NULL; + BIGNUM* gxBn = NULL; + BIGNUM* gyBn = NULL; + EC_GROUP* group = NULL; + EC_POINT* G = NULL; + EC_POINT* pubPoint = NULL; + + const int hasPublicKey = (qx != NULL && qy != NULL); + const int hasPrivateKey = (d != NULL && dLength > 0); bld = OSSL_PARAM_BLD_new(); if (bld == NULL) goto error; const char* fieldType = (curveType == Characteristic2) - ? FieldTypeChar2 - : FieldTypePrime; + ? SN_X9_62_characteristic_two_field + : SN_X9_62_prime_field; if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, 0)) goto error; @@ -1394,15 +1406,13 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( goto error; // Generator as uncompressed point: 0x04 || gx || gy - { - int32_t genLen; - generatorBuf = EncodeUncompressedPoint(gx, gxLength, gy, gyLength, &genLen); - if (generatorBuf == NULL) - goto error; + int32_t genLen; + generatorBuf = EncodeEcPointFromCoordinates(gx, gxLength, gy, gyLength, &genLen); + if (generatorBuf == NULL) + goto error; - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_GENERATOR, generatorBuf, (size_t)genLen)) - goto error; - } + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_GENERATOR, generatorBuf, (size_t)genLen)) + goto error; orderBn = BN_bin2bn(order, orderLength, NULL); cofactorBn = BN_bin2bn(cofactor, cofactorLength, NULL); @@ -1422,7 +1432,7 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( goto error; } - if (d && dLength > 0) + if (hasPrivateKey) { dBn = BN_bin2bn(d, dLength, NULL); if (dBn == NULL) @@ -1432,97 +1442,58 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( goto error; } - if (qx && qy) + // Push public key, deriving it from the private key if unavailable. + if (hasPublicKey) { int32_t pubKeyLen; - pubKeyBuf = EncodeUncompressedPoint(qx, qxLength, qy, qyLength, &pubKeyLen); + pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, &pubKeyLen); if (pubKeyBuf == NULL) goto error; if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, (size_t)pubKeyLen)) goto error; } - else if (d && dLength > 0) + else if (hasPrivateKey) { // No public key provided, derive Q = d * G from the explicit curve parameters. - const EC_METHOD* curveMethod = CurveTypeToMethod(curveType); - if (!curveMethod) - goto error; +#if HAVE_OPENSSL_EC2M + if (curveType == Characteristic2) + { + group = EC_GROUP_new_curve_GF2m(pBn, aBn, bBn, NULL); + } + else +#endif + if (curveType != Characteristic2) + { + group = EC_GROUP_new_curve_GFp(pBn, aBn, bBn, NULL); + } - EC_GROUP* group = EC_GROUP_new(curveMethod); if (group == NULL) goto error; - if (!EC_GROUP_set_curve(group, pBn, aBn, bBn, NULL)) - { - EC_GROUP_free(group); - goto error; - } - // Set the generator - EC_POINT* G = EC_POINT_new(group); - BIGNUM* gxBn = BN_bin2bn(gx, gxLength, NULL); - BIGNUM* gyBn = BN_bin2bn(gy, gyLength, NULL); + G = EC_POINT_new(group); + gxBn = BN_bin2bn(gx, gxLength, NULL); + gyBn = BN_bin2bn(gy, gyLength, NULL); if (G == NULL || gxBn == NULL || gyBn == NULL) - { - BN_free(gxBn); - BN_free(gyBn); - EC_POINT_free(G); - EC_GROUP_free(group); goto error; - } if (!EC_POINT_set_affine_coordinates(group, G, gxBn, gyBn, NULL)) - { - BN_free(gxBn); - BN_free(gyBn); - EC_POINT_free(G); - EC_GROUP_free(group); goto error; - } - - BN_free(gxBn); - BN_free(gyBn); EC_GROUP_set_generator(group, G, orderBn, cofactorBn); - EC_POINT_free(G); // Derive Q = d * G - EC_POINT* pubPoint = EC_POINT_new(group); + pubPoint = EC_POINT_new(group); if (pubPoint == NULL || !EC_POINT_mul(group, pubPoint, dBn, NULL, NULL, NULL)) - { - EC_POINT_free(pubPoint); - EC_GROUP_free(group); goto error; - } - size_t pubKeyLen = EC_POINT_point2oct(group, pubPoint, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); - if (pubKeyLen == 0) - { - EC_POINT_free(pubPoint); - EC_GROUP_free(group); - goto error; - } - - pubKeyBuf = (uint8_t*)OPENSSL_zalloc(pubKeyLen); + size_t pubKeyLen; + pubKeyBuf = EncodeEcPointFromPoint(group, pubPoint, &pubKeyLen); if (pubKeyBuf == NULL) - { - EC_POINT_free(pubPoint); - EC_GROUP_free(group); goto error; - } - - if (EC_POINT_point2oct(group, pubPoint, POINT_CONVERSION_UNCOMPRESSED, pubKeyBuf, pubKeyLen, NULL) != pubKeyLen) - { - EC_POINT_free(pubPoint); - EC_GROUP_free(group); - goto error; - } - - EC_POINT_free(pubPoint); - EC_GROUP_free(group); if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pubKeyBuf, pubKeyLen)) goto error; @@ -1536,7 +1507,7 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( if (ctx == NULL) goto error; - if ((!qx || !qy) && (!d || dLength <= 0)) + if (!hasPublicKey && !hasPrivateKey) { // No key material — generate a new key from the domain parameters. EVP_PKEY* templateKey = NULL; @@ -1565,38 +1536,35 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( if (EVP_PKEY_fromdata_init(ctx) != 1) goto error; - int selection = (d && dLength > 0) ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; + int selection = hasPrivateKey ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; if (EVP_PKEY_fromdata(ctx, &pkey, selection, params) != 1) goto error; } - OSSL_PARAM_free(params); - OSSL_PARAM_BLD_free(bld); - EVP_PKEY_CTX_free(ctx); - OPENSSL_free(generatorBuf); - OPENSSL_free(pubKeyBuf); - BN_free(pBn); - BN_free(aBn); - BN_free(bBn); - BN_free(orderBn); - BN_free(cofactorBn); - BN_clear_free(dBn); - return pkey; + goto exit; error: - OSSL_PARAM_free(params); - OSSL_PARAM_BLD_free(bld); - EVP_PKEY_CTX_free(ctx); - OPENSSL_free(generatorBuf); - OPENSSL_free(pubKeyBuf); - BN_free(pBn); - BN_free(aBn); - BN_free(bBn); - BN_free(orderBn); - BN_free(cofactorBn); - BN_clear_free(dBn); if (pkey) EVP_PKEY_free(pkey); - return NULL; + pkey = NULL; + +exit: + if (params) OSSL_PARAM_free(params); + if (bld) OSSL_PARAM_BLD_free(bld); + if (ctx) EVP_PKEY_CTX_free(ctx); + if (generatorBuf) OPENSSL_free(generatorBuf); + if (pubKeyBuf) OPENSSL_free(pubKeyBuf); + if (pBn) BN_free(pBn); + if (aBn) BN_free(aBn); + if (bBn) BN_free(bBn); + if (orderBn) BN_free(orderBn); + if (cofactorBn) BN_free(cofactorBn); + if (dBn) BN_clear_free(dBn); + if (gxBn) BN_free(gxBn); + if (gyBn) BN_free(gyBn); + if (G) EC_POINT_free(G); + if (pubPoint) EC_POINT_free(pubPoint); + if (group) EC_GROUP_free(group); + return pkey; #else (void)curveType; (void)qx; (void)qxLength; (void)qy; (void)qyLength; From 556f5d5b9130992020b3fab001cf474e0379200d Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Fri, 24 Apr 2026 11:39:10 -0700 Subject: [PATCH 07/15] style and cleanup --- .../ECDiffieHellmanOpenSslPublicKey.cs | 17 ++- .../Cryptography/ECOpenSsl.ImportExport.cs | 4 +- .../tests/EcDiffieHellmanOpenSslTests.cs | 118 ++++++++++-------- .../tests/EcDsaOpenSslTests.cs | 78 +++++++----- .../pal_ecc_import_export.c | 33 ++++- 5 files changed, 155 insertions(+), 95 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs index 6d73a840dbbe2c..b2103473233529 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs @@ -51,12 +51,16 @@ internal bool HasExplicitEncoding { get { + ThrowIfDisposed(); + bool? result = Interop.Crypto.EvpPKeyEcHasExplicitEncoding(GetKey()); + if (result.HasValue) + { return result.Value; + } - // Pre-3.0 fallback: check via EC_KEY whether the key has a curve name. - // If it doesn't have a curve name, it's explicit. + // Fallback for EC_KEY-backed handles: check via EC_KEY. using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(GetKey())) { return !Interop.Crypto.EcKeyHasCurveName(ecKey); @@ -69,19 +73,22 @@ internal int KeySize get { ThrowIfDisposed(); + int keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(_key); + if (keySize != 0) + { return keySize; + } - // For EC_KEY-backed handles, get size through EC_KEY path. + // Fallback for EC_KEY-backed handles: get size via EC_KEY. using (SafeEcKeyHandle? ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) { if (ecKey is not null && !ecKey.IsInvalid) return Interop.Crypto.EcKeyGetSize(ecKey); } - // TODO message - throw new CryptographicException(); + throw new CryptographicException(SR.Cryptography_InvalidHandle); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs index cf5415b5a8d633..30eaff6373456c 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs @@ -187,7 +187,7 @@ public static ECParameters ExportParameters(SafeEvpPKeyHandle pkey, bool include } catch (CryptographicException) { - // EVP_PKEY param extraction may fail for EC_KEY-backed keys. + // EVP_PKEY params API is unavailable on pre-3.0 OpenSSL. // Fall back to EC_KEY export path. } @@ -207,7 +207,7 @@ public static ECParameters ExportExplicitParameters(SafeEvpPKeyHandle pkey, bool } catch (CryptographicException) { - // EVP_PKEY param extraction may fail for EC_KEY-backed keys. + // EVP_PKEY params API is unavailable on pre-3.0 OpenSSL. // Fall back to EC_KEY export path. } diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslTests.cs b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslTests.cs index 8485f4637c1715..4e4f5ed3f62cd9 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslTests.cs +++ b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslTests.cs @@ -137,14 +137,18 @@ public void CtorEvpPKeyHandle(string oid, int expectedKeySize) { int rc = Interop.Crypto.EvpPKeyGenerateByEcKeyOid(out SafeEvpPKeyHandle pkey, oid); - if (rc != 1 || pkey.IsInvalid) + if (!PlatformDetection.IsOpenSsl3) { + Assert.Equal(0, rc); pkey.Dispose(); - throw new SkipTestException("EVP_PKEY EC generation not supported"); + return; } + Assert.Equal(1, rc); + Assert.False(pkey.IsInvalid); + using (pkey) - using (var e = new ECDiffieHellmanOpenSsl(pkey)) + using (ECDiffieHellmanOpenSsl e = new ECDiffieHellmanOpenSsl(pkey)) { Assert.Equal(expectedKeySize, e.KeySize); e.Exercise(); @@ -314,18 +318,22 @@ public void EcKeyAndEvpPKeyProduceSameExport(string oid, int expectedKeySize) { Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(ecKey)); - using var ecKeyBacked = new ECDiffieHellmanOpenSsl(ecKey); - Assert.Equal(expectedKeySize, ecKeyBacked.KeySize); + using (ECDiffieHellmanOpenSsl ecKeyBacked = new ECDiffieHellmanOpenSsl(ecKey)) + { + Assert.Equal(expectedKeySize, ecKeyBacked.KeySize); - ECParameters privateParams = ecKeyBacked.ExportParameters(true); + ECParameters privateParams = ecKeyBacked.ExportParameters(true); - using ECDiffieHellman evpBacked = ECDiffieHellman.Create(privateParams); - Assert.Equal(expectedKeySize, evpBacked.KeySize); + using (ECDiffieHellman evpBacked = ECDiffieHellman.Create(privateParams)) + { + Assert.Equal(expectedKeySize, evpBacked.KeySize); - ECParameters evpPrivateParams = evpBacked.ExportParameters(true); + ECParameters evpPrivateParams = evpBacked.ExportParameters(true); - ComparePublicKey(privateParams.Q, evpPrivateParams.Q); - ComparePrivateKey(privateParams, evpPrivateParams); + ComparePublicKey(privateParams.Q, evpPrivateParams.Q); + ComparePrivateKey(privateParams, evpPrivateParams); + } + } } finally { @@ -349,23 +357,22 @@ public void EcKeyAndEvpPKeyDeriveCrossCompatible(string oid) Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(rawKey1)); Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(rawKey2)); - using var ecKeyBacked1 = new ECDiffieHellmanOpenSsl(rawKey1); - using var ecKeyBacked2 = new ECDiffieHellmanOpenSsl(rawKey2); - - using ECDiffieHellman evpBacked1 = ECDiffieHellman.Create(ecKeyBacked1.ExportParameters(true)); - using ECDiffieHellman evpBacked2 = ECDiffieHellman.Create(ecKeyBacked2.ExportParameters(true)); - - using ECDiffieHellmanPublicKey ecPub2 = ecKeyBacked2.PublicKey; - using ECDiffieHellmanPublicKey evpPub2 = evpBacked2.PublicKey; - - byte[] ecEc = ecKeyBacked1.DeriveKeyFromHash(ecPub2, HashAlgorithmName.SHA256); - byte[] ecEvp = ecKeyBacked1.DeriveKeyFromHash(evpPub2, HashAlgorithmName.SHA256); - byte[] evpEc = evpBacked1.DeriveKeyFromHash(ecPub2, HashAlgorithmName.SHA256); - byte[] evpEvp = evpBacked1.DeriveKeyFromHash(evpPub2, HashAlgorithmName.SHA256); - - Assert.Equal(ecEc, ecEvp); - Assert.Equal(ecEc, evpEc); - Assert.Equal(ecEc, evpEvp); + using (ECDiffieHellmanOpenSsl ecKeyBacked1 = new ECDiffieHellmanOpenSsl(rawKey1)) + using (ECDiffieHellmanOpenSsl ecKeyBacked2 = new ECDiffieHellmanOpenSsl(rawKey2)) + using (ECDiffieHellman evpBacked1 = ECDiffieHellman.Create(ecKeyBacked1.ExportParameters(true))) + using (ECDiffieHellman evpBacked2 = ECDiffieHellman.Create(ecKeyBacked2.ExportParameters(true))) + using (ECDiffieHellmanPublicKey ecPub2 = ecKeyBacked2.PublicKey) + using (ECDiffieHellmanPublicKey evpPub2 = evpBacked2.PublicKey) + { + byte[] ecEc = ecKeyBacked1.DeriveKeyFromHash(ecPub2, HashAlgorithmName.SHA256); + byte[] ecEvp = ecKeyBacked1.DeriveKeyFromHash(evpPub2, HashAlgorithmName.SHA256); + byte[] evpEc = evpBacked1.DeriveKeyFromHash(ecPub2, HashAlgorithmName.SHA256); + byte[] evpEvp = evpBacked1.DeriveKeyFromHash(evpPub2, HashAlgorithmName.SHA256); + + Assert.Equal(ecEc, ecEvp); + Assert.Equal(ecEc, evpEc); + Assert.Equal(ecEc, evpEvp); + } } finally { @@ -379,14 +386,18 @@ public void ExplicitCurveEcKeyAndEvpPKeyProduceSameExport() { ECCurve explicitCurve = EccTestData.GetNistP256ExplicitCurve(); - using ECDiffieHellman ecKeyBacked = ECDiffieHellman.Create(explicitCurve); - ECParameters explicitParams = ecKeyBacked.ExportExplicitParameters(true); + using (ECDiffieHellman ecKeyBacked = ECDiffieHellman.Create(explicitCurve)) + { + ECParameters explicitParams = ecKeyBacked.ExportExplicitParameters(true); - using ECDiffieHellman evpBacked = ECDiffieHellman.Create(explicitParams); - ECParameters evpExplicitParams = evpBacked.ExportExplicitParameters(true); + using (ECDiffieHellman evpBacked = ECDiffieHellman.Create(explicitParams)) + { + ECParameters evpExplicitParams = evpBacked.ExportExplicitParameters(true); - ComparePublicKey(explicitParams.Q, evpExplicitParams.Q); - ComparePrivateKey(explicitParams, evpExplicitParams); + ComparePublicKey(explicitParams.Q, evpExplicitParams.Q); + ComparePrivateKey(explicitParams, evpExplicitParams); + } + } } [Fact] @@ -394,26 +405,27 @@ public void ExplicitCurveEcKeyAndEvpPKeyDeriveCrossCompatible() { ECCurve explicitCurve = EccTestData.GetNistP256ExplicitCurve(); - using ECDiffieHellman key1 = ECDiffieHellman.Create(explicitCurve); - using ECDiffieHellman key2 = ECDiffieHellman.Create(explicitCurve); - - ECParameters key1Params = key1.ExportExplicitParameters(true); - ECParameters key2Params = key2.ExportExplicitParameters(true); - - using ECDiffieHellman key1Reimported = ECDiffieHellman.Create(key1Params); - using ECDiffieHellman key2Reimported = ECDiffieHellman.Create(key2Params); - - using ECDiffieHellmanPublicKey pub2 = key2.PublicKey; - using ECDiffieHellmanPublicKey pub2Reimported = key2Reimported.PublicKey; - - byte[] derive1 = key1.DeriveKeyFromHash(pub2, HashAlgorithmName.SHA256); - byte[] derive2 = key1.DeriveKeyFromHash(pub2Reimported, HashAlgorithmName.SHA256); - byte[] derive3 = key1Reimported.DeriveKeyFromHash(pub2, HashAlgorithmName.SHA256); - byte[] derive4 = key1Reimported.DeriveKeyFromHash(pub2Reimported, HashAlgorithmName.SHA256); + using (ECDiffieHellman key1 = ECDiffieHellman.Create(explicitCurve)) + using (ECDiffieHellman key2 = ECDiffieHellman.Create(explicitCurve)) + { + ECParameters key1Params = key1.ExportExplicitParameters(true); + ECParameters key2Params = key2.ExportExplicitParameters(true); - Assert.Equal(derive1, derive2); - Assert.Equal(derive1, derive3); - Assert.Equal(derive1, derive4); + using (ECDiffieHellman key1Reimported = ECDiffieHellman.Create(key1Params)) + using (ECDiffieHellman key2Reimported = ECDiffieHellman.Create(key2Params)) + using (ECDiffieHellmanPublicKey pub2 = key2.PublicKey) + using (ECDiffieHellmanPublicKey pub2Reimported = key2Reimported.PublicKey) + { + byte[] derive1 = key1.DeriveKeyFromHash(pub2, HashAlgorithmName.SHA256); + byte[] derive2 = key1.DeriveKeyFromHash(pub2Reimported, HashAlgorithmName.SHA256); + byte[] derive3 = key1Reimported.DeriveKeyFromHash(pub2, HashAlgorithmName.SHA256); + byte[] derive4 = key1Reimported.DeriveKeyFromHash(pub2Reimported, HashAlgorithmName.SHA256); + + Assert.Equal(derive1, derive2); + Assert.Equal(derive1, derive3); + Assert.Equal(derive1, derive4); + } + } } } } diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs index 213f120420a280..da87d0cef76de8 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs +++ b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs @@ -139,12 +139,16 @@ public void CtorEvpPKeyHandle(string oid, int expectedKeySize) { int rc = Interop.Crypto.EvpPKeyGenerateByEcKeyOid(out SafeEvpPKeyHandle pkey, oid); - if (rc != 1 || pkey.IsInvalid) + if (!PlatformDetection.IsOpenSsl3) { + Assert.Equal(0, rc); pkey.Dispose(); - throw new SkipTestException("EVP_PKEY EC generation not supported"); + return; } + Assert.Equal(1, rc); + Assert.False(pkey.IsInvalid); + using (pkey) using (ECDsaOpenSsl e = new ECDsaOpenSsl(pkey)) { @@ -333,18 +337,22 @@ public void EcKeyAndEvpPKeyProduceSameExport(string oid, int expectedKeySize) { Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(ecKey)); - using ECDsaOpenSsl ecKeyBacked = new ECDsaOpenSsl(ecKey); - Assert.Equal(expectedKeySize, ecKeyBacked.KeySize); + using (ECDsaOpenSsl ecKeyBacked = new ECDsaOpenSsl(ecKey)) + { + Assert.Equal(expectedKeySize, ecKeyBacked.KeySize); - ECParameters privateParams = ecKeyBacked.ExportParameters(true); + ECParameters privateParams = ecKeyBacked.ExportParameters(true); - using ECDsa evpBacked = ECDsa.Create(privateParams); - Assert.Equal(expectedKeySize, evpBacked.KeySize); + using (ECDsa evpBacked = ECDsa.Create(privateParams)) + { + Assert.Equal(expectedKeySize, evpBacked.KeySize); - ECParameters evpPrivateParams = evpBacked.ExportParameters(true); + ECParameters evpPrivateParams = evpBacked.ExportParameters(true); - ComparePublicKey(privateParams.Q, evpPrivateParams.Q); - ComparePrivateKey(privateParams, evpPrivateParams); + ComparePublicKey(privateParams.Q, evpPrivateParams.Q); + ComparePrivateKey(privateParams, evpPrivateParams); + } + } } finally { @@ -367,14 +375,15 @@ public void EcKeyAndEvpPKeySignVerifyCrossCompatible(string oid) { Assert.NotEqual(0, Interop.Crypto.EcKeyGenerateKey(ecKey)); - using ECDsaOpenSsl ecKeyBacked = new ECDsaOpenSsl(ecKey); - using ECDsa evpBacked = ECDsa.Create(ecKeyBacked.ExportParameters(true)); - - byte[] sig1 = ecKeyBacked.SignData(data, HashAlgorithmName.SHA256); - Assert.True(evpBacked.VerifyData(data, sig1, HashAlgorithmName.SHA256)); + using (ECDsaOpenSsl ecKeyBacked = new ECDsaOpenSsl(ecKey)) + using (ECDsa evpBacked = ECDsa.Create(ecKeyBacked.ExportParameters(true))) + { + byte[] sig1 = ecKeyBacked.SignData(data, HashAlgorithmName.SHA256); + Assert.True(evpBacked.VerifyData(data, sig1, HashAlgorithmName.SHA256)); - byte[] sig2 = evpBacked.SignData(data, HashAlgorithmName.SHA256); - Assert.True(ecKeyBacked.VerifyData(data, sig2, HashAlgorithmName.SHA256)); + byte[] sig2 = evpBacked.SignData(data, HashAlgorithmName.SHA256); + Assert.True(ecKeyBacked.VerifyData(data, sig2, HashAlgorithmName.SHA256)); + } } finally { @@ -387,14 +396,18 @@ public void ExplicitCurveEcKeyAndEvpPKeyProduceSameExport() { ECCurve explicitCurve = EccTestData.GetNistP256ExplicitCurve(); - using ECDsa ecKeyBacked = ECDsa.Create(explicitCurve); - ECParameters explicitParams = ecKeyBacked.ExportExplicitParameters(true); + using (ECDsa ecKeyBacked = ECDsa.Create(explicitCurve)) + { + ECParameters explicitParams = ecKeyBacked.ExportExplicitParameters(true); - using ECDsa evpBacked = ECDsa.Create(explicitParams); - ECParameters evpExplicitParams = evpBacked.ExportExplicitParameters(true); + using (ECDsa evpBacked = ECDsa.Create(explicitParams)) + { + ECParameters evpExplicitParams = evpBacked.ExportExplicitParameters(true); - ComparePublicKey(explicitParams.Q, evpExplicitParams.Q); - ComparePrivateKey(explicitParams, evpExplicitParams); + ComparePublicKey(explicitParams.Q, evpExplicitParams.Q); + ComparePrivateKey(explicitParams, evpExplicitParams); + } + } } [Fact] @@ -403,16 +416,19 @@ public void ExplicitCurveEcKeyAndEvpPKeySignVerifyCrossCompatible() byte[] data = ByteUtils.RepeatByte(0x42, 64); ECCurve explicitCurve = EccTestData.GetNistP256ExplicitCurve(); - using ECDsa key1 = ECDsa.Create(explicitCurve); - ECParameters explicitParams = key1.ExportExplicitParameters(true); - - using ECDsa key2 = ECDsa.Create(explicitParams); + using (ECDsa key1 = ECDsa.Create(explicitCurve)) + { + ECParameters explicitParams = key1.ExportExplicitParameters(true); - byte[] sig1 = key1.SignData(data, HashAlgorithmName.SHA256); - Assert.True(key2.VerifyData(data, sig1, HashAlgorithmName.SHA256)); + using (ECDsa key2 = ECDsa.Create(explicitParams)) + { + byte[] sig1 = key1.SignData(data, HashAlgorithmName.SHA256); + Assert.True(key2.VerifyData(data, sig1, HashAlgorithmName.SHA256)); - byte[] sig2 = key2.SignData(data, HashAlgorithmName.SHA256); - Assert.True(key1.VerifyData(data, sig2, HashAlgorithmName.SHA256)); + byte[] sig2 = key2.SignData(data, HashAlgorithmName.SHA256); + Assert.True(key1.VerifyData(data, sig2, HashAlgorithmName.SHA256)); + } + } } } } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index 55a2d311757e32..f841102ec9de69 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -502,9 +502,29 @@ int32_t CryptoNative_EvpPKeyGetEcFieldDegree(const EVP_PKEY* pkey) #endif #ifdef NEED_OPENSSL_3_0 - char fieldType[32] = {0}; - if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, sizeof(fieldType), NULL)) - return 0; + // Determine whether this is a binary field (GF(2^m)) for the degree adjustment. + // Some providers (e.g. TPM2) don't expose OSSL_PKEY_PARAM_EC_FIELD_TYPE, + // so try EC_GROUP from the curve name first, then fall back to the param. + int isChar2 = 0; + + int nid = 0; + if (CryptoNative_EvpPKeyGetEcGroupNid(pkey, &nid) && nid != NID_undef) + { + EC_GROUP* group = EC_GROUP_new_by_curve_name(nid); + if (group) + { + isChar2 = (EC_GROUP_get_field_type(group) == NID_X9_62_characteristic_two_field); + EC_GROUP_free(group); + } + } + else + { + char fieldType[32] = {0}; + if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, sizeof(fieldType), NULL)) + { + isChar2 = (strcmp(fieldType, SN_X9_62_characteristic_two_field) == 0); + } + } BIGNUM* p = NULL; if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_P, &p) || !p) @@ -513,7 +533,7 @@ int32_t CryptoNative_EvpPKeyGetEcFieldDegree(const EVP_PKEY* pkey) // For GF(2^m): p is the irreducible polynomial, degree = BN_num_bits(p) - 1. // For GF(p): degree = BN_num_bits(p). int degree = BN_num_bits(p); - if (strcmp(fieldType, SN_X9_62_characteristic_two_field) == 0) + if (isChar2) degree--; BN_free(p); @@ -962,10 +982,15 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( else { // Named curve: create group to get field type. + // curveTypeNID will be always NID_X9_62_characteristic_two_field or NID_X9_62_prime_field group = EC_GROUP_new_by_curve_name(curveTypeNID); if (!group) goto error; + // In some cases EVP_PKEY_get_field_type can return NID_undef + // and some providers seem to be ignoring OSSL_PKEY_PARAM_EC_FIELD_TYPE. + // This is specifically true for tpm2 provider. + // We can reliably get the field type from the EC_GROUP. fieldTypeNID = EC_GROUP_get_field_type(group); } From 883529f29f8271a85ccbb9444f8e9bfb082b0a2a Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Fri, 24 Apr 2026 12:06:36 -0700 Subject: [PATCH 08/15] PR comments --- .../System/Security/Cryptography/ECOpenSsl.cs | 32 ++++++++++++-- .../pal_ecc_import_export.c | 43 ++++++++++++++----- .../pal_ecc_import_export.h | 3 +- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index 28d8911d46bb5e..36cd11d73f0dd1 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -149,7 +149,13 @@ internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) if (pkey is not null) { keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(pkey); - return pkey; + + if (keySize != 0) + { + return pkey; + } + + pkey.Dispose(); } } else if (curve.IsPrime || curve.IsCharacteristic2) @@ -174,7 +180,13 @@ internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) if (pkey is not null) { keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(pkey); - return pkey; + + if (keySize != 0) + { + return pkey; + } + + pkey.Dispose(); } } @@ -200,7 +212,13 @@ internal static SafeEvpPKeyHandle ImportECKey(ECParameters parameters, out int k if (pkey is not null) { keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(pkey); - return pkey; + + if (keySize != 0) + { + return pkey; + } + + pkey.Dispose(); } } else if (parameters.Curve.IsPrime || parameters.Curve.IsCharacteristic2) @@ -224,7 +242,13 @@ internal static SafeEvpPKeyHandle ImportECKey(ECParameters parameters, out int k if (pkey is not null) { keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(pkey); - return pkey; + + if (keySize != 0) + { + return pkey; + } + + pkey.Dispose(); } } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index f841102ec9de69..d006405a671285 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -495,7 +495,7 @@ int32_t CryptoNative_EvpPKeyGetEcFieldDegree(const EVP_PKEY* pkey) return 0; #ifdef FEATURE_DISTRO_AGNOSTIC_SSL - if (!API_EXISTS(EVP_PKEY_get_bn_param)) + if (!API_EXISTS(EVP_PKEY_get_bn_param) || !API_EXISTS(EVP_PKEY_get_utf8_string_param)) { return 0; } @@ -558,7 +558,9 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( assert(cbD != NULL); #ifdef FEATURE_DISTRO_AGNOSTIC_SSL - if (!API_EXISTS(EVP_PKEY_get_bn_param)) + if (!API_EXISTS(EVP_PKEY_get_bn_param) || + !API_EXISTS(EVP_PKEY_get_octet_string_param) || + !API_EXISTS(EVP_PKEY_get_utf8_string_param)) { *cbQx = *cbQy = 0; *qx = *qy = 0; @@ -932,7 +934,9 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( #ifdef FEATURE_DISTRO_AGNOSTIC_SSL if (!API_EXISTS(EC_GROUP_get_field_type) || - !API_EXISTS(EVP_PKEY_get_octet_string_param)) + !API_EXISTS(EVP_PKEY_get_octet_string_param) || + !API_EXISTS(EVP_PKEY_get_utf8_string_param) || + !API_EXISTS(EVP_PKEY_get_bn_param)) { return 0; } @@ -1153,11 +1157,14 @@ int32_t CryptoNative_EvpPKeyGenerateByEcKeyOid( ERR_clear_error(); -#ifdef NEED_OPENSSL_3_0 - if (!API_EXISTS(EVP_PKEY_CTX_new_from_name)) +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (!API_EXISTS(EVP_PKEY_CTX_new_from_name) || !API_EXISTS(EVP_PKEY_CTX_set_group_name)) { return 0; } +#endif + +#ifdef NEED_OPENSSL_3_0 int nid = OBJ_txt2nid(oid); if (!nid) @@ -1221,11 +1228,17 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( ERR_clear_error(); -#ifdef NEED_OPENSSL_3_0 - if (!API_EXISTS(EVP_PKEY_fromdata) || !API_EXISTS(OSSL_PARAM_BLD_new)) +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (!API_EXISTS(EVP_PKEY_fromdata) || + !API_EXISTS(EVP_PKEY_fromdata_init) || + !API_EXISTS(EVP_PKEY_CTX_new_from_name) || + !API_EXISTS(OSSL_PARAM_BLD_new)) { return 0; } +#endif + +#ifdef NEED_OPENSSL_3_0 // Verify the OID is recognized before doing any work. int nid = OBJ_txt2nid(oid); @@ -1372,12 +1385,19 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( ERR_clear_error(); -#ifdef NEED_OPENSSL_3_0 - if (!API_EXISTS(EVP_PKEY_fromdata) || !API_EXISTS(OSSL_PARAM_BLD_new) || - !API_EXISTS(EVP_PKEY_generate) || !API_EXISTS(EVP_PKEY_CTX_new_from_pkey)) +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (!API_EXISTS(EVP_PKEY_fromdata) || + !API_EXISTS(EVP_PKEY_fromdata_init) || + !API_EXISTS(EVP_PKEY_CTX_new_from_name) || + !API_EXISTS(EVP_PKEY_CTX_new_from_pkey) || + !API_EXISTS(EVP_PKEY_generate) || + !API_EXISTS(OSSL_PARAM_BLD_new)) { return NULL; } +#endif + +#ifdef NEED_OPENSSL_3_0 EVP_PKEY* pkey = NULL; EVP_PKEY_CTX* ctx = NULL; @@ -1507,7 +1527,8 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( if (!EC_POINT_set_affine_coordinates(group, G, gxBn, gyBn, NULL)) goto error; - EC_GROUP_set_generator(group, G, orderBn, cofactorBn); + if (!EC_GROUP_set_generator(group, G, orderBn, cofactorBn)) + goto error; // Derive Q = d * G pubPoint = EC_POINT_new(group); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h index 45341a9de46ee7..4ee487ea65d7ea 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h @@ -98,7 +98,8 @@ PALEXPORT int32_t CryptoNative_EvpPKeyGenerateByEcKeyOid( const char* oid); /* -Returns 1 if the EVP_PKEY EC key uses explicit encoding, 0 otherwise. +Returns 1 if the EVP_PKEY EC key uses explicit encoding, 0 if it uses named curve encoding, +or -1 if the encoding could not be determined (e.g. pre-3.0 OpenSSL or unavailable API). */ PALEXPORT int32_t CryptoNative_EvpPKeyEcHasExplicitEncoding(const EVP_PKEY* pkey); From d0b37ac961d59531c145cd1d91be0ad6025b5eca Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Fri, 24 Apr 2026 12:14:41 -0700 Subject: [PATCH 09/15] validate curve --- .../Common/src/System/Security/Cryptography/ECOpenSsl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index 36cd11d73f0dd1..043bbd765586e8 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -140,6 +140,8 @@ internal static SafeEvpPKeyHandle GenerateECKey(int keySize) internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) { + curve.Validate(); + if (curve.IsNamed) { string oid = !string.IsNullOrEmpty(curve.Oid.Value) ? curve.Oid.Value : curve.Oid.FriendlyName!; From 4cf0d135c3739eae13bbf4fc006600c87e1af966 Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Fri, 24 Apr 2026 12:51:34 -0700 Subject: [PATCH 10/15] PR comments --- .../ECDiffieHellmanOpenSslPublicKey.cs | 5 +++++ .../Cryptography/ECOpenSsl.ImportExport.cs | 15 +++------------ .../pal_ecc_import_export.c | 5 ++--- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs index b2103473233529..1afd236cbe8613 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs @@ -63,6 +63,11 @@ internal bool HasExplicitEncoding // Fallback for EC_KEY-backed handles: check via EC_KEY. using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(GetKey())) { + if (ecKey is null || ecKey.IsInvalid) + { + throw new CryptographicException(SR.Cryptography_InvalidHandle); + } + return !Interop.Crypto.EcKeyHasCurveName(ecKey); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs index 30eaff6373456c..5df77b7d874b99 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs @@ -181,15 +181,10 @@ public static ECParameters ExportParameters(SafeEvpPKeyHandle pkey, bool include { CheckInvalidKey(pkey); - try + if (SafeEvpPKeyHandle.OpenSslVersion >= 0x3_00_00_00_0) { return ExportECParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); } - catch (CryptographicException) - { - // EVP_PKEY params API is unavailable on pre-3.0 OpenSSL. - // Fall back to EC_KEY export path. - } using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(pkey)) { @@ -201,15 +196,10 @@ public static ECParameters ExportExplicitParameters(SafeEvpPKeyHandle pkey, bool { CheckInvalidKey(pkey); - try + if (SafeEvpPKeyHandle.OpenSslVersion >= 0x3_00_00_00_0) { return ExportExplicitCurveParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); } - catch (CryptographicException) - { - // EVP_PKEY params API is unavailable on pre-3.0 OpenSSL. - // Fall back to EC_KEY export path. - } using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(pkey)) { @@ -232,6 +222,7 @@ private static ECParameters ExportECParametersFromEvpPKeyUsingParams(SafeEvpPKey } string? curveName = Interop.Crypto.EvpPKeyGetCurveName(pkey); + if (curveName is null) { return ExportExplicitCurveParametersFromEvpPKeyUsingParams(pkey, includePrivateParameters); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index d006405a671285..bd9e4deeeb5554 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -469,7 +469,7 @@ int32_t CryptoNative_EvpPKeyGetEcGroupNid(const EVP_PKEY *pkey, int32_t* nidName int32_t CryptoNative_EvpPKeyEcHasExplicitEncoding(const EVP_PKEY* pkey) { if (!pkey || EVP_PKEY_get_base_id(pkey) != EVP_PKEY_EC) - return 0; + return -1; #ifdef FEATURE_DISTRO_AGNOSTIC_SSL if (!API_EXISTS(EVP_PKEY_get_utf8_string_param)) @@ -985,8 +985,7 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( } else { - // Named curve: create group to get field type. - // curveTypeNID will be always NID_X9_62_characteristic_two_field or NID_X9_62_prime_field + // Named curve: create group from the curve NID to get the field type. group = EC_GROUP_new_by_curve_name(curveTypeNID); if (!group) goto error; From 49ec2ab214b69f44912d6a99f89a1052e0194ef5 Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Fri, 24 Apr 2026 13:59:51 -0700 Subject: [PATCH 11/15] more PR comments --- .../Interop.EcDsa.ImportExport.cs | 3 +- .../osslcompat_30.h | 37 +++++++++++++------ .../pal_ecc_import_export.c | 18 ++++++--- .../pal_ecc_import_export.h | 5 ++- 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs index bfc59efc9c8774..47c55fb8699f1d 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs @@ -245,7 +245,8 @@ internal static bool EvpPKeyHasCurveName(SafeEvpPKeyHandle pkey) private static partial int CryptoNative_EvpPKeyEcHasExplicitEncoding(SafeEvpPKeyHandle pkey); /// - /// Returns true if the key has explicit encoding, false if named, null if indeterminate (pre-3.0). + /// Returns true if the key has explicit encoding, false if named (or encoding unavailable), + /// null if the API is unavailable (pre-3.0) and the caller should use an alternative method. /// internal static bool? EvpPKeyEcHasExplicitEncoding(SafeEvpPKeyHandle pkey) { diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index ec8d0c43a6f314..ab0a64445f5652 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -25,17 +25,21 @@ #define OSSL_MAC_PARAM_XOF "xof" #define OSSL_MAC_PARAM_SIZE "size" -#define OSSL_PKEY_PARAM_GROUP_NAME "group" -#define OSSL_PKEY_PARAM_PRIV_KEY "priv" -#define OSSL_PKEY_PARAM_EC_PUB_X "qx" -#define OSSL_PKEY_PARAM_EC_PUB_Y "qy" -#define OSSL_PKEY_PARAM_EC_P "p" -#define OSSL_PKEY_PARAM_EC_A "a" -#define OSSL_PKEY_PARAM_EC_B "b" -#define OSSL_PKEY_PARAM_EC_GENERATOR "generator" -#define OSSL_PKEY_PARAM_EC_ORDER "order" -#define OSSL_PKEY_PARAM_EC_COFACTOR "cofactor" -#define OSSL_PKEY_PARAM_EC_SEED "seed" +#define OSSL_PKEY_PARAM_GROUP_NAME "group" +#define OSSL_PKEY_PARAM_PRIV_KEY "priv" +#define OSSL_PKEY_PARAM_EC_PUB_X "qx" +#define OSSL_PKEY_PARAM_EC_PUB_Y "qy" +#define OSSL_PKEY_PARAM_EC_P "p" +#define OSSL_PKEY_PARAM_EC_A "a" +#define OSSL_PKEY_PARAM_EC_B "b" +#define OSSL_PKEY_PARAM_EC_GENERATOR "generator" +#define OSSL_PKEY_PARAM_EC_ORDER "order" +#define OSSL_PKEY_PARAM_EC_COFACTOR "cofactor" +#define OSSL_PKEY_PARAM_EC_SEED "seed" +#define OSSL_PKEY_PARAM_EC_ENCODING "encoding" +#define OSSL_PKEY_PARAM_EC_FIELD_TYPE "field-type" + +#define EVP_PKEY_KEY_PARAMETERS 4 #define OSSL_PKEY_PARAM_RSA_N "n" #define OSSL_PKEY_PARAM_RSA_E "e" @@ -52,6 +56,7 @@ typedef struct ossl_lib_ctx_st OSSL_LIB_CTX; typedef struct ossl_param_st OSSL_PARAM; +typedef struct ossl_param_bld_st OSSL_PARAM_BLD; typedef struct ossl_provider_st OSSL_PROVIDER; typedef struct ossl_store_ctx_st OSSL_STORE_CTX; typedef struct ossl_store_info_st OSSL_STORE_INFO; @@ -98,6 +103,7 @@ EVP_PKEY_CTX *EVP_PKEY_CTX_new_from_name(OSSL_LIB_CTX *libctx, const char *name, EVP_PKEY_CTX *EVP_PKEY_CTX_new_from_pkey(OSSL_LIB_CTX *libctx, EVP_PKEY *pkey, const char *propquery); int EVP_PKEY_CTX_set_params(EVP_PKEY_CTX *ctx, const OSSL_PARAM *params); int EVP_PKEY_CTX_set_rsa_keygen_bits(EVP_PKEY_CTX* ctx, int bits); +int EVP_PKEY_CTX_set_group_name(EVP_PKEY_CTX *ctx, const char *name); int EVP_PKEY_CTX_set_rsa_oaep_md(EVP_PKEY_CTX* ctx, const EVP_MD* md); int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int pad_mode); int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX* ctx, int saltlen); @@ -119,6 +125,7 @@ int EVP_PKEY_fromdata(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey, int selection, OSSL_PARAM params[]); +int EVP_PKEY_generate(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey); int EVP_PKEY_get_base_id(const EVP_PKEY* pkey); int EVP_PKEY_get_bits(const EVP_PKEY* pkey); int EVP_PKEY_get_bn_param(const EVP_PKEY *pkey, const char *key_name, BIGNUM **bn); @@ -135,6 +142,14 @@ OSSL_PARAM OSSL_PARAM_construct_int32(const char *key, int32_t *buf); OSSL_PARAM OSSL_PARAM_construct_octet_string(const char *key, void *buf, size_t bsize); OSSL_PARAM OSSL_PARAM_construct_utf8_string(const char *key, char *buf, size_t bsize); +OSSL_PARAM_BLD *OSSL_PARAM_BLD_new(void); +void OSSL_PARAM_BLD_free(OSSL_PARAM_BLD *bld); +int OSSL_PARAM_BLD_push_utf8_string(OSSL_PARAM_BLD *bld, const char *key, const char *buf, size_t bsize); +int OSSL_PARAM_BLD_push_octet_string(OSSL_PARAM_BLD *bld, const char *key, const void *buf, size_t bsize); +int OSSL_PARAM_BLD_push_BN(OSSL_PARAM_BLD *bld, const char *key, const BIGNUM *bn); +OSSL_PARAM *OSSL_PARAM_BLD_to_param(OSSL_PARAM_BLD *bld); +void OSSL_PARAM_free(OSSL_PARAM *params); + void OSSL_LIB_CTX_free(OSSL_LIB_CTX*); OSSL_LIB_CTX* OSSL_LIB_CTX_new(void); OSSL_PROVIDER* OSSL_PROVIDER_load(OSSL_LIB_CTX*, const char* name); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index bd9e4deeeb5554..d99ca30604b6df 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -12,9 +12,11 @@ static uint8_t* EncodeEcPointFromCoordinates( const uint8_t* x, int32_t xLength, const uint8_t* y, int32_t yLength, + int32_t fieldSize, int32_t* outLength) { - int32_t coordLen = xLength > yLength ? xLength : yLength; + int32_t coordLen = fieldSize; + assert(xLength <= coordLen && yLength <= coordLen); int32_t len = 1 + 2 * coordLen; uint8_t* buf = (uint8_t*)OPENSSL_zalloc((size_t)len); if (buf == NULL) @@ -534,7 +536,7 @@ int32_t CryptoNative_EvpPKeyGetEcFieldDegree(const EVP_PKEY* pkey) // For GF(p): degree = BN_num_bits(p). int degree = BN_num_bits(p); if (isChar2) - degree--; + degree = degree > 0 ? degree - 1 : 0; BN_free(p); return degree; @@ -1283,7 +1285,9 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( if (hasPublicKey) { int32_t pubKeyLen; - pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, &pubKeyLen); + + // Coordinates are padded to field size (qxLength) by managed caller. + pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, qxLength, &pubKeyLen); if (pubKeyBuf == NULL) goto error; @@ -1449,9 +1453,9 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_B, bBn)) goto error; - // Generator as uncompressed point: 0x04 || gx || gy + // Generator as uncompressed point: 0x04 || gx || gy, padded to field size (pLength). int32_t genLen; - generatorBuf = EncodeEcPointFromCoordinates(gx, gxLength, gy, gyLength, &genLen); + generatorBuf = EncodeEcPointFromCoordinates(gx, gxLength, gy, gyLength, pLength, &genLen); if (generatorBuf == NULL) goto error; @@ -1490,7 +1494,9 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( if (hasPublicKey) { int32_t pubKeyLen; - pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, &pubKeyLen); + + // Coordinates are padded to field size (pLength) by managed caller. + pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, pLength, &pubKeyLen); if (pubKeyBuf == NULL) goto error; diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h index 4ee487ea65d7ea..1353cfe93b5739 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h @@ -98,8 +98,9 @@ PALEXPORT int32_t CryptoNative_EvpPKeyGenerateByEcKeyOid( const char* oid); /* -Returns 1 if the EVP_PKEY EC key uses explicit encoding, 0 if it uses named curve encoding, -or -1 if the encoding could not be determined (e.g. pre-3.0 OpenSSL or unavailable API). +Returns 1 if the EVP_PKEY EC key uses explicit encoding, 0 if it uses named curve encoding +or the encoding could not be read (named curve is the default), or -1 if the API is unavailable +(e.g. pre-3.0 OpenSSL) and the caller should use an alternative method. */ PALEXPORT int32_t CryptoNative_EvpPKeyEcHasExplicitEncoding(const EVP_PKEY* pkey); From 754e355ddace8a0df9d7b073c50f755f4b2412e7 Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Fri, 24 Apr 2026 16:13:05 -0700 Subject: [PATCH 12/15] fix build --- .../pal_ecc_import_export.c | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index d99ca30604b6df..b2ac42fe74215c 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -577,6 +577,10 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( BIGNUM *yBn = NULL; BIGNUM *dBn = NULL; uint8_t* pubKeyBuf = NULL; + size_t pubKeyLen = 0; + EC_GROUP* group = NULL; + EC_POINT* point = NULL; + char curveName[80] = {0}; #ifdef NEED_OPENSSL_3_0 // Ensure we have an EC key @@ -588,7 +592,6 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( // Get the public key as an encoded point (may be compressed or uncompressed). // We use OSSL_PKEY_PARAM_PUB_KEY instead of OSSL_PKEY_PARAM_EC_PUB_X/Y // because the individual X/Y components may not be materialized yet. - size_t pubKeyLen = 0; if (!EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, NULL, 0, &pubKeyLen)) goto error; @@ -601,10 +604,8 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( // Decode the encoded point (compressed or uncompressed) to extract X and Y. // Build an EC_GROUP from the key's parameters to perform the decoding. - EC_GROUP* group = NULL; // Try named curve first. - char curveName[80] = {0}; if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curveName, sizeof(curveName), NULL)) { int nid = OBJ_txt2nid(curveName); @@ -661,12 +662,10 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( goto error; } - EC_POINT* point = EC_POINT_new(group); + point = EC_POINT_new(group); if (point == NULL || !EC_POINT_oct2point(group, point, pubKeyBuf, pubKeyLen, NULL)) { - EC_POINT_free(point); - EC_GROUP_free(group); goto error; } @@ -674,26 +673,17 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( yBn = BN_new(); if (xBn == NULL || yBn == NULL) - { - EC_POINT_free(point); - EC_GROUP_free(group); goto error; - } if (!EcPointGetAffineCoordinates(group, point, xBn, yBn)) - { - EC_POINT_free(point); - EC_GROUP_free(group); goto error; - } - - EC_POINT_free(point); - EC_GROUP_free(group); *qx = xBn; - *cbQx = BN_num_bytes(xBn); + xBn = NULL; + *cbQx = BN_num_bytes(*qx); *qy = yBn; - *cbQy = BN_num_bytes(yBn); + yBn = NULL; + *cbQy = BN_num_bytes(*qy); if (includePrivate) { @@ -701,7 +691,8 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( goto error; *d = dBn; - *cbD = BN_num_bytes(dBn); + dBn = NULL; + *cbD = BN_num_bytes(*d); } else { @@ -709,9 +700,8 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( *cbD = 0; } - // success - if (pubKeyBuf) OPENSSL_free(pubKeyBuf); - return 1; + rc = 1; + goto exit; error: #else @@ -722,9 +712,14 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( *qx = *qy = 0; if (d) *d = NULL; if (cbD) *cbD = 0; + +exit: if (xBn) BN_free(xBn); if (yBn) BN_free(yBn); + if (dBn) BN_free(dBn); if (pubKeyBuf) OPENSSL_free(pubKeyBuf); + if (point) EC_POINT_free(point); + if (group) EC_GROUP_free(group); return rc; } @@ -1423,14 +1418,14 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( const int hasPublicKey = (qx != NULL && qy != NULL); const int hasPrivateKey = (d != NULL && dLength > 0); - bld = OSSL_PARAM_BLD_new(); - if (bld == NULL) - goto error; - const char* fieldType = (curveType == Characteristic2) ? SN_X9_62_characteristic_two_field : SN_X9_62_prime_field; + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) + goto error; + if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_EC_FIELD_TYPE, fieldType, 0)) goto error; From d0c06d8865f6535ee2e837f6b7cceb34bdc4a8aa Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Fri, 24 Apr 2026 16:46:42 -0700 Subject: [PATCH 13/15] fix 1.1 build --- .../pal_ecc_import_export.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index b2ac42fe74215c..0122573183f550 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -573,6 +573,8 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( #endif int rc = 0; + +#ifdef NEED_OPENSSL_3_0 BIGNUM *xBn = NULL; BIGNUM *yBn = NULL; BIGNUM *dBn = NULL; @@ -582,7 +584,6 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( EC_POINT* point = NULL; char curveName[80] = {0}; -#ifdef NEED_OPENSSL_3_0 // Ensure we have an EC key if (EVP_PKEY_get_base_id(pkey) != EVP_PKEY_EC) goto error; @@ -704,10 +705,6 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( goto exit; error: -#else - (void)pkey; - (void)includePrivate; -#endif *cbQx = *cbQy = 0; *qx = *qy = 0; if (d) *d = NULL; @@ -721,6 +718,15 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( if (point) EC_POINT_free(point); if (group) EC_GROUP_free(group); return rc; +#else + (void)pkey; + (void)includePrivate; + *cbQx = *cbQy = 0; + *qx = *qy = 0; + if (d) *d = NULL; + if (cbD) *cbD = 0; + return 0; +#endif } EC_KEY* CryptoNative_EcKeyCreateByExplicitParameters( From d7068b75d8a5a411dcfceb1ca0623936d0ddd8ff Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Fri, 24 Apr 2026 17:39:32 -0700 Subject: [PATCH 14/15] Addressing more PR comments --- .../pal_ecc_import_export.c | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index 0122573183f550..4f85af8ef2cde4 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -16,7 +16,9 @@ static uint8_t* EncodeEcPointFromCoordinates( int32_t* outLength) { int32_t coordLen = fieldSize; - assert(xLength <= coordLen && yLength <= coordLen); + if (xLength > coordLen || yLength > coordLen) + return NULL; + int32_t len = 1 + 2 * coordLen; uint8_t* buf = (uint8_t*)OPENSSL_zalloc((size_t)len); if (buf == NULL) @@ -713,7 +715,7 @@ int32_t CryptoNative_EvpPKeyGetEcKeyParameters( exit: if (xBn) BN_free(xBn); if (yBn) BN_free(yBn); - if (dBn) BN_free(dBn); + if (dBn) BN_clear_free(dBn); if (pubKeyBuf) OPENSSL_free(pubKeyBuf); if (point) EC_POINT_free(point); if (group) EC_GROUP_free(group); @@ -1264,6 +1266,7 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( BIGNUM* dBn = NULL; EC_GROUP* group = NULL; EC_POINT* pubPoint = NULL; + int32_t fieldSize = 0; const int hasPublicKey = (qx != NULL && qy != NULL); const int hasPrivateKey = (d != NULL && dLength > 0); @@ -1282,13 +1285,19 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( goto error; } + // Build an EC_GROUP to determine the field size and (if needed) derive the public key. + group = EC_GROUP_new_by_curve_name(nid); + if (group == NULL) + goto error; + + fieldSize = (EC_GROUP_get_degree(group) + 7) / 8; + // Push public key, deriving it from the private key if unavailable. if (hasPublicKey) { int32_t pubKeyLen; - // Coordinates are padded to field size (qxLength) by managed caller. - pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, qxLength, &pubKeyLen); + pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, fieldSize, &pubKeyLen); if (pubKeyBuf == NULL) goto error; @@ -1298,10 +1307,6 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( else if (hasPrivateKey) { // No public key provided, derive Q = d * G using EC_GROUP/EC_POINT (not deprecated). - group = EC_GROUP_new_by_curve_name(nid); - if (group == NULL) - goto error; - pubPoint = EC_POINT_new(group); if (pubPoint == NULL || !EC_POINT_mul(group, pubPoint, dBn, NULL, NULL, NULL)) @@ -1420,6 +1425,8 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( EC_GROUP* group = NULL; EC_POINT* G = NULL; EC_POINT* pubPoint = NULL; + int32_t fieldSize = 0; + int32_t genLen = 0; const int hasPublicKey = (qx != NULL && qy != NULL); const int hasPrivateKey = (d != NULL && dLength > 0); @@ -1454,9 +1461,10 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_B, bBn)) goto error; - // Generator as uncompressed point: 0x04 || gx || gy, padded to field size (pLength). - int32_t genLen; - generatorBuf = EncodeEcPointFromCoordinates(gx, gxLength, gy, gyLength, pLength, &genLen); + fieldSize = BN_num_bytes(pBn); + + // Generator as uncompressed point: 0x04 || gx || gy, padded to field size. + generatorBuf = EncodeEcPointFromCoordinates(gx, gxLength, gy, gyLength, fieldSize, &genLen); if (generatorBuf == NULL) goto error; @@ -1496,8 +1504,7 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( { int32_t pubKeyLen; - // Coordinates are padded to field size (pLength) by managed caller. - pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, pLength, &pubKeyLen); + pubKeyBuf = EncodeEcPointFromCoordinates(qx, qxLength, qy, qyLength, fieldSize, &pubKeyLen); if (pubKeyBuf == NULL) goto error; From a214223372f43ed46e5c5d4438e23c48cb4e9ec5 Mon Sep 17 00:00:00 2001 From: Pranav Senthilnathan Date: Mon, 27 Apr 2026 14:04:25 -0700 Subject: [PATCH 15/15] address PR comments --- .../ECDiffieHellmanTests.ImportExport.cs | 66 +++++++++++++++ .../ECDsa/ECDsaImportExport.cs | 84 +++++++++++++++++++ .../pal_ecc_import_export.c | 41 +++++++-- 3 files changed, 186 insertions(+), 5 deletions(-) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.ImportExport.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.ImportExport.cs index 27bb25e7d6674e..ff4cb7cc792238 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.ImportExport.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.ImportExport.cs @@ -510,6 +510,72 @@ private static void VerifyExplicitCurve(ECParameters parameters, ECDiffieHellman ECParameters paramSecondExport = ec.ExportExplicitParameters(curveDef.IncludePrivate); AssertEqual(parameters, paramSecondExport); } + + [Theory] + [MemberData(nameof(NistEccCdhPrimeCurveVectors))] + public static void EcdhKeyAgreement_PrimeCurve_MatchesKnownSharedSecret( + string curveName, string curveOid, + string qCavsXHex, string qCavsYHex, + string dIutHex, string qIutXHex, string qIutYHex, + string expectedZHex) + { + _ = curveName; + ECCurve curve = ECCurve.CreateFromValue(curveOid); + + using (ECDiffieHellman iut = ECDiffieHellmanFactory.Create()) + using (ECDiffieHellman cavs = ECDiffieHellmanFactory.Create()) + { + iut.ImportParameters(new ECParameters + { + Curve = curve, + D = dIutHex.HexToByteArray(), + Q = new ECPoint + { + X = qIutXHex.HexToByteArray(), + Y = qIutYHex.HexToByteArray(), + }, + }); + + cavs.ImportParameters(new ECParameters + { + Curve = curve, + Q = new ECPoint + { + X = qCavsXHex.HexToByteArray(), + Y = qCavsYHex.HexToByteArray(), + }, + }); + + byte[] derivedZ = iut.DeriveRawSecretAgreement(cavs.PublicKey); + Assert.Equal(expectedZHex.HexToByteArray(), derivedZ); + } + } + + // NIST SP 800-56A ECCCDH vectors (CAVS 14.1), P-256 + // Source: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program + public static TheoryData NistEccCdhPrimeCurveVectors => new() + { + // P-256, COUNT=0 + { + "P-256", "1.2.840.10045.3.1.7", + "700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287", + "db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac", + "7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534", + "ead218590119e8876b29146ff89ca61770c4edbbf97d38ce385ed281d8a6b230", + "28af61281fd35e2fa7002523acc85a429cb06ee6648325389f59edfce1405141", + "46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b" + }, + // P-256, COUNT=1 + { + "P-256", "1.2.840.10045.3.1.7", + "809f04289c64348c01515eb03d5ce7ac1a8cb9498f5caa50197e58d43a86a7ae", + "b29d84e811197f25eba8f5194092cb6ff440e26d4421011372461f579271cda3", + "38f65d6dce47676044d58ce5139582d568f64bb16098d179dbab07741dd5caf5", + "119f2f047902782ab0c9e27a54aff5eb9b964829ca99c06b02ddba95b0a3f6d0", + "8f52b726664cac366fc98ac7a012b2682cbd962e5acb544671d41b9445704d1d", + "057d636096cb80b67a8c038c890e887d1adfa4195e9b3ce241c8a778c59cda67" + }, + }; } #endif } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaImportExport.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaImportExport.cs index baf4d7580fd049..08fe32b17260a6 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaImportExport.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaImportExport.cs @@ -354,6 +354,90 @@ public static void ImportFromPrivateOnlyKey() } } + [ConditionalFact(typeof(ECDsaImportExportTests), nameof(CanDeriveNewPublicKey))] + public static void DerivePublicKey_Named_P256() + { + VerifyPrivateKeyDerivesPublicKey(EccTestData.GetNistP256ReferenceKey(), explicitCurve: false); + } + + [ConditionalFact(typeof(ECDsaImportExportTests), nameof(CanDeriveNewPublicKey))] + public static void DerivePublicKey_Named_P521_DiminishedCoords() + { + VerifyPrivateKeyDerivesPublicKey(EccTestData.GetNistP521DiminishedCoordsParameters(), explicitCurve: false); + } + + [ConditionalFact(typeof(ECDsaImportExportTests), nameof(CanDeriveNewPublicKey))] + public static void DerivePublicKey_Named_Sect163k1() + { + if (!ECDsaFactory.IsCurveValid(EccTestData.Sect163k1Key1.Curve.Oid)) + return; + + VerifyPrivateKeyDerivesPublicKey(EccTestData.Sect163k1Key1, explicitCurve: false); + } + + [ConditionalFact(typeof(ECDsaImportExportTests), nameof(CanDeriveNewPublicKey))] + public static void DerivePublicKey_Named_C2pnb163v1() + { + if (!ECDsaFactory.IsCurveValid(EccTestData.C2pnb163v1Key1.Curve.Oid)) + return; + + VerifyPrivateKeyDerivesPublicKey(EccTestData.C2pnb163v1Key1, explicitCurve: false); + } + + [ConditionalFact(typeof(ECDsaImportExportTests), nameof(ECExplicitCurvesSupported), nameof(CanDeriveNewPublicKey))] + public static void DerivePublicKey_Explicit_P256() + { + VerifyPrivateKeyDerivesPublicKey(EccTestData.GetNistP256ReferenceKeyExplicit(), explicitCurve: true); + } + + [ConditionalFact(typeof(ECDsaImportExportTests), nameof(ECExplicitCurvesSupported), nameof(CanDeriveNewPublicKey))] + public static void DerivePublicKey_Explicit_P521_DiminishedCoords() + { + ECParameters p521 = EccTestData.GetNistP521DiminishedCoordsParameters(); + p521.Curve = EccTestData.GetNistP521ExplicitCurve(); + VerifyPrivateKeyDerivesPublicKey(p521, explicitCurve: true); + } + + [ConditionalFact(typeof(ECDsaImportExportTests), nameof(ECExplicitCurvesSupported), nameof(CanDeriveNewPublicKey))] + public static void DerivePublicKey_Explicit_Sect163k1() + { + if (!ECDsaFactory.IsCurveValid(EccTestData.Sect163k1Key1.Curve.Oid)) + return; + + VerifyPrivateKeyDerivesPublicKey(EccTestData.Sect163k1Key1Explicit, explicitCurve: true); + } + + [ConditionalFact(typeof(ECDsaImportExportTests), nameof(ECExplicitCurvesSupported), nameof(CanDeriveNewPublicKey))] + public static void DerivePublicKey_Explicit_C2pnb163v1() + { + if (!ECDsaFactory.IsCurveValid(EccTestData.C2pnb163v1Key1.Curve.Oid)) + return; + + VerifyPrivateKeyDerivesPublicKey(EccTestData.C2pnb163v1Key1Explicit, explicitCurve: true); + } + + private static void VerifyPrivateKeyDerivesPublicKey(ECParameters knownKey, bool explicitCurve) + { + ECParameters importParams = new ECParameters + { + Curve = knownKey.Curve, + D = knownKey.D, + Q = default, + }; + + using (ECDsa ecdsa = ECDsaFactory.Create()) + { + ecdsa.ImportParameters(importParams); + + ECParameters exported = explicitCurve + ? ecdsa.ExportExplicitParameters(true) + : ecdsa.ExportParameters(true); + + Assert.Equal(knownKey.Q.X, exported.Q.X); + Assert.Equal(knownKey.Q.Y, exported.Q.Y); + } + } + [Theory] [MemberData(nameof(NamedCurves))] public static void OidPresentOnCurveMiscased(ECCurve curve) diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c index 4f85af8ef2cde4..0b7c9ee598bab0 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -1023,7 +1023,12 @@ int32_t CryptoNative_EvpPKeyGetEcCurveParameters( #if HAVE_OPENSSL_EC2M if (fieldTypeNID == NID_X9_62_characteristic_two_field) { - group = EC_GROUP_new_curve_GF2m(pBn, aBn, bBn, NULL); +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (API_EXISTS(EC_GROUP_new_curve_GF2m)) +#endif + { + group = EC_GROUP_new_curve_GF2m(pBn, aBn, bBn, NULL); + } } else #endif @@ -1236,7 +1241,13 @@ int32_t CryptoNative_EvpPKeyCreateByEcKeyParameters( if (!API_EXISTS(EVP_PKEY_fromdata) || !API_EXISTS(EVP_PKEY_fromdata_init) || !API_EXISTS(EVP_PKEY_CTX_new_from_name) || - !API_EXISTS(OSSL_PARAM_BLD_new)) + !API_EXISTS(OSSL_PARAM_BLD_new) || + !API_EXISTS(OSSL_PARAM_BLD_free) || + !API_EXISTS(OSSL_PARAM_BLD_push_utf8_string) || + !API_EXISTS(OSSL_PARAM_BLD_push_octet_string) || + !API_EXISTS(OSSL_PARAM_BLD_push_BN) || + !API_EXISTS(OSSL_PARAM_BLD_to_param) || + !API_EXISTS(OSSL_PARAM_free)) { return 0; } @@ -1400,7 +1411,13 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( !API_EXISTS(EVP_PKEY_CTX_new_from_name) || !API_EXISTS(EVP_PKEY_CTX_new_from_pkey) || !API_EXISTS(EVP_PKEY_generate) || - !API_EXISTS(OSSL_PARAM_BLD_new)) + !API_EXISTS(OSSL_PARAM_BLD_new) || + !API_EXISTS(OSSL_PARAM_BLD_free) || + !API_EXISTS(OSSL_PARAM_BLD_push_utf8_string) || + !API_EXISTS(OSSL_PARAM_BLD_push_octet_string) || + !API_EXISTS(OSSL_PARAM_BLD_push_BN) || + !API_EXISTS(OSSL_PARAM_BLD_to_param) || + !API_EXISTS(OSSL_PARAM_free)) { return NULL; } @@ -1425,6 +1442,7 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( EC_GROUP* group = NULL; EC_POINT* G = NULL; EC_POINT* pubPoint = NULL; + int32_t fieldBits = 0; int32_t fieldSize = 0; int32_t genLen = 0; @@ -1461,7 +1479,15 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_B, bBn)) goto error; - fieldSize = BN_num_bytes(pBn); + // For prime curves, BN_num_bits(pBn) is the bit-length of the prime. + // For characteristic-2 curves, p is the irreducible polynomial of degree m, + // so BN_num_bits(pBn) = m + 1. The field size (coordinate length) is ceil(m/8). + fieldBits = BN_num_bits(pBn); + + if (curveType == Characteristic2) + fieldBits--; + + fieldSize = (fieldBits + 7) / 8; // Generator as uncompressed point: 0x04 || gx || gy, padded to field size. generatorBuf = EncodeEcPointFromCoordinates(gx, gxLength, gy, gyLength, fieldSize, &genLen); @@ -1517,7 +1543,12 @@ EVP_PKEY* CryptoNative_EvpPKeyCreateByEcExplicitParameters( #if HAVE_OPENSSL_EC2M if (curveType == Characteristic2) { - group = EC_GROUP_new_curve_GF2m(pBn, aBn, bBn, NULL); +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (API_EXISTS(EC_GROUP_new_curve_GF2m)) +#endif + { + group = EC_GROUP_new_curve_GF2m(pBn, aBn, bBn, NULL); + } } else #endif