From 9f9d5fc474915ad81eb860070615fff08cc7162e Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Fri, 14 Nov 2025 20:44:51 +0100 Subject: [PATCH] (feature): JwsGenerator support for ACME extended JwsHeader --- README.md | 40 ++++++++++--------- src/MaksIT.Core/MaksIT.Core.csproj | 2 +- .../Security/JWK/JwkThumbprintUtility.cs | 9 ++++- src/MaksIT.Core/Security/JWS/JwsGenerator.cs | 15 +++---- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 524fd7e..aa65ae1 100644 --- a/README.md +++ b/README.md @@ -1034,6 +1034,9 @@ The `JwsGenerator` class in the `MaksIT.Core.Security.JWS` namespace provides me 1. **JWS Creation**: - Sign string or object payloads using an RSA key and JWK. - Produces a JWS message containing the protected header, payload, and signature. + - Supports generic protected header and payload types. + - Automatically sets the `Algorithm` property to `RS256` in the protected header. + - Sets either the `KeyId` or the full `Jwk` in the protected header, depending on the presence of `KeyId`. --- @@ -1045,7 +1048,7 @@ using MaksIT.Core.Security.JWK; using MaksIT.Core.Security.JWS; using var rsa = RSA.Create(2048); -JwkGenerator.TryGenerateFromRCA(rsa, out var jwk, out var errorMessage); +JwkGenerator.TryGenerateFromRSA(rsa, out var jwk, out var errorMessage); var header = new JwsHeader(); var payload = "my-payload"; var result = JwsGenerator.TryEncode(rsa, jwk!, header, payload, out var jwsMessage, out var error); @@ -1064,34 +1067,35 @@ else #### API ```csharp -public static bool TryEncode( +public static bool TryEncode( RSA rsa, Jwk jwk, - JwsHeader protectedHeader, - [NotNullWhen(true)]out JwsMessage? message, - [NotNullWhen(false)] out string? errorMessage -) -``` -- Signs an empty payload. - -```csharp -public static bool TryEncode( - RSA rsa, - Jwk jwk, - JwsHeader protectedHeader, - T? payload, + THeader protectedHeader, [NotNullWhen(true)] out JwsMessage? message, [NotNullWhen(false)] out string? errorMessage -) +) where THeader : JwsHeader ``` -- Signs the provided payload (string or object). +- Signs an empty payload with a generic protected header. + +```csharp +public static bool TryEncode( + RSA rsa, + Jwk jwk, + THeader protectedHeader, + TPayload? payload, + [NotNullWhen(true)] out JwsMessage? message, + [NotNullWhen(false)] out string? errorMessage +) where THeader : JwsHeader +``` +- Signs the provided payload (string or object) with a generic protected header. --- #### Notes - Only supports signing (no verification or key authorization). - The protected header is automatically set to use RS256. -- The payload is base64url encoded. +- If the JWK has a `KeyId`, it is set in the header; otherwise, the full JWK is included. +- The payload is base64url encoded (as a string or JSON). - Returns `false` and an error message if signing fails. --- diff --git a/src/MaksIT.Core/MaksIT.Core.csproj b/src/MaksIT.Core/MaksIT.Core.csproj index 3f168fd..676529d 100644 --- a/src/MaksIT.Core/MaksIT.Core.csproj +++ b/src/MaksIT.Core/MaksIT.Core.csproj @@ -8,7 +8,7 @@ MaksIT.Core - 1.5.8 + 1.5.9 Maksym Sadovnychyy MAKS-IT MaksIT.Core diff --git a/src/MaksIT.Core/Security/JWK/JwkThumbprintUtility.cs b/src/MaksIT.Core/Security/JWK/JwkThumbprintUtility.cs index 16907d8..36071ce 100644 --- a/src/MaksIT.Core/Security/JWK/JwkThumbprintUtility.cs +++ b/src/MaksIT.Core/Security/JWK/JwkThumbprintUtility.cs @@ -37,19 +37,26 @@ public static class JwkThumbprintUtility { ) { thumbprint = null; errorMessage = null; + try { if (jwk.RsaExponent == null || jwk.RsaModulus == null) throw new ArgumentException("RSA exponent or modulus is null."); + var thumbprintObj = new OrderedJwk { E = jwk.RsaExponent, Kty = JwkKeyType.Rsa.Name, N = jwk.RsaModulus }; + var json = thumbprintObj.ToJson(); + thumbprint = Base64UrlUtility.Encode(SHA256.HashData(Encoding.UTF8.GetBytes(json))); + return true; - } catch (Exception ex) { + } + catch (Exception ex) { errorMessage = ex.Message; + return false; } } diff --git a/src/MaksIT.Core/Security/JWS/JwsGenerator.cs b/src/MaksIT.Core/Security/JWS/JwsGenerator.cs index 1159c0f..39dfcfc 100644 --- a/src/MaksIT.Core/Security/JWS/JwsGenerator.cs +++ b/src/MaksIT.Core/Security/JWS/JwsGenerator.cs @@ -8,23 +8,24 @@ using MaksIT.Core.Extensions; namespace MaksIT.Core.Security.JWS; public static class JwsGenerator { - public static bool TryEncode( + public static bool TryEncode( RSA rsa, Jwk jwk, - JwsHeader protectedHeader, + THeader protectedHeader, [NotNullWhen(true)] out JwsMessage? message, [NotNullWhen(false)] out string? errorMessage - ) => TryEncode(rsa, jwk, protectedHeader, null, out message, out errorMessage); + ) where THeader : JwsHeader => + TryEncode(rsa, jwk, protectedHeader, null, out message, out errorMessage); - public static bool TryEncode( + public static bool TryEncode( RSA rsa, Jwk jwk, - JwsHeader protectedHeader, - T? payload, + THeader protectedHeader, + TPayload? payload, [NotNullWhen(true)] out JwsMessage? message, [NotNullWhen(false)] out string? errorMessage - ) { + ) where THeader : JwsHeader { try { protectedHeader.Algorithm = JwkAlgorithm.Rs256.Name;