Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment on lines +109 to +111
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.

For the internal calls, do we need both qx and qxLength? Can this just be ReadOnlySpan so they're tracked together? (Or, if the array-ness is useful. does the caller always pass array.Length? If so, the internal call can take that away)

{
SafeEvpPKeyHandle pkey;
int rc = EvpPKeyCreateByEcKeyParameters(out pkey, oid, qx, qxLength, qy, qyLength, d, dLength);

if (rc == -1)
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.

In our shim, -1 usually means "the caller (this code) did something nonsensical". Communicating a "normal" error should use something else, like 2. (If this is exactly matching what we did with EC_KEY, then, well... it could be better)

{
pkey.Dispose();
ErrClearError();
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, oid));
}

if (rc != 1 || pkey.IsInvalid)
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.

Why does it make sense that we'd get not-success, but just clear the error queue and return null? If this code is right, it probably needs some comments.

{
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);
Comment thread
PranavSenthilnathan marked this conversation as resolved.

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(
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.

maybe you mean by EcCurveOid? "EcKey", especially in this file, means EC_KEY

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);

Expand All @@ -104,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;
}

/// <summary>
Expand All @@ -122,7 +236,35 @@ 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);

/// <summary>
/// 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.
/// </summary>
internal static bool? EvpPKeyEcHasExplicitEncoding(SafeEvpPKeyHandle pkey)
{
int result = CryptoNative_EvpPKeyEcHasExplicitEncoding(pkey);
return result switch
{
1 => true,
0 => false,
_ => null,
};
}

[LibraryImport(Libraries.CryptoNative)]
private static partial int CryptoNative_EvpPKeyGetEcFieldDegree(SafeEvpPKeyHandle pkey);

internal static int EvpPKeyGetEcFieldDegree(SafeEvpPKeyHandle pkey)
{
return CryptoNative_EvpPKeyGetEcFieldDegree(pkey);
}

[LibraryImport(Libraries.CryptoNative)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,19 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa
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)
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;
}
Comment on lines +93 to 97
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.

else
{
thisIsNamed = Interop.Crypto.EcKeyHasCurveName(ecKey);
// Pre-3.0 fallback: check via EC_KEY.
using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value))
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.

Is this always what the fallback code will look like? Does it exist in more than one place? If yes+yes, move it into EvpPKeyEcHasExplicitEncoding and get rid of the nullable bool.

{
thisIsNamed = Interop.Crypto.EcKeyHasCurveName(ecKey);
}
}
}

Expand All @@ -118,7 +120,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;
Expand All @@ -143,10 +145,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
{
Expand All @@ -155,11 +154,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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Security.Cryptography
{
internal sealed class ECDiffieHellmanOpenSslPublicKey : ECDiffieHellmanPublicKey
{
private ECOpenSsl? _key;
private SafeEvpPKeyHandle? _key;

internal ECDiffieHellmanOpenSslPublicKey(SafeEvpPKeyHandle pkeyHandle)
{
Expand All @@ -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.
Expand All @@ -60,14 +45,55 @@ 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 bool HasExplicitEncoding
{
get
{
ThrowIfDisposed();

bool? result = Interop.Crypto.EvpPKeyEcHasExplicitEncoding(GetKey());

if (result.HasValue)
{
return result.Value;
}

// Fallback for EC_KEY-backed handles: check via EC_KEY.
using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(GetKey()))
{
Comment thread
PranavSenthilnathan marked this conversation as resolved.
if (ecKey is null || ecKey.IsInvalid)
{
throw new CryptographicException(SR.Cryptography_InvalidHandle);
}

return !Interop.Crypto.EcKeyHasCurveName(ecKey);
}
}
}

internal int KeySize
{
get
{
ThrowIfDisposed();
return _key.KeySize;

int keySize = Interop.Crypto.EvpPKeyGetEcFieldDegree(_key);

if (keySize != 0)
{
return keySize;
}

// 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);
Comment thread
PranavSenthilnathan marked this conversation as resolved.
}

throw new CryptographicException(SR.Cryptography_InvalidHandle);
}
}

Expand All @@ -84,8 +110,7 @@ protected override void Dispose(bool disposing)

internal SafeEvpPKeyHandle DuplicateKeyHandle()
{
SafeEcKeyHandle currentKey = GetKey();
return Interop.Crypto.CreateEvpPkeyFromEcKey(currentKey);
return GetKey().DuplicateHandle();
}

[MemberNotNull(nameof(_key))]
Expand All @@ -94,10 +119,10 @@ private void ThrowIfDisposed()
ObjectDisposedException.ThrowIf(_key is null, this);
}

private SafeEcKeyHandle GetKey()
private SafeEvpPKeyHandle GetKey()
{
ThrowIfDisposed();
return _key.Value;
return _key;
}
}
}
Loading
Loading