(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);
|
||||
Assert.True(result, errorMessage);
|
||||
Assert.NotNull(jwk);
|
||||
Assert.Equal(JwkKeyType.Rsa.Name, jwk.Kty);
|
||||
Assert.NotNull(jwk.N);
|
||||
Assert.NotNull(jwk.E);
|
||||
Assert.Null(jwk.D);
|
||||
Assert.Null(jwk.P);
|
||||
Assert.Null(jwk.Q);
|
||||
Assert.Null(jwk.DP);
|
||||
Assert.Null(jwk.DQ);
|
||||
Assert.Null(jwk.QI);
|
||||
Assert.False(string.IsNullOrWhiteSpace(jwk.Kid));
|
||||
Assert.Equal(JwkKeyType.Rsa.Name, jwk.KeyType);
|
||||
Assert.NotNull(jwk.RsaModulus);
|
||||
Assert.NotNull(jwk.RsaExponent);
|
||||
Assert.Null(jwk.PrivateKey);
|
||||
Assert.Null(jwk.RsaFirstPrimeFactor);
|
||||
Assert.Null(jwk.RsaSecondPrimeFactor);
|
||||
Assert.Null(jwk.RsaFirstFactorCRTExponent);
|
||||
Assert.Null(jwk.RsaSecondFactorCRTExponent);
|
||||
Assert.Null(jwk.RsaFirstCRTCoefficient);
|
||||
Assert.False(string.IsNullOrWhiteSpace(jwk.KeyId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -28,13 +28,13 @@ public class JwkGeneratorTests
|
||||
var result = JwkGenerator.TryGenerateRsa(2048, true, null, null, null, out var jwk, out var errorMessage);
|
||||
Assert.True(result, errorMessage);
|
||||
Assert.NotNull(jwk);
|
||||
Assert.Equal(JwkKeyType.Rsa.Name, jwk.Kty);
|
||||
Assert.NotNull(jwk.D);
|
||||
Assert.NotNull(jwk.P);
|
||||
Assert.NotNull(jwk.Q);
|
||||
Assert.NotNull(jwk.DP);
|
||||
Assert.NotNull(jwk.DQ);
|
||||
Assert.NotNull(jwk.QI);
|
||||
Assert.Equal(JwkKeyType.Rsa.Name, jwk.KeyType);
|
||||
Assert.NotNull(jwk.PrivateKey);
|
||||
Assert.NotNull(jwk.RsaFirstPrimeFactor);
|
||||
Assert.NotNull(jwk.RsaSecondPrimeFactor);
|
||||
Assert.NotNull(jwk.RsaFirstFactorCRTExponent);
|
||||
Assert.NotNull(jwk.RsaSecondFactorCRTExponent);
|
||||
Assert.NotNull(jwk.RsaFirstCRTCoefficient);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -47,12 +47,12 @@ public class JwkGeneratorTests
|
||||
var result = JwkGenerator.TryGenerateEc(curveObj, false, null, null, null, out var jwk, out var errorMessage);
|
||||
Assert.True(result, errorMessage);
|
||||
Assert.NotNull(jwk);
|
||||
Assert.Equal(JwkKeyType.Ec.Name, jwk.Kty);
|
||||
Assert.Equal(curve, jwk.Crv);
|
||||
Assert.NotNull(jwk.X);
|
||||
Assert.NotNull(jwk.Y);
|
||||
Assert.Null(jwk.D_EC);
|
||||
Assert.False(string.IsNullOrWhiteSpace(jwk.Kid));
|
||||
Assert.Equal(JwkKeyType.Ec.Name, jwk.KeyType);
|
||||
Assert.Equal(curve, jwk.EcCurve);
|
||||
Assert.NotNull(jwk.EcX);
|
||||
Assert.NotNull(jwk.EcY);
|
||||
Assert.Null(jwk.PrivateKey);
|
||||
Assert.False(string.IsNullOrWhiteSpace(jwk.KeyId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -65,9 +65,9 @@ public class JwkGeneratorTests
|
||||
var result = JwkGenerator.TryGenerateEc(curveObj, true, null, null, null, out var jwk, out var errorMessage);
|
||||
Assert.True(result, errorMessage);
|
||||
Assert.NotNull(jwk);
|
||||
Assert.Equal(JwkKeyType.Ec.Name, jwk.Kty);
|
||||
Assert.Equal(curve, jwk.Crv);
|
||||
Assert.NotNull(jwk.D_EC);
|
||||
Assert.Equal(JwkKeyType.Ec.Name, jwk.KeyType);
|
||||
Assert.Equal(curve, jwk.EcCurve);
|
||||
Assert.NotNull(jwk.PrivateKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -76,9 +76,9 @@ public class JwkGeneratorTests
|
||||
var result = JwkGenerator.TryGenerateOct(256, null, null, null, out var jwk, out var errorMessage);
|
||||
Assert.True(result, errorMessage);
|
||||
Assert.NotNull(jwk);
|
||||
Assert.Equal(JwkKeyType.Oct.Name, jwk.Kty);
|
||||
Assert.NotNull(jwk.K);
|
||||
Assert.False(string.IsNullOrWhiteSpace(jwk.Kid));
|
||||
Assert.Equal(JwkKeyType.Oct.Name, jwk.KeyType);
|
||||
Assert.NotNull(jwk.SymmetricKey);
|
||||
Assert.False(string.IsNullOrWhiteSpace(jwk.KeyId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -102,6 +102,6 @@ public class JwkGeneratorTests
|
||||
Assert.True(result2, errorMessage2);
|
||||
Assert.NotNull(jwk1);
|
||||
Assert.NotNull(jwk2);
|
||||
Assert.NotEqual(jwk1.Kid, jwk2.Kid);
|
||||
Assert.NotEqual(jwk1.KeyId, jwk2.KeyId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
<!-- NuGet package metadata -->
|
||||
<PackageId>MaksIT.Core</PackageId>
|
||||
<Version>1.5.4</Version>
|
||||
<Version>1.5.6</Version>
|
||||
<Authors>Maksym Sadovnychyy</Authors>
|
||||
<Company>MAKS-IT</Company>
|
||||
<Product>MaksIT.Core</Product>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace MaksIT.Core.Security.JWK;
|
||||
|
||||
/// <summary>
|
||||
@ -14,8 +13,11 @@ public static class Base64UrlUtility
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
return base64.TrimEnd('=')
|
||||
.Replace('+', '-')
|
||||
.Replace('/', '_');
|
||||
@ -26,7 +28,9 @@ public static class Base64UrlUtility
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
@ -35,13 +39,17 @@ public static class Base64UrlUtility
|
||||
/// </summary>
|
||||
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('_', '/');
|
||||
|
||||
switch (base64Url.Length % 4)
|
||||
{
|
||||
case 2: padded += "=="; break;
|
||||
case 3: padded += "="; break;
|
||||
}
|
||||
|
||||
return Convert.FromBase64String(padded);
|
||||
}
|
||||
|
||||
|
||||
@ -3,71 +3,172 @@
|
||||
|
||||
namespace MaksIT.Core.Security.JWK;
|
||||
|
||||
/// <summary>
|
||||
/// Standard JWK class supporting RSA, EC, and octet keys.
|
||||
/// </summary>
|
||||
|
||||
public class Jwk {
|
||||
// Common fields
|
||||
[JsonPropertyName("kty")]
|
||||
public string? Kty { get; set; }
|
||||
#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")]
|
||||
public string? KeyType { get; set; }
|
||||
|
||||
[JsonPropertyName("kid")]
|
||||
public string? Kid { 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")]
|
||||
public string? KeyId { get; set; }
|
||||
|
||||
[JsonPropertyName("alg")]
|
||||
public string? Alg { get; set; }
|
||||
/// <summary>
|
||||
/// "alg" (Algorithm) Parameter
|
||||
/// <para>
|
||||
/// The "alg" (algorithm) parameter identifies the algorithm intended for
|
||||
/// use with the key.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[JsonPropertyName("alg")]
|
||||
public string? Algorithm { get; set; }
|
||||
|
||||
[JsonPropertyName("use")]
|
||||
public string? Use { 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")]
|
||||
public string? KeyUse { get; set; }
|
||||
|
||||
[JsonPropertyName("key_ops")]
|
||||
public string[]? KeyOps { 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")]
|
||||
public string[]? KeyOperations { get; set; }
|
||||
#endregion
|
||||
|
||||
// RSA fields
|
||||
[JsonPropertyName("n")]
|
||||
public string? N { get; set; } // Modulus
|
||||
#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")]
|
||||
public string? RsaModulus { get; set; }
|
||||
|
||||
[JsonPropertyName("e")]
|
||||
public string? E { get; set; } // Exponent
|
||||
/// <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")]
|
||||
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")]
|
||||
public string? RsaFirstPrimeFactor { get; set; }
|
||||
|
||||
[JsonPropertyName("p")]
|
||||
public string? P { get; set; }
|
||||
/// <summary>
|
||||
/// The second prime factor. It is represented as the Base64URL encoding of the value's big endian representation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("q")]
|
||||
public string? RsaSecondPrimeFactor { get; set; }
|
||||
|
||||
[JsonPropertyName("q")]
|
||||
public string? Q { 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")]
|
||||
public string? RsaFirstFactorCRTExponent { get; set; }
|
||||
|
||||
[JsonPropertyName("dp")]
|
||||
public string? DP { 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")]
|
||||
public string? RsaSecondFactorCRTExponent { get; set; }
|
||||
|
||||
[JsonPropertyName("dq")]
|
||||
public string? DQ { 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")]
|
||||
public string? RsaFirstCRTCoefficient { get; set; }
|
||||
|
||||
[JsonPropertyName("qi")]
|
||||
public string? QI { get; set; }
|
||||
/// <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
|
||||
|
||||
// EC fields
|
||||
[JsonPropertyName("crv")]
|
||||
public string? Crv { get; set; }
|
||||
#region EC fields
|
||||
/// <summary>
|
||||
/// The "crv" (Curve) parameter identifies the cryptographic curve used with the key.
|
||||
/// </summary>
|
||||
[JsonPropertyName("crv")]
|
||||
public string? EcCurve { get; set; }
|
||||
|
||||
[JsonPropertyName("x")]
|
||||
public string? X { 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")]
|
||||
public string? EcX { get; set; }
|
||||
|
||||
[JsonPropertyName("y")]
|
||||
public string? Y { 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")]
|
||||
public string? EcY { get; set; }
|
||||
#endregion
|
||||
|
||||
[JsonPropertyName("d_ec")]
|
||||
public string? D_EC { get; set; } // EC private key
|
||||
#region Private Key field
|
||||
/// <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
|
||||
[JsonPropertyName("k")]
|
||||
public string? K { get; set; }
|
||||
#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")]
|
||||
public string? SymmetricKey { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
// Backward compatibility for old code
|
||||
[JsonIgnore]
|
||||
public string? Exponent { get => E; set => E = value; }
|
||||
[JsonIgnore]
|
||||
public string? Modulus { get => N; set => N = value; }
|
||||
/// <summary>
|
||||
/// Represents an entry in the 'oth' (Other Primes Info) parameter for multi-prime RSA keys.
|
||||
/// </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 MaksIT.Core.Extensions;
|
||||
|
||||
|
||||
namespace MaksIT.Core.Security.JWK;
|
||||
|
||||
/// <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>
|
||||
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) {
|
||||
@ -15,7 +16,8 @@ public static class JwkGenerator {
|
||||
jwk = GenerateRsa(keySize, includePrivate, alg, use, keyOps);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
jwk = null;
|
||||
errorMessage = ex.Message;
|
||||
return false;
|
||||
@ -27,7 +29,8 @@ public static class JwkGenerator {
|
||||
jwk = GenerateEc(curve, includePrivate, alg, use, keyOps);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
jwk = null;
|
||||
errorMessage = ex.Message;
|
||||
return false;
|
||||
@ -39,7 +42,8 @@ public static class JwkGenerator {
|
||||
jwk = GenerateOct(keySizeBits, alg, use, keyOps);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
jwk = null;
|
||||
errorMessage = ex.Message;
|
||||
return false;
|
||||
@ -51,7 +55,8 @@ public static class JwkGenerator {
|
||||
jwk = GenerateRsaFromRsa(rsa, includePrivate, alg, use, keyOps);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
jwk = null;
|
||||
errorMessage = ex.Message;
|
||||
return false;
|
||||
@ -59,9 +64,9 @@ public static class JwkGenerator {
|
||||
}
|
||||
|
||||
public static bool TryComputeThumbprint(
|
||||
Jwk jwk,
|
||||
[NotNullWhen(true)] out string? thumbprint,
|
||||
[NotNullWhen(false)] out string? errorMessage) {
|
||||
Jwk jwk,
|
||||
[NotNullWhen(true)] out string? thumbprint,
|
||||
[NotNullWhen(false)] out string? errorMessage) {
|
||||
thumbprint = null;
|
||||
errorMessage = null;
|
||||
|
||||
@ -69,17 +74,17 @@ public static class JwkGenerator {
|
||||
errorMessage = "JWK cannot be null.";
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrEmpty(jwk.E) || string.IsNullOrEmpty(jwk.N)) {
|
||||
errorMessage = "JWK must have Exponent and Modulus set.";
|
||||
if (string.IsNullOrEmpty(jwk.RsaExponent) || string.IsNullOrEmpty(jwk.RsaModulus)) {
|
||||
errorMessage = "JWK must have RsaExponent and RsaModulus set.";
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// RFC 7638: Lexicographic order: e, kty, n
|
||||
// RFC7638: Lexicographic order: e, kty, n
|
||||
var orderedJwk = new OrderedJwk {
|
||||
E = jwk.E!,
|
||||
E = jwk.RsaExponent!,
|
||||
Kty = "RSA",
|
||||
N = jwk.N!
|
||||
N = jwk.RsaModulus!
|
||||
};
|
||||
|
||||
var json = orderedJwk.ToJson();
|
||||
@ -97,22 +102,22 @@ public static class JwkGenerator {
|
||||
using var rsa = RSA.Create(keySize);
|
||||
var parameters = rsa.ExportParameters(includePrivate);
|
||||
var jwk = new Jwk {
|
||||
Kty = JwkKeyType.Rsa.Name,
|
||||
N = Base64UrlEncode(parameters.Modulus!),
|
||||
E = Base64UrlEncode(parameters.Exponent!),
|
||||
Alg = (alg ?? (keySize >= 4096 ? JwkAlgorithm.Rs512 : JwkAlgorithm.Rs256)).Name,
|
||||
Use = use,
|
||||
KeyOps = keyOps,
|
||||
KeyType = JwkKeyType.Rsa.Name,
|
||||
RsaModulus = Base64UrlEncode(parameters.Modulus!),
|
||||
RsaExponent = Base64UrlEncode(parameters.Exponent!),
|
||||
Algorithm = (alg ?? (keySize >= 4096 ? JwkAlgorithm.Rs512 : JwkAlgorithm.Rs256)).Name,
|
||||
KeyUse = use,
|
||||
KeyOperations = keyOps,
|
||||
};
|
||||
if (includePrivate) {
|
||||
jwk.D = Base64UrlEncode(parameters.D!);
|
||||
jwk.P = Base64UrlEncode(parameters.P!);
|
||||
jwk.Q = Base64UrlEncode(parameters.Q!);
|
||||
jwk.DP = Base64UrlEncode(parameters.DP!);
|
||||
jwk.DQ = Base64UrlEncode(parameters.DQ!);
|
||||
jwk.QI = Base64UrlEncode(parameters.InverseQ!);
|
||||
jwk.PrivateKey = Base64UrlEncode(parameters.D!);
|
||||
jwk.RsaFirstPrimeFactor = Base64UrlEncode(parameters.P!);
|
||||
jwk.RsaSecondPrimeFactor = Base64UrlEncode(parameters.Q!);
|
||||
jwk.RsaFirstFactorCRTExponent = Base64UrlEncode(parameters.DP!);
|
||||
jwk.RsaSecondFactorCRTExponent = Base64UrlEncode(parameters.DQ!);
|
||||
jwk.RsaFirstCRTCoefficient = Base64UrlEncode(parameters.InverseQ!);
|
||||
}
|
||||
jwk.Kid = ComputeKid(jwk);
|
||||
jwk.KeyId = ComputeKid(jwk);
|
||||
return jwk;
|
||||
}
|
||||
|
||||
@ -127,31 +132,31 @@ public static class JwkGenerator {
|
||||
using var ec = ECDsa.Create(ecCurve);
|
||||
var parameters = ec.ExportParameters(includePrivate);
|
||||
var jwk = new Jwk {
|
||||
Kty = JwkKeyType.Ec.Name,
|
||||
Crv = curve.Name,
|
||||
X = Base64UrlEncode(parameters.Q.X!),
|
||||
Y = Base64UrlEncode(parameters.Q.Y!),
|
||||
Alg = (alg ?? (curve == JwkCurve.P384 ? JwkAlgorithm.Es384 : curve == JwkCurve.P521 ? JwkAlgorithm.Es512 : JwkAlgorithm.Es256)).Name,
|
||||
Use = use,
|
||||
KeyOps = keyOps,
|
||||
KeyType = JwkKeyType.Ec.Name,
|
||||
EcCurve = curve.Name,
|
||||
EcX = Base64UrlEncode(parameters.Q.X!),
|
||||
EcY = Base64UrlEncode(parameters.Q.Y!),
|
||||
Algorithm = (alg ?? (curve == JwkCurve.P384 ? JwkAlgorithm.Es384 : curve == JwkCurve.P521 ? JwkAlgorithm.Es512 : JwkAlgorithm.Es256)).Name,
|
||||
KeyUse = use,
|
||||
KeyOperations = keyOps,
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
private static Jwk GenerateOct(int keySizeBits = 256, JwkAlgorithm? alg = null, string? use = null, string[]? keyOps = null) {
|
||||
var key = RandomNumberGenerator.GetBytes(keySizeBits / 8);
|
||||
var jwk = new Jwk {
|
||||
Kty = JwkKeyType.Oct.Name,
|
||||
K = Base64UrlEncode(key),
|
||||
Alg = (alg ?? (keySizeBits == 256 ? JwkAlgorithm.A256Gcm : keySizeBits == 128 ? JwkAlgorithm.A128Gcm : JwkAlgorithm.A512Gcm)).Name,
|
||||
Use = use,
|
||||
KeyOps = keyOps,
|
||||
KeyType = JwkKeyType.Oct.Name,
|
||||
SymmetricKey = Base64UrlEncode(key),
|
||||
Algorithm = (alg ?? (keySizeBits == 256 ? JwkAlgorithm.A256Gcm : keySizeBits == 128 ? JwkAlgorithm.A128Gcm : JwkAlgorithm.A512Gcm)).Name,
|
||||
KeyUse = use,
|
||||
KeyOperations = keyOps,
|
||||
};
|
||||
jwk.Kid = ComputeKid(jwk);
|
||||
jwk.KeyId = ComputeKid(jwk);
|
||||
return jwk;
|
||||
}
|
||||
|
||||
@ -159,22 +164,22 @@ public static class JwkGenerator {
|
||||
if (rsa == null) throw new ArgumentNullException(nameof(rsa));
|
||||
var parameters = rsa.ExportParameters(includePrivate);
|
||||
var jwk = new Jwk {
|
||||
Kty = JwkKeyType.Rsa.Name,
|
||||
N = Base64UrlUtility.Encode(parameters.Modulus!),
|
||||
E = Base64UrlUtility.Encode(parameters.Exponent!),
|
||||
Alg = (alg ?? JwkAlgorithm.Rs256).Name,
|
||||
Use = use,
|
||||
KeyOps = keyOps,
|
||||
KeyType = JwkKeyType.Rsa.Name,
|
||||
RsaModulus = Base64UrlUtility.Encode(parameters.Modulus!),
|
||||
RsaExponent = Base64UrlUtility.Encode(parameters.Exponent!),
|
||||
Algorithm = (alg ?? JwkAlgorithm.Rs256).Name,
|
||||
KeyUse = use,
|
||||
KeyOperations = keyOps,
|
||||
};
|
||||
if (includePrivate) {
|
||||
jwk.D = Base64UrlUtility.Encode(parameters.D!);
|
||||
jwk.P = Base64UrlUtility.Encode(parameters.P!);
|
||||
jwk.Q = Base64UrlUtility.Encode(parameters.Q!);
|
||||
jwk.DP = Base64UrlUtility.Encode(parameters.DP!);
|
||||
jwk.DQ = Base64UrlUtility.Encode(parameters.DQ!);
|
||||
jwk.QI = Base64UrlUtility.Encode(parameters.InverseQ!);
|
||||
jwk.PrivateKey = Base64UrlUtility.Encode(parameters.D!);
|
||||
jwk.RsaFirstPrimeFactor = Base64UrlUtility.Encode(parameters.P!);
|
||||
jwk.RsaSecondPrimeFactor = Base64UrlUtility.Encode(parameters.Q!);
|
||||
jwk.RsaFirstFactorCRTExponent = Base64UrlUtility.Encode(parameters.DP!);
|
||||
jwk.RsaSecondFactorCRTExponent = Base64UrlUtility.Encode(parameters.DQ!);
|
||||
jwk.RsaFirstCRTCoefficient = Base64UrlUtility.Encode(parameters.InverseQ!);
|
||||
}
|
||||
jwk.Kid = ComputeKid(jwk);
|
||||
jwk.KeyId = ComputeKid(jwk);
|
||||
return jwk;
|
||||
}
|
||||
|
||||
@ -184,15 +189,15 @@ public static class JwkGenerator {
|
||||
|
||||
private static string ComputeKid(Jwk jwk) {
|
||||
// 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 _);
|
||||
return thumb ?? Guid.NewGuid().ToString("N");
|
||||
}
|
||||
// For EC and oct, use a hash of the key material
|
||||
using var sha = SHA256.Create();
|
||||
string keyMaterial = jwk.Kty switch {
|
||||
"EC" => jwk.X + jwk.Y + jwk.Crv,
|
||||
"oct" => jwk.K,
|
||||
string keyMaterial = jwk.KeyType switch {
|
||||
"EC" => jwk.EcX + jwk.EcY + jwk.EcCurve,
|
||||
"oct" => jwk.SymmetricKey,
|
||||
_ => null
|
||||
} ?? Guid.NewGuid().ToString();
|
||||
var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(keyMaterial));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user