Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9539d0c
Add ECDsa support in X509SecurityKey
joegoldman2 Oct 21, 2023
3ef0896
Merge branch 'dev' into fix/1943
joegoldman2 Dec 5, 2023
4e9428d
Merge branch 'dev' into fix/1943
joegoldman2 Dec 31, 2023
d86cfe1
Merge branch 'dev' into fix/1943
joegoldman2 Feb 24, 2024
9380183
Merge branch 'dev' into fix/1943
joegoldman2 Mar 18, 2024
ca0c7a2
Merge branch 'dev' into fix/1943
joegoldman2 Apr 5, 2024
0d3761f
Merge branch 'dev' into fix/1943
joegoldman2 Apr 12, 2024
7a5b326
Merge branch 'dev' into fix/1943
joegoldman2 Apr 19, 2024
adec70c
Merge branch 'dev' into fix/1943
joegoldman2 May 7, 2024
ededf6c
Merge branch 'dev' into fix/1943
joegoldman2 Jun 15, 2024
6879ff5
Merge branch 'dev' into fix/1943
joegoldman2 Jun 29, 2024
47d85e4
Add unit test
joegoldman2 Jul 2, 2024
4f30088
Add support for ECDsa in JsonWebKeyConverter.ConvertFromX509SecurityKey
joegoldman2 Jul 2, 2024
197831d
Update unit test
joegoldman2 Jul 2, 2024
6676f8c
Remove use of self-signed certificate
joegoldman2 Jul 3, 2024
4fbfb22
Change order for validation
joegoldman2 Jul 3, 2024
b920682
Add condition for .NET 4.7.2 or NET
joegoldman2 Jul 3, 2024
5b9b31b
Merge branch 'dev' into fix/1943
joegoldman2 Aug 17, 2024
9ea0c2d
Fix unit tests
joegoldman2 Aug 17, 2024
d47c2e1
Merge branch 'dev' into fix/1943
joegoldman2 Sep 29, 2024
304a086
Merge branch 'dev' into fix/1943
joegoldman2 Oct 5, 2024
d736b99
Merge branch 'dev' into fix/1943
joegoldman2 Oct 28, 2024
02e655a
Merge branch 'dev' into fix/1943
joegoldman2 May 24, 2025
4fffbfb
Merge branch 'dev' into fix/1943
joegoldman2 Jun 2, 2025
f3abe6a
Merge branch 'dev' into fix/1943
joegoldman2 Sep 26, 2025
e3bc837
Use OIDs to determine certificate key types
joegoldman2 Sep 26, 2025
c2feef5
Update preprocessor directives
joegoldman2 Sep 27, 2025
06ae31e
Address feedbacks from review
joegoldman2 Sep 29, 2025
b6931fe
Merge branch 'dev' into fix/1943
joegoldman2 Oct 6, 2025
c50fad6
Merge branch 'dev' into fix/1943
keegan-caruso Oct 7, 2025
59e131b
Merge branch 'dev' into fix/1943
keegan-caruso Oct 7, 2025
601fbbe
Merge branch 'dev' into fix/1943
keegan-caruso Oct 9, 2025
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
55 changes: 40 additions & 15 deletions src/Microsoft.IdentityModel.Tokens/JsonWebKeyConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static JsonWebKey ConvertFromSecurityKey(SecurityKey key)
return ConvertFromSymmetricSecurityKey(symmetricKey);
else if (key is X509SecurityKey x509Key)
return ConvertFromX509SecurityKey(x509Key);
#if NET472 || NETSTANDARD2_0 || NET6_0_OR_GREATER
#if NET472_OR_GREATER || NETSTANDARD2_0 || NET
else if (key is ECDsaSecurityKey ecdsaSecurityKey)
return ConvertFromECDsaSecurityKey(ecdsaSecurityKey);
#endif
Expand Down Expand Up @@ -97,9 +97,16 @@ public static JsonWebKey ConvertFromX509SecurityKey(X509SecurityKey key)
if (key == null)
throw LogHelper.LogArgumentNullException(nameof(key));

var kty = key.PublicKey switch
{
RSA => JsonWebAlgorithmsKeyTypes.RSA,
ECDsa => JsonWebAlgorithmsKeyTypes.EllipticCurve,
_ => throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10674, LogHelper.MarkAsNonPII(key.GetType().FullName))))
};

var jsonWebKey = new JsonWebKey
{
Kty = JsonWebAlgorithmsKeyTypes.RSA,
Kty = kty,
Kid = key.KeyId,
X5t = key.X5t,
ConvertedSecurityKey = key
Expand All @@ -116,26 +123,44 @@ public static JsonWebKey ConvertFromX509SecurityKey(X509SecurityKey key)
/// </summary>
/// <param name="key">a <see cref="X509SecurityKey"/> to convert.</param>
/// <param name="representAsRsaKey">
/// <c>true</c> to represent the <paramref name="key"/> as an <see cref="RsaSecurityKey"/>,
/// <c>true</c> to represent the <paramref name="key"/> as an <see cref="RsaSecurityKey"/>,
/// <c>false</c> to represent the <paramref name="key"/> as an <see cref="X509SecurityKey"/>, using the "x5c" parameter.
/// </param>
/// <returns>a <see cref="JsonWebKey"/>.</returns>
/// <exception cref="ArgumentNullException">if <paramref name="key"/>is null.</exception>
public static JsonWebKey ConvertFromX509SecurityKey(X509SecurityKey key, bool representAsRsaKey)
{
if (!representAsRsaKey)
return ConvertFromX509SecurityKey(key);

if (key == null)
throw LogHelper.LogArgumentNullException(nameof(key));

RSA rsaKey;
if (!representAsRsaKey)
return ConvertFromX509SecurityKey(key);

if (key.PrivateKeyStatus == PrivateKeyStatus.Exists)
rsaKey = key.PrivateKey as RSA;
else
rsaKey = key.PublicKey as RSA;
{
if (key.PrivateKey is RSA rsaPrivateKey)
{
return ConvertFromRSASecurityKey(new RsaSecurityKey(rsaPrivateKey) { KeyId = key.KeyId });
}
#if NET472_OR_GREATER || NETSTANDARD2_0 || NET
else if (key.PrivateKey is ECDsa ecdsaPrivateKey)
{
return ConvertFromECDsaSecurityKey(new ECDsaSecurityKey(ecdsaPrivateKey) { KeyId = key.KeyId });
}
#endif
}
else if (key.PublicKey is RSA rsaPublicKey)
{
return ConvertFromRSASecurityKey(new RsaSecurityKey(rsaPublicKey) { KeyId = key.KeyId });
}
#if NET472_OR_GREATER || NETSTANDARD2_0 || NET
else if (key.PublicKey is ECDsa ecdsaPublicKey)
{
return ConvertFromECDsaSecurityKey(new ECDsaSecurityKey(ecdsaPublicKey) { KeyId = key.KeyId });
}
#endif

return ConvertFromRSASecurityKey(new RsaSecurityKey(rsaKey) { KeyId = key.KeyId });
throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10674, LogHelper.MarkAsNonPII(key.GetType().FullName))));
}

/// <summary>
Expand All @@ -158,7 +183,7 @@ public static JsonWebKey ConvertFromSymmetricSecurityKey(SymmetricSecurityKey ke
};
}

#if NET472 || NETSTANDARD2_0 || NET6_0_OR_GREATER
#if NET472_OR_GREATER || NETSTANDARD2_0 || NET
/// <summary>
/// Converts a <see cref="ECDsaSecurityKey"/> into a <see cref="JsonWebKey"/>
/// </summary>
Expand All @@ -167,15 +192,15 @@ public static JsonWebKey ConvertFromSymmetricSecurityKey(SymmetricSecurityKey ke
/// <exception cref="ArgumentNullException">if <paramref name="key"/>is null.</exception>
public static JsonWebKey ConvertFromECDsaSecurityKey(ECDsaSecurityKey key)
{
if (!ECDsaAdapter.SupportsECParameters())
throw LogHelper.LogExceptionMessage(new PlatformNotSupportedException(LogMessages.IDX10695));

if (key == null)
throw LogHelper.LogArgumentNullException(nameof(key));

if (key.ECDsa == null)
throw LogHelper.LogArgumentNullException(nameof(key.ECDsa));

if (!ECDsaAdapter.SupportsECParameters())
throw LogHelper.LogExceptionMessage(new PlatformNotSupportedException(LogMessages.IDX10695));

ECParameters parameters;
try
{
Expand Down
57 changes: 43 additions & 14 deletions src/Microsoft.IdentityModel.Tokens/X509SecurityKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ namespace Microsoft.IdentityModel.Tokens
/// </summary>
public class X509SecurityKey : AsymmetricSecurityKey
{
// OID for RSA encryption
// <see href="https://www.ietf.org/rfc/rfc3447"/> and <see href="https://www.ietf.org/rfc/rfc5698"/>
const string RSAOid = "1.2.840.113549.1.1.1";

// OID for ECDSA
// <see href="https://datatracker.ietf.org/doc/html/rfc3279#section-2.3.5"/> and <see href="https://datatracker.ietf.org/doc/html/rfc5480"/>
const string ECDsaOid = "1.2.840.10045.2.1";

AsymmetricAlgorithm _privateKey;
bool _privateKeyAvailabilityDetermined;
AsymmetricAlgorithm _publicKey;
#if NET9_0_OR_GREATER
Lock _thisLock = new();
Expand Down Expand Up @@ -48,7 +55,7 @@ public X509SecurityKey(X509Certificate2 certificate)
/// Instantiates a <see cref="X509SecurityKey"/> using a <see cref="X509Certificate2"/>.
/// </summary>
/// <param name="certificate">The <see cref="X509Certificate2"/> to use.</param>
/// <param name="keyId">The value to set for the KeyId</param>
/// <param name="keyId">The value to set for the KeyId.</param>
/// <exception cref="ArgumentNullException">if <paramref name="certificate"/> is null.</exception>
/// <exception cref="ArgumentNullException">if <paramref name="keyId"/> is null or empty.</exception>
public X509SecurityKey(X509Certificate2 certificate, string keyId)
Expand Down Expand Up @@ -78,15 +85,25 @@ public AsymmetricAlgorithm PrivateKey
{
get
{
if (!_privateKeyAvailabilityDetermined)
if (_privateKey == null)
{
lock (ThisLock)
{
if (!_privateKeyAvailabilityDetermined)
if (_privateKey == null)
{
_privateKey = RSACertificateExtensions.GetRSAPrivateKey(Certificate);

_privateKeyAvailabilityDetermined = true;
switch (Certificate.PublicKey.Oid.Value)
{
case RSAOid:
{
_privateKey = Certificate.GetRSAPrivateKey();
break;
}
case ECDsaOid:
{
_privateKey = Certificate.GetECDsaPrivateKey();
break;
}
}
}
}
}
Expand All @@ -108,7 +125,19 @@ public AsymmetricAlgorithm PublicKey
{
if (_publicKey == null)
{
_publicKey = RSACertificateExtensions.GetRSAPublicKey(Certificate);
switch (Certificate.PublicKey.Oid.Value)
{
case RSAOid:
{
_publicKey = Certificate.GetRSAPublicKey();
break;
}
case ECDsaOid:
{
_publicKey = Certificate.GetECDsaPublicKey();
break;
}
}
}
}
}
Expand All @@ -128,10 +157,10 @@ object ThisLock
/// Gets a bool indicating if a private key exists.
/// </summary>
/// <return>true if it has a private key; otherwise, false.</return>
[System.Obsolete("HasPrivateKey method is deprecated, please use PrivateKeyStatus.")]
[Obsolete("HasPrivateKey method is deprecated, please use PrivateKeyStatus.")]
public override bool HasPrivateKey
{
get { return (PrivateKey != null); }
get { return PrivateKey != null; }
}

/// <summary>
Expand Down Expand Up @@ -161,20 +190,20 @@ public X509Certificate2 Certificate
/// Determines whether the <see cref="X509SecurityKey"/> can compute a JWK thumbprint.
/// </summary>
/// <returns><c>true</c> if JWK thumbprint can be computed; otherwise, <c>false</c>.</returns>
/// <remarks>https://datatracker.ietf.org/doc/html/rfc7638</remarks>
/// <remarks>See: <see href="https://datatracker.ietf.org/doc/html/rfc7638"/></remarks>
public override bool CanComputeJwkThumbprint()
{
return (PublicKey as RSA) != null ? true : false;
return PublicKey is RSA || PublicKey is ECDsa;
}

/// <summary>
/// Computes a sha256 hash over the <see cref="X509SecurityKey"/>.
/// </summary>
/// <returns>A JWK thumbprint.</returns>
/// <remarks>https://datatracker.ietf.org/doc/html/rfc7638</remarks>
/// <remarks>See: <see href="https://datatracker.ietf.org/doc/html/rfc7638"/></remarks>
public override byte[] ComputeJwkThumbprint()
{
return new RsaSecurityKey(PublicKey as RSA).ComputeJwkThumbprint();
return PublicKey is RSA ? new RsaSecurityKey(PublicKey as RSA).ComputeJwkThumbprint() : new ECDsaSecurityKey(PublicKey as ECDsa).ComputeJwkThumbprint();
}

/// <summary>
Expand Down
Loading
Loading