Skip to content

Prefer using non-deprecated EC OSSL APIs where possible#127190

Open
PranavSenthilnathan wants to merge 22 commits into
dotnet:mainfrom
PranavSenthilnathan:ossl-deprecations-ec
Open

Prefer using non-deprecated EC OSSL APIs where possible#127190
PranavSenthilnathan wants to merge 22 commits into
dotnet:mainfrom
PranavSenthilnathan:ossl-deprecations-ec

Conversation

@PranavSenthilnathan
Copy link
Copy Markdown
Member

@PranavSenthilnathan PranavSenthilnathan commented Apr 20, 2026

Adds new native entry points that use the modern OpenSSL 3.0 EVP_PKEY parameter and keygen APIs for EC key
generation, import, and export, avoiding the deprecated EC_KEY APIs where possible.

On pre-3.0 OpenSSL, the existing EC_KEY code paths are preserved as fallbacks. All new lightup APIs are guarded with
API_EXISTS under FEATURE_DISTRO_AGNOSTIC_SSL.

Unified EC APIs (EC_GROUP_get/set_curve, EC_POINT_get/set_affine_coordinates) are promoted to REQUIRED (available
since OpenSSL 1.1.1, which is the minimum supported version).

@PranavSenthilnathan PranavSenthilnathan self-assigned this Apr 20, 2026
Copilot AI review requested due to automatic review settings April 20, 2026 23:34
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @bartonjs, @vcsjones, @dotnet/area-system-security
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces new OpenSSL 3.0+ EVP_PKEY-based EC key generation/import paths (to avoid deprecated EC_KEY APIs where possible), with managed code updated to prefer these paths and fall back to legacy behavior when needed.

Changes:

  • Add native CryptoNative exports to generate/import EC keys via EVP_PKEY (named curves and explicit parameters).
  • Update managed ECOpenSsl/ECDH code to use the new EVP_PKEY paths first, with legacy EC_KEY fallback.
  • Extend the OpenSSL shim to light up additional OpenSSL 3.0 param-building/keygen APIs.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h Adds new native API declarations for EVP_PKEY EC key generation/import.
src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Implements EVP_PKEY-based EC keygen and fromdata import (named + explicit).
src/native/libs/System.Security.Cryptography.Native/opensslshim.h Lights up OpenSSL 3.0 param_build and related functions used by new native code.
src/native/libs/System.Security.Cryptography.Native/entrypoints.c Exposes the new native functions via the CryptoNative entrypoint table.
src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs Prefers EVP_PKEY EC keygen/import for OpenSSL 3.0 with fallback to EC_KEY.
src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs Stores/uses EVP_PKEY handles directly instead of wrapping EC_KEY.
src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs Uses EVP_PKEY curve-name detection and imports via new ECOpenSsl helpers.
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs Adds P/Invoke wrappers for the new CryptoNative EVP_PKEY EC APIs.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment on lines +141 to +147
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);
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenerateECKey(ECCurve curve, out int keySize) uses curve.Oid.Value / curve.Oid.FriendlyName! without calling curve.Validate() first. Since ECCurve is a struct, callers can pass a default/invalid named curve (e.g., CurveType = Named with Oid == null), which would currently throw a NullReferenceException instead of the expected crypto/PlatformNotSupported exception. Consider calling curve.Validate() up front (matching ECOpenSsl.GenerateKey) before trying the EVP_PKEY path/fallback.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 22, 2026 23:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 13 comments.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment on lines +141 to +158
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.EvpPKeyGetEcFieldDegree(pkey);
return pkey;
}
}
else if (curve.IsPrime || curve.IsCharacteristic2)
{
byte[] pField = curve.IsPrime ? curve.Prime! : curve.Polynomial!;

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenerateECKey(ECCurve curve, ...) now reads explicit-curve fields (Prime/Polynomial/A/B/G/Order/Cofactor) without validating the input first. This can throw NullReferenceException for malformed curves and bypass the normal curve.Validate() error behavior. Consider calling curve.Validate() at the start (matching ECOpenSsl.GenerateKey) before branching on curve.IsNamed/IsPrime/....

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 24, 2026 18:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Comment thread src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/opensslshim.h
Comment thread src/native/libs/System.Security.Cryptography.Native/opensslshim.h
#endif

return 1;
return EC_POINT_get_affine_coordinates(group, p, x, y, NULL) ? 1 : 0;
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EcPointGetAffineCoordinates now unconditionally calls EC_POINT_get_affine_coordinates, which is not available on OpenSSL < 3.0. This will break builds targeting OpenSSL 1.x. Please keep the previous GFp/GF2m branching for older OpenSSL, and only use EC_POINT_get_affine_coordinates when building/running with OpenSSL 3 (NEED_OPENSSL_3_0 + API_EXISTS).

Suggested change
return EC_POINT_get_affine_coordinates(group, p, x, y, NULL) ? 1 : 0;
#ifdef NEED_OPENSSL_3_0
if (API_EXISTS(EC_POINT_get_affine_coordinates))
return EC_POINT_get_affine_coordinates(group, p, x, y, NULL) ? 1 : 0;
#endif
const EC_METHOD* method = EC_GROUP_method_of(group);
if (method == NULL)
return 0;
switch (MethodToCurveType(method))
{
case PrimeMontgomery:
case PrimeShortWeierstrass:
return EC_POINT_get_affine_coordinates_GFp(group, p, x, y, NULL) ? 1 : 0;
#if HAVE_OPENSSL_EC2M
case Characteristic2:
if (API_EXISTS(EC_POINT_get_affine_coordinates_GF2m))
return EC_POINT_get_affine_coordinates_GF2m(group, p, x, y, NULL) ? 1 : 0;
return 0;
#endif
default:
return 0;
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're in OSSL 1.1.1.

Comment on lines +258 to +259
if (!EC_GROUP_get_curve(group, pBn, aBn, bBn, NULL))
goto error;
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CryptoNative_GetECCurveParameters switched to EC_GROUP_get_curve, which is an OpenSSL 3.x API and isn’t declared in osslcompat_30.h. This will fail to compile when building against OpenSSL < 3.0. Please keep the existing EC_GROUP_get_curve_GFp/GF2m fallback for older OpenSSL, and only call EC_GROUP_get_curve when OpenSSL 3 APIs are available.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're in OSSL 1.1.1.

Comment on lines +779 to +780
if (!EC_GROUP_set_curve(group, pBn, aBn, bBn, NULL))
goto error;
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CryptoNative_EcKeyCreateByExplicitParameters now calls EC_GROUP_set_curve / EC_POINT_set_affine_coordinates, which are OpenSSL 3.x APIs. Since this function is used for OpenSSL 1.x builds too, it needs to retain the GFp/GF2m variants (or otherwise guard these calls under NEED_OPENSSL_3_0 with fallbacks).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're in OSSL 1.1.1.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.h Outdated
Copilot AI review requested due to automatic review settings April 24, 2026 19:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment on lines +510 to +519
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);
}
}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CryptoNative_EvpPKeyGetEcFieldDegree only falls back to reading OSSL_PKEY_PARAM_EC_FIELD_TYPE when CryptoNative_EvpPKeyGetEcGroupNid fails. If the NID is available but EC_GROUP_new_by_curve_name(nid) fails (returns NULL), isChar2 stays at the default (prime-field) and the degree calculation for binary curves will be wrong. Consider adding a fallback to query OSSL_PKEY_PARAM_EC_FIELD_TYPE when group could not be created as well.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If nid is available then group creation should not fail.

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))
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the portable (FEATURE_DISTRO_AGNOSTIC_SSL) build, this function gates on OSSL_PARAM_BLD_new but then unconditionally calls other OSSL_PARAM_BLD_* and OSSL_PARAM_free symbols. Since these are LIGHTUP functions in the shim, the guard should also verify all required OSSL_PARAM_BLD_push_*, OSSL_PARAM_BLD_to_param, OSSL_PARAM_BLD_free, and OSSL_PARAM_free APIs are present to avoid calling through a missing function pointer at runtime.

Suggested change
!API_EXISTS(OSSL_PARAM_BLD_new))
!API_EXISTS(OSSL_PARAM_BLD_new) ||
!API_EXISTS(OSSL_PARAM_BLD_push_utf8_string) ||
!API_EXISTS(OSSL_PARAM_BLD_push_octet_string) ||
!API_EXISTS(OSSL_PARAM_BLD_to_param) ||
!API_EXISTS(OSSL_PARAM_BLD_free) ||
!API_EXISTS(OSSL_PARAM_free))

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_new should cover all the others too since they were shipped together.

!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))
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: the FEATURE_DISTRO_AGNOSTIC_SSL guard only checks OSSL_PARAM_BLD_new, but the implementation relies on multiple OSSL_PARAM_BLD_* functions and OSSL_PARAM_free. Please extend the API_EXISTS checks to cover every LIGHTUP symbol used in this code path so portable builds running on older OpenSSL versions don’t crash when a symbol is absent.

Suggested change
!API_EXISTS(OSSL_PARAM_BLD_new))
!API_EXISTS(OSSL_PARAM_BLD_new) ||
!API_EXISTS(OSSL_PARAM_BLD_push_BN) ||
!API_EXISTS(OSSL_PARAM_BLD_push_octet_string) ||
!API_EXISTS(OSSL_PARAM_BLD_to_param) ||
!API_EXISTS(OSSL_PARAM_BLD_free) ||
!API_EXISTS(OSSL_PARAM_free))

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_new should cover all the others too since they were shipped together.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs Outdated
Copilot AI review requested due to automatic review settings April 24, 2026 23:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment on lines 93 to 97
bool? explicitEncoding = Interop.Crypto.EvpPKeyEcHasExplicitEncoding(_key.Value);
if (explicitEncoding.HasValue)
{
// This may happen when EVP_PKEY was created by provider and getting EC_KEY is not possible.
thisIsNamed = Interop.Crypto.EvpPKeyHasCurveName(_key.Value);
thisIsNamed = !explicitEncoding.Value;
}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvpPKeyEcHasExplicitEncoding returns false both for named-curve encoding and when the encoding cannot be read (native returns 0 for either case). The current logic treats false as "named" (thisIsNamed = !explicitEncoding.Value) and similarly for otherIsNamed, which can misclassify explicit keys from providers that don't expose OSSL_PKEY_PARAM_EC_ENCODING. Consider disambiguating by also checking whether the key has a group name (e.g., Interop.Crypto.EvpPKeyHasCurveName) when the encoding result is false, or change the native API to return a distinct value for "unavailable/unknown".

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not being able to read the encoding means it's named.

Copilot AI review requested due to automatic review settings April 25, 2026 00:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
@PranavSenthilnathan PranavSenthilnathan changed the title [WIP] Prefer using non-deprecated EC OSSL APIs where possible Prefer using non-deprecated EC OSSL APIs where possible Apr 27, 2026
Copy link
Copy Markdown
Member

@bartonjs bartonjs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have most of the native code to review, but here is the feedback so far.

[InlineData(ECDSA_P256_OID_VALUE)]
[InlineData(ECDSA_P384_OID_VALUE)]
[InlineData(ECDSA_P521_OID_VALUE)]
public void EcKeyAndEvpPKeySignVerifyCrossCompatible(string oid)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure about any of the "compare EC_KEY with EVP_PKEY" tests. What we really want is "does the EC_KEY ctor work?", given that we already have "do the non-interop paths work" (and maybe we should have more "here's an EVP_PKEY" tests, but I don't know that we care to smash them off each other)

That feels like

  • Named, explicit-prime, explicit-char2 exports work as expected.
  • ECDH
    • From-EC_KEY works as the left side and right side of a DeriveKey
    • From-EC_KEY(public only) fails as the left side of DeriveKey
  • ECDSA
    • From-EC_KEY(public only) throws something sensible from Sign
    • From-EC_KEY(public only) verifies a known signature
    • From-EC_KEY(public private) signs a thing that platform-import-this-key can verify.

Maybe what I've seen is just those 8 tests, but it feels like more than that.

This would, of course, be easier if the ECDH/ECDSA tests were written more like the MLDSA tests... we'd just extend the KAT-verifying base class (which also would check things like signing with a public key) to do a mode where it imports the key via EC_KEY instead of using the platform public "figure it out, so probably EVP_PKEY" approach. (That's not "go rewrite the tests for this", but rather "a musing out loud from a hindsight perspective" thing)

Comment thread src/native/libs/System.Security.Cryptography.Native/opensslshim.h
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Copilot AI review requested due to automatic review settings May 15, 2026 15:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 10 comments.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 15, 2026 15:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslTests.cs Outdated
Copilot AI review requested due to automatic review settings May 15, 2026 19:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Comment on lines +73 to +76
using (SafeEcKeyHandle? ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key))
{
if (ecKey is not null && !ecKey.IsInvalid)
return Interop.Crypto.EcKeyGetSize(ecKey);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this sensibly be folded into EvpPKeyGetEcFieldDegree, or the two of them unified in some other "hidden in the shim" single call?

My expected ideal state is the only time we talk about EC_KEY at all in the managed layer would be in the ctors that took EC_KEY* as IntPtr.

We should be able to just fully delete SafeEcKeyHandle, I think?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unified the calls into a single native call. We still need SafeEcKeyHandle for export, but once that's gone, we should be able to remove it.

Comment thread src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs
Comment thread src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs
CheckInvalidKey(pkey);

using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(pkey))
if (SafeEvpPKeyHandle.OpenSslVersion >= 0x3_00_00_00_0)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. This might be the same answer as to my other "can we just delete SafeEcKeyHandle at this point?"

// Uses EC_POINT_set_affine_coordinates + EC_POINT_point2oct so OpenSSL handles
// field-size-aware serialization (tolerates leading-zero-padded inputs).
// Returns an OPENSSL_zalloc'd buffer (caller must OPENSSL_free), or NULL on failure.
static uint8_t* EncodeEcPointFromCoordinates(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main value of this seems to be in supporting xLength != yLength, but all of our API requires that import-inputs match (enforced by ECParameters.Validate() and ECCurve.Validate(), I believe?).

If we need the points to be in the uncompressed public key (04) format, would it be better to just do that in managed code before calling down?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tl;dr: The main purpose of this method is to produce coordinates with length equal to the bit-length of the curve's group order.

Full explanation: The issue with manually encoding to uncompressed point is that even if x and y are both the same length, they are both padded with an extra leading zero in some scenarios. Specifically, the following test fails:

System.Security.Cryptography.EcDsa.Tests.ECDsaImportExportTests.TestNamedCurves(curveDef: CurveDef Named:(, wap-wsg-idm-ecid-wtls7))

Apparently (relying on AI since I don't know enough number theory to verify it) the group order bit-length of that curve is 161 bits but the prime bit-length is only 160 bits. EvpPKeyBits gives the group order bit-length and we force x and y to be padded to that length:

But the problem is that the point in uncompressed format needs to have prime bit-length coordinates instead. So this function just converts the coordinates to bignum, and asks OSSL to encode that correctly internally (handling the size padding/truncation itself).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, then I wonder if it's OpenSSL, or us, that's is encoding the parameters wrongly... I wonder if our SPKI/PKCS8 exports for that curve don't interoperate well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say OSSL is encoding it wrongly. Interestingly, OSSL also defines that curve wrong, since wap-wsg-idm-ecid-wtls7 is supposed to alias secp160r1, but OSSL made it alias secp160r2 by mistake. And since it took 16 years before a bugrep came in, they decided Won't Fix.

Anyways, OSSL's definition uses secp160r2, which has a P value of 2^160 - 2^32 - 2^14 - 2^12 - 2^9 - 2^8 - 2^7 - 2^3 - 2^2 - 1, or FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFAC73. (https://www.secg.org/SEC2-Ver-1.0.pdf 2.4.3 or https://github.com/openssl/openssl/blob/d099e33e5733bb9d3975fc4f3ac4a85b6ed1a4cb/crypto/ec/ec_curve.c#L558-L584).

In https://www.secg.org/SEC1-Ver-1.0.pdf, 2.3.3, EllipticCurvePoint-to-OctetString Conversion.:

  1. If P = (xP ; yP ) != O and point compression is not being used, proceed as follows:
    3.1. Convert the field element xP to an octet string X of length ceil(log2(q)) / 8 octets using the conversion routine specified in Section 2.3.5.
    3.2. Convert the field element yP to an octet string Y of length ceil(log2(q)) / 8 octets using the conversion routine specified in Section 2.3.5.
    3.3. Output M = 0x04 || X || Y

The encoding routine calls it Fq so it's generic over the char-2 curves and the prime curves. For prime curves, q == p, so log2 of "2^160 minus a bit" is 159.999x, ceiling of that is 160.

Looking at the (default) implementation of EC_POINT_point2oct for GFp (which I believe this curve will use), it uses BN_num_bytes on the field (p), so our uncompressed point encoding should match (phew?)... so I'm not sure what would be failing. Or what nonsense the AI is hallucinating. (The only thing that exports by the encoding size of the order is d -- which ECParameters.Validate() handles for explicit curves, but looks like named curves it assumes the field/X size... sorry secp160r1/secp160r2)

$ openssl ecparam -name wap-wsg-idm-ecid-wtls7 -genkey | openssl ec -text
read EC key
Private-Key: (161 bit)
priv:
    00:00:43:e6:60:40:a0:c5:d2:69:86:b6:af:f0:63:
    25:03:58:bf:5f:6d
pub:
    04:1b:78:c0:a0:74:eb:a4:9b:2a:38:68:aa:d6:bc:
    8f:de:e8:31:7d:8e:13:5b:4f:68:5b:ca:79:7c:ce:
    e2:06:b9:f8:36:03:86:56:10:e0:b6
ASN1 OID: wap-wsg-idm-ecid-wtls7
writing EC key
-----BEGIN EC PRIVATE KEY-----
MFECAQEEFQAAQ+ZgQKDF0mmGtq/wYyUDWL9fbaAHBgVnKwEEB6EsAyoABBt4wKB0
66SbKjhoqta8j97oMX2OE1tPaFvKeXzO4ga5+DYDhlYQ4LY=
-----END EC PRIVATE KEY-----
$ $ openssl asn1parse -dump
-----BEGIN EC PRIVATE KEY-----
MFECAQEEFQAAQ+ZgQKDF0mmGtq/wYyUDWL9fbaAHBgVnKwEEB6EsAyoABBt4wKB0
66SbKjhoqta8j97oMX2OE1tPaFvKeXzO4ga5+DYDhlYQ4LY=
-----END EC PRIVATE KEY-----

    0:d=0  hl=2 l=  81 cons: SEQUENCE
    2:d=1  hl=2 l=   1 prim: INTEGER           :01
    5:d=1  hl=2 l=  21 prim: OCTET STRING
      0000 - 00 00 43 e6 60 40 a0 c5-d2 69 86 b6 af f0 63 25   ..C.`@...i....c%
      0010 - 03 58 bf 5f 6d                                    .X._m
   28:d=1  hl=2 l=   7 cons: cont [ 0 ]
   30:d=2  hl=2 l=   5 prim: OBJECT            :wap-wsg-idm-ecid-wtls7
   37:d=1  hl=2 l=  44 cons: cont [ 1 ]
   39:d=2  hl=2 l=  42 prim: BIT STRING
      0000 - 00 04 1b 78 c0 a0 74 eb-a4 9b 2a 38 68 aa d6 bc   ...x..t...*8h...
      0010 - 8f de e8 31 7d 8e 13 5b-4f 68 5b ca 79 7c ce e2   ...1}..[Oh[.y|..
      0020 - 06 b9 f8 36 03 86 56 10-e0 b6                     ...6..V...

the public key is always 42 content bytes (the BIT STRING 00 unused bits, then the uncompressed point 04, then 20 bytes for x, and 20 bytes for y)... and I would expect that to come from the same routine you're calling here.

After changing the curve OID to secp160r2 (so it would potentially interop), I see that we can't import the p8 blob, because the public key isn't twice the private key plus 1... so, yeah, we don't work very well on secp160r1/secp160r2 since their order exceeds p (which, apparently, it can..., because of that whole thing where each x has two y values, but not all of them are reachable from G).

-----BEGIN EC PRIVATE KEY-----
MFECAQEEFQAAQ+ZgQKDF0mmGtq/wYyUDWL9fbaAHBgUrgQQAHqEsAyoABBt4wKB0
66SbKjhoqta8j97oMX2OE1tPaFvKeXzO4ga5+DYDhlYQ4LY=
-----END EC PRIVATE KEY-----

Anyways, the long and the sort of it is... I'm confused. If we're failing on that curve, maybe check that we're encoding the public part right, and not making a mistake by basing it on the size of d, which would make us export a buffer too big.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we don't do secp160r2 right on Windows, either. We always export an extra 0x00 on the right of X/Y instead of the left:

using ECDsa key = ECDsa.Create(ECCurve.CreateFromFriendlyName("secp160r2"));
ECParameters ecParameters = key.ExportParameters(true);
Console.WriteLine($"D: {Convert.ToHexString(ecParameters.D)} ({ecParameters.D.Length} bytes)");
Console.WriteLine($"X: {Convert.ToHexString(ecParameters.Q.X)}");
ecParameters.Validate();
Console.WriteLine($"Y: {Convert.ToHexString(ecParameters.Q.Y)}");

yields

D: 00F57EE1271B3FB45A090CE7C51710418D7DDB1F19 (21 bytes)
X: 139AD3350F777B5497821F099979BB412FC245CD00
Y: C1C8643C97132A08C874D4E0EB33996BB102186C00

I guess I'll open an issue 😄

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing that exports by the encoding size of the order is d

The public key also uses the same size for x and y:

parameters.Q = new ECPoint
{
X = Crypto.ExtractBignum(qx_bn, cbKey),
Y = Crypto.ExtractBignum(qy_bn, cbKey)
};
parameters.D = d_cb == 0 ? null : Crypto.ExtractBignum(d_bn, cbKey);

That would be the key size which is the bit-length of the order. This isn't "incorrect" though - we can still parse those correctly into bignums. They just need to be fixed up before using in other encodings like uncompressed point format.

Either way, even if we change the code to export a better encoding, we'd probably still want to support importing the current values for backcompat.

Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native/pal_ecc_import_export.c Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants