(feature): jwk std impementation and props naming
This commit is contained in:
parent
e619cf276c
commit
8100c206bc
@ -10,16 +10,16 @@ public class JwkGeneratorTests
|
|||||||
var result = JwkGenerator.TryGenerateRsa(2048, false, null, null, null, out var jwk, out var errorMessage);
|
var result = JwkGenerator.TryGenerateRsa(2048, false, null, null, null, out var jwk, out var errorMessage);
|
||||||
Assert.True(result, errorMessage);
|
Assert.True(result, errorMessage);
|
||||||
Assert.NotNull(jwk);
|
Assert.NotNull(jwk);
|
||||||
Assert.Equal(JwkKeyType.Rsa.Name, jwk.Kty);
|
Assert.Equal(JwkKeyType.Rsa.Name, jwk.KeyType);
|
||||||
Assert.NotNull(jwk.N);
|
Assert.NotNull(jwk.RsaModulus);
|
||||||
Assert.NotNull(jwk.E);
|
Assert.NotNull(jwk.RsaExponent);
|
||||||
Assert.Null(jwk.D);
|
Assert.Null(jwk.PrivateKey);
|
||||||
Assert.Null(jwk.P);
|
Assert.Null(jwk.RsaFirstPrimeFactor);
|
||||||
Assert.Null(jwk.Q);
|
Assert.Null(jwk.RsaSecondPrimeFactor);
|
||||||
Assert.Null(jwk.DP);
|
Assert.Null(jwk.RsaFirstFactorCRTExponent);
|
||||||
Assert.Null(jwk.DQ);
|
Assert.Null(jwk.RsaSecondFactorCRTExponent);
|
||||||
Assert.Null(jwk.QI);
|
Assert.Null(jwk.RsaFirstCRTCoefficient);
|
||||||
Assert.False(string.IsNullOrWhiteSpace(jwk.Kid));
|
Assert.False(string.IsNullOrWhiteSpace(jwk.KeyId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -28,13 +28,13 @@ public class JwkGeneratorTests
|
|||||||
var result = JwkGenerator.TryGenerateRsa(2048, true, null, null, null, out var jwk, out var errorMessage);
|
var result = JwkGenerator.TryGenerateRsa(2048, true, null, null, null, out var jwk, out var errorMessage);
|
||||||
Assert.True(result, errorMessage);
|
Assert.True(result, errorMessage);
|
||||||
Assert.NotNull(jwk);
|
Assert.NotNull(jwk);
|
||||||
Assert.Equal(JwkKeyType.Rsa.Name, jwk.Kty);
|
Assert.Equal(JwkKeyType.Rsa.Name, jwk.KeyType);
|
||||||
Assert.NotNull(jwk.D);
|
Assert.NotNull(jwk.PrivateKey);
|
||||||
Assert.NotNull(jwk.P);
|
Assert.NotNull(jwk.RsaFirstPrimeFactor);
|
||||||
Assert.NotNull(jwk.Q);
|
Assert.NotNull(jwk.RsaSecondPrimeFactor);
|
||||||
Assert.NotNull(jwk.DP);
|
Assert.NotNull(jwk.RsaFirstFactorCRTExponent);
|
||||||
Assert.NotNull(jwk.DQ);
|
Assert.NotNull(jwk.RsaSecondFactorCRTExponent);
|
||||||
Assert.NotNull(jwk.QI);
|
Assert.NotNull(jwk.RsaFirstCRTCoefficient);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -47,12 +47,12 @@ public class JwkGeneratorTests
|
|||||||
var result = JwkGenerator.TryGenerateEc(curveObj, false, null, null, null, out var jwk, out var errorMessage);
|
var result = JwkGenerator.TryGenerateEc(curveObj, false, null, null, null, out var jwk, out var errorMessage);
|
||||||
Assert.True(result, errorMessage);
|
Assert.True(result, errorMessage);
|
||||||
Assert.NotNull(jwk);
|
Assert.NotNull(jwk);
|
||||||
Assert.Equal(JwkKeyType.Ec.Name, jwk.Kty);
|
Assert.Equal(JwkKeyType.Ec.Name, jwk.KeyType);
|
||||||
Assert.Equal(curve, jwk.Crv);
|
Assert.Equal(curve, jwk.EcCurve);
|
||||||
Assert.NotNull(jwk.X);
|
Assert.NotNull(jwk.EcX);
|
||||||
Assert.NotNull(jwk.Y);
|
Assert.NotNull(jwk.EcY);
|
||||||
Assert.Null(jwk.D_EC);
|
Assert.Null(jwk.PrivateKey);
|
||||||
Assert.False(string.IsNullOrWhiteSpace(jwk.Kid));
|
Assert.False(string.IsNullOrWhiteSpace(jwk.KeyId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -65,9 +65,9 @@ public class JwkGeneratorTests
|
|||||||
var result = JwkGenerator.TryGenerateEc(curveObj, true, null, null, null, out var jwk, out var errorMessage);
|
var result = JwkGenerator.TryGenerateEc(curveObj, true, null, null, null, out var jwk, out var errorMessage);
|
||||||
Assert.True(result, errorMessage);
|
Assert.True(result, errorMessage);
|
||||||
Assert.NotNull(jwk);
|
Assert.NotNull(jwk);
|
||||||
Assert.Equal(JwkKeyType.Ec.Name, jwk.Kty);
|
Assert.Equal(JwkKeyType.Ec.Name, jwk.KeyType);
|
||||||
Assert.Equal(curve, jwk.Crv);
|
Assert.Equal(curve, jwk.EcCurve);
|
||||||
Assert.NotNull(jwk.D_EC);
|
Assert.NotNull(jwk.PrivateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -76,9 +76,9 @@ public class JwkGeneratorTests
|
|||||||
var result = JwkGenerator.TryGenerateOct(256, null, null, null, out var jwk, out var errorMessage);
|
var result = JwkGenerator.TryGenerateOct(256, null, null, null, out var jwk, out var errorMessage);
|
||||||
Assert.True(result, errorMessage);
|
Assert.True(result, errorMessage);
|
||||||
Assert.NotNull(jwk);
|
Assert.NotNull(jwk);
|
||||||
Assert.Equal(JwkKeyType.Oct.Name, jwk.Kty);
|
Assert.Equal(JwkKeyType.Oct.Name, jwk.KeyType);
|
||||||
Assert.NotNull(jwk.K);
|
Assert.NotNull(jwk.SymmetricKey);
|
||||||
Assert.False(string.IsNullOrWhiteSpace(jwk.Kid));
|
Assert.False(string.IsNullOrWhiteSpace(jwk.KeyId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -102,6 +102,6 @@ public class JwkGeneratorTests
|
|||||||
Assert.True(result2, errorMessage2);
|
Assert.True(result2, errorMessage2);
|
||||||
Assert.NotNull(jwk1);
|
Assert.NotNull(jwk1);
|
||||||
Assert.NotNull(jwk2);
|
Assert.NotNull(jwk2);
|
||||||
Assert.NotEqual(jwk1.Kid, jwk2.Kid);
|
Assert.NotEqual(jwk1.KeyId, jwk2.KeyId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<!-- NuGet package metadata -->
|
<!-- NuGet package metadata -->
|
||||||
<PackageId>MaksIT.Core</PackageId>
|
<PackageId>MaksIT.Core</PackageId>
|
||||||
<Version>1.5.4</Version>
|
<Version>1.5.6</Version>
|
||||||
<Authors>Maksym Sadovnychyy</Authors>
|
<Authors>Maksym Sadovnychyy</Authors>
|
||||||
<Company>MAKS-IT</Company>
|
<Company>MAKS-IT</Company>
|
||||||
<Product>MaksIT.Core</Product>
|
<Product>MaksIT.Core</Product>
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
namespace MaksIT.Core.Security.JWK;
|
namespace MaksIT.Core.Security.JWK;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -14,8 +13,11 @@ public static class Base64UrlUtility
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Encode(byte[] data)
|
public static string Encode(byte[] data)
|
||||||
{
|
{
|
||||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
if (data == null)
|
||||||
|
throw new ArgumentNullException(nameof(data));
|
||||||
|
|
||||||
string base64 = Convert.ToBase64String(data);
|
string base64 = Convert.ToBase64String(data);
|
||||||
|
|
||||||
return base64.TrimEnd('=')
|
return base64.TrimEnd('=')
|
||||||
.Replace('+', '-')
|
.Replace('+', '-')
|
||||||
.Replace('/', '_');
|
.Replace('/', '_');
|
||||||
@ -26,7 +28,9 @@ public static class Base64UrlUtility
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Encode(string value)
|
public static string Encode(string value)
|
||||||
{
|
{
|
||||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
if (value == null)
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
|
||||||
return Encode(Encoding.UTF8.GetBytes(value));
|
return Encode(Encoding.UTF8.GetBytes(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,13 +39,17 @@ public static class Base64UrlUtility
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static byte[] Decode(string base64Url)
|
public static byte[] Decode(string base64Url)
|
||||||
{
|
{
|
||||||
if (base64Url == null) throw new ArgumentNullException(nameof(base64Url));
|
if (base64Url == null)
|
||||||
|
throw new ArgumentNullException(nameof(base64Url));
|
||||||
|
|
||||||
string padded = base64Url.Replace('-', '+').Replace('_', '/');
|
string padded = base64Url.Replace('-', '+').Replace('_', '/');
|
||||||
|
|
||||||
switch (base64Url.Length % 4)
|
switch (base64Url.Length % 4)
|
||||||
{
|
{
|
||||||
case 2: padded += "=="; break;
|
case 2: padded += "=="; break;
|
||||||
case 3: padded += "="; break;
|
case 3: padded += "="; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Convert.FromBase64String(padded);
|
return Convert.FromBase64String(padded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,71 +3,172 @@
|
|||||||
|
|
||||||
namespace MaksIT.Core.Security.JWK;
|
namespace MaksIT.Core.Security.JWK;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Standard JWK class supporting RSA, EC, and octet keys.
|
|
||||||
/// </summary>
|
|
||||||
public class Jwk {
|
public class Jwk {
|
||||||
// Common fields
|
#region Common fields
|
||||||
|
/// <summary>
|
||||||
|
/// "kty" (Key Type) Parameter
|
||||||
|
/// <para>
|
||||||
|
/// The "kty" (key type) parameter identifies the cryptographic algorithm
|
||||||
|
/// family used with the key, such as "RSA" or "EC".
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("kty")]
|
[JsonPropertyName("kty")]
|
||||||
public string? Kty { get; set; }
|
public string? KeyType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "kid" (Key ID) Parameter
|
||||||
|
/// <para>
|
||||||
|
/// The "kid" (key ID) parameter is used to match a specific key. This
|
||||||
|
/// is used, for instance, to choose among a set of keys within a JWK Set
|
||||||
|
/// during key rollover. The structure of the "kid" value is
|
||||||
|
/// unspecified.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("kid")]
|
[JsonPropertyName("kid")]
|
||||||
public string? Kid { get; set; }
|
public string? KeyId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "alg" (Algorithm) Parameter
|
||||||
|
/// <para>
|
||||||
|
/// The "alg" (algorithm) parameter identifies the algorithm intended for
|
||||||
|
/// use with the key.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("alg")]
|
[JsonPropertyName("alg")]
|
||||||
public string? Alg { get; set; }
|
public string? Algorithm { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "use" (Public Key Use) Parameter
|
||||||
|
/// <para>
|
||||||
|
/// The "use" (public key use) parameter identifies the intended use of
|
||||||
|
/// the public key. The "use" parameter is employed to indicate whether
|
||||||
|
/// a public key is used for encrypting data or verifying the signature
|
||||||
|
/// on data.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("use")]
|
[JsonPropertyName("use")]
|
||||||
public string? Use { get; set; }
|
public string? KeyUse { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "key_ops" (Key Operations) Parameter
|
||||||
|
/// <para>
|
||||||
|
/// The "key_ops" (key operations) parameter identifies the operation(s) for which the key is intended to be used.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("key_ops")]
|
[JsonPropertyName("key_ops")]
|
||||||
public string[]? KeyOps { get; set; }
|
public string[]? KeyOperations { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
// RSA fields
|
#region RSA fields
|
||||||
|
/// <summary>
|
||||||
|
/// The modulus value for the public RSA key. It is represented as the Base64URL encoding of value's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("n")]
|
[JsonPropertyName("n")]
|
||||||
public string? N { get; set; } // Modulus
|
public string? RsaModulus { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exponent value for the public RSA key. It is represented as the Base64URL encoding of value's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("e")]
|
[JsonPropertyName("e")]
|
||||||
public string? E { get; set; } // Exponent
|
public string? RsaExponent { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("d")]
|
|
||||||
public string? D { get; set; } // Private exponent
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first prime factor. It is represented as the Base64URL encoding of the value's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("p")]
|
[JsonPropertyName("p")]
|
||||||
public string? P { get; set; }
|
public string? RsaFirstPrimeFactor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The second prime factor. It is represented as the Base64URL encoding of the value's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("q")]
|
[JsonPropertyName("q")]
|
||||||
public string? Q { get; set; }
|
public string? RsaSecondPrimeFactor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first factor Chinese Remainder Theorem exponent. It is represented as the Base64URL encoding of the value's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("dp")]
|
[JsonPropertyName("dp")]
|
||||||
public string? DP { get; set; }
|
public string? RsaFirstFactorCRTExponent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The second factor Chinese Remainder Theorem exponent. It is represented as the Base64URL encoding of the value's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("dq")]
|
[JsonPropertyName("dq")]
|
||||||
public string? DQ { get; set; }
|
public string? RsaSecondFactorCRTExponent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first Chinese Remainder Theorem coefficient. It is represented as the Base64URL encoding of the value's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("qi")]
|
[JsonPropertyName("qi")]
|
||||||
public string? QI { get; set; }
|
public string? RsaFirstCRTCoefficient { get; set; }
|
||||||
|
|
||||||
// EC fields
|
/// <summary>
|
||||||
|
/// The other primes information, should they exist, null or an empty list if not specified.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("oth")]
|
||||||
|
public List<OtherPrimeInfo>? RsaOtherPrimesInfo { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region EC fields
|
||||||
|
/// <summary>
|
||||||
|
/// The "crv" (Curve) parameter identifies the cryptographic curve used with the key.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("crv")]
|
[JsonPropertyName("crv")]
|
||||||
public string? Crv { get; set; }
|
public string? EcCurve { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "x" coordinate for the EC public key. It is represented as the Base64URL encoding of the coordinate's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("x")]
|
[JsonPropertyName("x")]
|
||||||
public string? X { get; set; }
|
public string? EcX { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "y" coordinate for the EC public key. It is represented as the Base64URL encoding of the coordinate's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("y")]
|
[JsonPropertyName("y")]
|
||||||
public string? Y { get; set; }
|
public string? EcY { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
[JsonPropertyName("d_ec")]
|
#region Private Key field
|
||||||
public string? D_EC { get; set; } // EC private key
|
/// <summary>
|
||||||
|
/// The private key value ("d"). Used for RSA (private exponent) and EC (private key).
|
||||||
|
/// RFC 7518 uses "d" for both.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("d")]
|
||||||
|
public string? PrivateKey { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
// Symmetric (octet) fields
|
#region Symmetric (octet) fields
|
||||||
|
/// <summary>
|
||||||
|
/// The symmetric (octet) key value. It is represented as the Base64URL encoding of the value's big endian representation.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("k")]
|
[JsonPropertyName("k")]
|
||||||
public string? K { get; set; }
|
public string? SymmetricKey { get; set; }
|
||||||
|
#endregion
|
||||||
// Backward compatibility for old code
|
}
|
||||||
[JsonIgnore]
|
|
||||||
public string? Exponent { get => E; set => E = value; }
|
/// <summary>
|
||||||
[JsonIgnore]
|
/// Represents an entry in the 'oth' (Other Primes Info) parameter for multi-prime RSA keys.
|
||||||
public string? Modulus { get => N; set => N = value; }
|
/// </summary>
|
||||||
|
public class OtherPrimeInfo {
|
||||||
|
#region OtherPrimeInfo fields
|
||||||
|
/// <summary>
|
||||||
|
/// The value of the other prime factor.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("r")]
|
||||||
|
public string? PrimeFactor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CRT exponent of the other prime factor.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("d")]
|
||||||
|
public string? FactorCRTExponent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CRT coefficient of the other prime factor.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("t")]
|
||||||
|
public string? FactorCRTCoefficient { get; set; }
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@ -4,10 +4,11 @@ using System.Text.Json.Serialization;
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using MaksIT.Core.Extensions;
|
using MaksIT.Core.Extensions;
|
||||||
|
|
||||||
|
|
||||||
namespace MaksIT.Core.Security.JWK;
|
namespace MaksIT.Core.Security.JWK;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides utilities for JWK (JSON Web Key) operations, including RFC 7638 thumbprint computation and key generation.
|
/// Provides utilities for JWK (JSON Web Key) operations, including RFC7638 thumbprint computation and key generation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class JwkGenerator {
|
public static class JwkGenerator {
|
||||||
public static bool TryGenerateRsa(int keySize, bool includePrivate, JwkAlgorithm? alg, string? use, string[]? keyOps, [NotNullWhen(true)] out Jwk? jwk, [NotNullWhen(false)] out string? errorMessage) {
|
public static bool TryGenerateRsa(int keySize, bool includePrivate, JwkAlgorithm? alg, string? use, string[]? keyOps, [NotNullWhen(true)] out Jwk? jwk, [NotNullWhen(false)] out string? errorMessage) {
|
||||||
@ -15,7 +16,8 @@ public static class JwkGenerator {
|
|||||||
jwk = GenerateRsa(keySize, includePrivate, alg, use, keyOps);
|
jwk = GenerateRsa(keySize, includePrivate, alg, use, keyOps);
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
jwk = null;
|
jwk = null;
|
||||||
errorMessage = ex.Message;
|
errorMessage = ex.Message;
|
||||||
return false;
|
return false;
|
||||||
@ -27,7 +29,8 @@ public static class JwkGenerator {
|
|||||||
jwk = GenerateEc(curve, includePrivate, alg, use, keyOps);
|
jwk = GenerateEc(curve, includePrivate, alg, use, keyOps);
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
jwk = null;
|
jwk = null;
|
||||||
errorMessage = ex.Message;
|
errorMessage = ex.Message;
|
||||||
return false;
|
return false;
|
||||||
@ -39,7 +42,8 @@ public static class JwkGenerator {
|
|||||||
jwk = GenerateOct(keySizeBits, alg, use, keyOps);
|
jwk = GenerateOct(keySizeBits, alg, use, keyOps);
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
jwk = null;
|
jwk = null;
|
||||||
errorMessage = ex.Message;
|
errorMessage = ex.Message;
|
||||||
return false;
|
return false;
|
||||||
@ -51,7 +55,8 @@ public static class JwkGenerator {
|
|||||||
jwk = GenerateRsaFromRsa(rsa, includePrivate, alg, use, keyOps);
|
jwk = GenerateRsaFromRsa(rsa, includePrivate, alg, use, keyOps);
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
jwk = null;
|
jwk = null;
|
||||||
errorMessage = ex.Message;
|
errorMessage = ex.Message;
|
||||||
return false;
|
return false;
|
||||||
@ -69,17 +74,17 @@ public static class JwkGenerator {
|
|||||||
errorMessage = "JWK cannot be null.";
|
errorMessage = "JWK cannot be null.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(jwk.E) || string.IsNullOrEmpty(jwk.N)) {
|
if (string.IsNullOrEmpty(jwk.RsaExponent) || string.IsNullOrEmpty(jwk.RsaModulus)) {
|
||||||
errorMessage = "JWK must have Exponent and Modulus set.";
|
errorMessage = "JWK must have RsaExponent and RsaModulus set.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// RFC 7638: Lexicographic order: e, kty, n
|
// RFC7638: Lexicographic order: e, kty, n
|
||||||
var orderedJwk = new OrderedJwk {
|
var orderedJwk = new OrderedJwk {
|
||||||
E = jwk.E!,
|
E = jwk.RsaExponent!,
|
||||||
Kty = "RSA",
|
Kty = "RSA",
|
||||||
N = jwk.N!
|
N = jwk.RsaModulus!
|
||||||
};
|
};
|
||||||
|
|
||||||
var json = orderedJwk.ToJson();
|
var json = orderedJwk.ToJson();
|
||||||
@ -97,22 +102,22 @@ public static class JwkGenerator {
|
|||||||
using var rsa = RSA.Create(keySize);
|
using var rsa = RSA.Create(keySize);
|
||||||
var parameters = rsa.ExportParameters(includePrivate);
|
var parameters = rsa.ExportParameters(includePrivate);
|
||||||
var jwk = new Jwk {
|
var jwk = new Jwk {
|
||||||
Kty = JwkKeyType.Rsa.Name,
|
KeyType = JwkKeyType.Rsa.Name,
|
||||||
N = Base64UrlEncode(parameters.Modulus!),
|
RsaModulus = Base64UrlEncode(parameters.Modulus!),
|
||||||
E = Base64UrlEncode(parameters.Exponent!),
|
RsaExponent = Base64UrlEncode(parameters.Exponent!),
|
||||||
Alg = (alg ?? (keySize >= 4096 ? JwkAlgorithm.Rs512 : JwkAlgorithm.Rs256)).Name,
|
Algorithm = (alg ?? (keySize >= 4096 ? JwkAlgorithm.Rs512 : JwkAlgorithm.Rs256)).Name,
|
||||||
Use = use,
|
KeyUse = use,
|
||||||
KeyOps = keyOps,
|
KeyOperations = keyOps,
|
||||||
};
|
};
|
||||||
if (includePrivate) {
|
if (includePrivate) {
|
||||||
jwk.D = Base64UrlEncode(parameters.D!);
|
jwk.PrivateKey = Base64UrlEncode(parameters.D!);
|
||||||
jwk.P = Base64UrlEncode(parameters.P!);
|
jwk.RsaFirstPrimeFactor = Base64UrlEncode(parameters.P!);
|
||||||
jwk.Q = Base64UrlEncode(parameters.Q!);
|
jwk.RsaSecondPrimeFactor = Base64UrlEncode(parameters.Q!);
|
||||||
jwk.DP = Base64UrlEncode(parameters.DP!);
|
jwk.RsaFirstFactorCRTExponent = Base64UrlEncode(parameters.DP!);
|
||||||
jwk.DQ = Base64UrlEncode(parameters.DQ!);
|
jwk.RsaSecondFactorCRTExponent = Base64UrlEncode(parameters.DQ!);
|
||||||
jwk.QI = Base64UrlEncode(parameters.InverseQ!);
|
jwk.RsaFirstCRTCoefficient = Base64UrlEncode(parameters.InverseQ!);
|
||||||
}
|
}
|
||||||
jwk.Kid = ComputeKid(jwk);
|
jwk.KeyId = ComputeKid(jwk);
|
||||||
return jwk;
|
return jwk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,31 +132,31 @@ public static class JwkGenerator {
|
|||||||
using var ec = ECDsa.Create(ecCurve);
|
using var ec = ECDsa.Create(ecCurve);
|
||||||
var parameters = ec.ExportParameters(includePrivate);
|
var parameters = ec.ExportParameters(includePrivate);
|
||||||
var jwk = new Jwk {
|
var jwk = new Jwk {
|
||||||
Kty = JwkKeyType.Ec.Name,
|
KeyType = JwkKeyType.Ec.Name,
|
||||||
Crv = curve.Name,
|
EcCurve = curve.Name,
|
||||||
X = Base64UrlEncode(parameters.Q.X!),
|
EcX = Base64UrlEncode(parameters.Q.X!),
|
||||||
Y = Base64UrlEncode(parameters.Q.Y!),
|
EcY = Base64UrlEncode(parameters.Q.Y!),
|
||||||
Alg = (alg ?? (curve == JwkCurve.P384 ? JwkAlgorithm.Es384 : curve == JwkCurve.P521 ? JwkAlgorithm.Es512 : JwkAlgorithm.Es256)).Name,
|
Algorithm = (alg ?? (curve == JwkCurve.P384 ? JwkAlgorithm.Es384 : curve == JwkCurve.P521 ? JwkAlgorithm.Es512 : JwkAlgorithm.Es256)).Name,
|
||||||
Use = use,
|
KeyUse = use,
|
||||||
KeyOps = keyOps,
|
KeyOperations = keyOps,
|
||||||
};
|
};
|
||||||
if (includePrivate && parameters.D != null) {
|
if (includePrivate && parameters.D != null) {
|
||||||
jwk.D_EC = Base64UrlEncode(parameters.D);
|
jwk.PrivateKey = Base64UrlEncode(parameters.D);
|
||||||
}
|
}
|
||||||
jwk.Kid = ComputeKid(jwk);
|
jwk.KeyId = ComputeKid(jwk);
|
||||||
return jwk;
|
return jwk;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Jwk GenerateOct(int keySizeBits = 256, JwkAlgorithm? alg = null, string? use = null, string[]? keyOps = null) {
|
private static Jwk GenerateOct(int keySizeBits = 256, JwkAlgorithm? alg = null, string? use = null, string[]? keyOps = null) {
|
||||||
var key = RandomNumberGenerator.GetBytes(keySizeBits / 8);
|
var key = RandomNumberGenerator.GetBytes(keySizeBits / 8);
|
||||||
var jwk = new Jwk {
|
var jwk = new Jwk {
|
||||||
Kty = JwkKeyType.Oct.Name,
|
KeyType = JwkKeyType.Oct.Name,
|
||||||
K = Base64UrlEncode(key),
|
SymmetricKey = Base64UrlEncode(key),
|
||||||
Alg = (alg ?? (keySizeBits == 256 ? JwkAlgorithm.A256Gcm : keySizeBits == 128 ? JwkAlgorithm.A128Gcm : JwkAlgorithm.A512Gcm)).Name,
|
Algorithm = (alg ?? (keySizeBits == 256 ? JwkAlgorithm.A256Gcm : keySizeBits == 128 ? JwkAlgorithm.A128Gcm : JwkAlgorithm.A512Gcm)).Name,
|
||||||
Use = use,
|
KeyUse = use,
|
||||||
KeyOps = keyOps,
|
KeyOperations = keyOps,
|
||||||
};
|
};
|
||||||
jwk.Kid = ComputeKid(jwk);
|
jwk.KeyId = ComputeKid(jwk);
|
||||||
return jwk;
|
return jwk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,22 +164,22 @@ public static class JwkGenerator {
|
|||||||
if (rsa == null) throw new ArgumentNullException(nameof(rsa));
|
if (rsa == null) throw new ArgumentNullException(nameof(rsa));
|
||||||
var parameters = rsa.ExportParameters(includePrivate);
|
var parameters = rsa.ExportParameters(includePrivate);
|
||||||
var jwk = new Jwk {
|
var jwk = new Jwk {
|
||||||
Kty = JwkKeyType.Rsa.Name,
|
KeyType = JwkKeyType.Rsa.Name,
|
||||||
N = Base64UrlUtility.Encode(parameters.Modulus!),
|
RsaModulus = Base64UrlUtility.Encode(parameters.Modulus!),
|
||||||
E = Base64UrlUtility.Encode(parameters.Exponent!),
|
RsaExponent = Base64UrlUtility.Encode(parameters.Exponent!),
|
||||||
Alg = (alg ?? JwkAlgorithm.Rs256).Name,
|
Algorithm = (alg ?? JwkAlgorithm.Rs256).Name,
|
||||||
Use = use,
|
KeyUse = use,
|
||||||
KeyOps = keyOps,
|
KeyOperations = keyOps,
|
||||||
};
|
};
|
||||||
if (includePrivate) {
|
if (includePrivate) {
|
||||||
jwk.D = Base64UrlUtility.Encode(parameters.D!);
|
jwk.PrivateKey = Base64UrlUtility.Encode(parameters.D!);
|
||||||
jwk.P = Base64UrlUtility.Encode(parameters.P!);
|
jwk.RsaFirstPrimeFactor = Base64UrlUtility.Encode(parameters.P!);
|
||||||
jwk.Q = Base64UrlUtility.Encode(parameters.Q!);
|
jwk.RsaSecondPrimeFactor = Base64UrlUtility.Encode(parameters.Q!);
|
||||||
jwk.DP = Base64UrlUtility.Encode(parameters.DP!);
|
jwk.RsaFirstFactorCRTExponent = Base64UrlUtility.Encode(parameters.DP!);
|
||||||
jwk.DQ = Base64UrlUtility.Encode(parameters.DQ!);
|
jwk.RsaSecondFactorCRTExponent = Base64UrlUtility.Encode(parameters.DQ!);
|
||||||
jwk.QI = Base64UrlUtility.Encode(parameters.InverseQ!);
|
jwk.RsaFirstCRTCoefficient = Base64UrlUtility.Encode(parameters.InverseQ!);
|
||||||
}
|
}
|
||||||
jwk.Kid = ComputeKid(jwk);
|
jwk.KeyId = ComputeKid(jwk);
|
||||||
return jwk;
|
return jwk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,15 +189,15 @@ public static class JwkGenerator {
|
|||||||
|
|
||||||
private static string ComputeKid(Jwk jwk) {
|
private static string ComputeKid(Jwk jwk) {
|
||||||
// Use thumbprint as kid if possible
|
// Use thumbprint as kid if possible
|
||||||
if (jwk.Kty == "RSA" && !string.IsNullOrEmpty(jwk.N) && !string.IsNullOrEmpty(jwk.E)) {
|
if (jwk.KeyType == "RSA" && !string.IsNullOrEmpty(jwk.RsaModulus) && !string.IsNullOrEmpty(jwk.RsaExponent)) {
|
||||||
TryComputeThumbprint(jwk, out var thumb, out _);
|
TryComputeThumbprint(jwk, out var thumb, out _);
|
||||||
return thumb ?? Guid.NewGuid().ToString("N");
|
return thumb ?? Guid.NewGuid().ToString("N");
|
||||||
}
|
}
|
||||||
// For EC and oct, use a hash of the key material
|
// For EC and oct, use a hash of the key material
|
||||||
using var sha = SHA256.Create();
|
using var sha = SHA256.Create();
|
||||||
string keyMaterial = jwk.Kty switch {
|
string keyMaterial = jwk.KeyType switch {
|
||||||
"EC" => jwk.X + jwk.Y + jwk.Crv,
|
"EC" => jwk.EcX + jwk.EcY + jwk.EcCurve,
|
||||||
"oct" => jwk.K,
|
"oct" => jwk.SymmetricKey,
|
||||||
_ => null
|
_ => null
|
||||||
} ?? Guid.NewGuid().ToString();
|
} ?? Guid.NewGuid().ToString();
|
||||||
var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(keyMaterial));
|
var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(keyMaterial));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user