(feature): JwsGenerator support for ACME extended JwsHeader

This commit is contained in:
Maksym Sadovnychyy 2025-11-14 20:44:51 +01:00
parent c6f7e47240
commit 9f9d5fc474
4 changed files with 39 additions and 27 deletions

View File

@ -1034,6 +1034,9 @@ The `JwsGenerator` class in the `MaksIT.Core.Security.JWS` namespace provides me
1. **JWS Creation**: 1. **JWS Creation**:
- Sign string or object payloads using an RSA key and JWK. - Sign string or object payloads using an RSA key and JWK.
- Produces a JWS message containing the protected header, payload, and signature. - 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 MaksIT.Core.Security.JWS;
using var rsa = RSA.Create(2048); 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 header = new JwsHeader();
var payload = "my-payload"; var payload = "my-payload";
var result = JwsGenerator.TryEncode(rsa, jwk!, header, payload, out var jwsMessage, out var error); var result = JwsGenerator.TryEncode(rsa, jwk!, header, payload, out var jwsMessage, out var error);
@ -1064,34 +1067,35 @@ else
#### API #### API
```csharp ```csharp
public static bool TryEncode( public static bool TryEncode<THeader>(
RSA rsa, RSA rsa,
Jwk jwk, Jwk jwk,
JwsHeader protectedHeader, THeader protectedHeader,
[NotNullWhen(true)]out JwsMessage? message,
[NotNullWhen(false)] out string? errorMessage
)
```
- Signs an empty payload.
```csharp
public static bool TryEncode<T>(
RSA rsa,
Jwk jwk,
JwsHeader protectedHeader,
T? payload,
[NotNullWhen(true)] out JwsMessage? message, [NotNullWhen(true)] out JwsMessage? message,
[NotNullWhen(false)] out string? errorMessage [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<THeader, TPayload>(
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 #### Notes
- Only supports signing (no verification or key authorization). - Only supports signing (no verification or key authorization).
- The protected header is automatically set to use RS256. - 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. - Returns `false` and an error message if signing fails.
--- ---

View File

@ -8,7 +8,7 @@
<!-- NuGet package metadata --> <!-- NuGet package metadata -->
<PackageId>MaksIT.Core</PackageId> <PackageId>MaksIT.Core</PackageId>
<Version>1.5.8</Version> <Version>1.5.9</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>

View File

@ -37,19 +37,26 @@ public static class JwkThumbprintUtility {
) { ) {
thumbprint = null; thumbprint = null;
errorMessage = null; errorMessage = null;
try { try {
if (jwk.RsaExponent == null || jwk.RsaModulus == null) if (jwk.RsaExponent == null || jwk.RsaModulus == null)
throw new ArgumentException("RSA exponent or modulus is null."); throw new ArgumentException("RSA exponent or modulus is null.");
var thumbprintObj = new OrderedJwk { var thumbprintObj = new OrderedJwk {
E = jwk.RsaExponent, E = jwk.RsaExponent,
Kty = JwkKeyType.Rsa.Name, Kty = JwkKeyType.Rsa.Name,
N = jwk.RsaModulus N = jwk.RsaModulus
}; };
var json = thumbprintObj.ToJson(); var json = thumbprintObj.ToJson();
thumbprint = Base64UrlUtility.Encode(SHA256.HashData(Encoding.UTF8.GetBytes(json))); thumbprint = Base64UrlUtility.Encode(SHA256.HashData(Encoding.UTF8.GetBytes(json)));
return true; return true;
} catch (Exception ex) { }
catch (Exception ex) {
errorMessage = ex.Message; errorMessage = ex.Message;
return false; return false;
} }
} }

View File

@ -8,23 +8,24 @@ using MaksIT.Core.Extensions;
namespace MaksIT.Core.Security.JWS; namespace MaksIT.Core.Security.JWS;
public static class JwsGenerator { public static class JwsGenerator {
public static bool TryEncode( public static bool TryEncode<THeader>(
RSA rsa, RSA rsa,
Jwk jwk, Jwk jwk,
JwsHeader protectedHeader, THeader protectedHeader,
[NotNullWhen(true)] out JwsMessage? message, [NotNullWhen(true)] out JwsMessage? message,
[NotNullWhen(false)] out string? errorMessage [NotNullWhen(false)] out string? errorMessage
) => TryEncode<string>(rsa, jwk, protectedHeader, null, out message, out errorMessage); ) where THeader : JwsHeader =>
TryEncode<THeader, string>(rsa, jwk, protectedHeader, null, out message, out errorMessage);
public static bool TryEncode<T>( public static bool TryEncode<THeader, TPayload>(
RSA rsa, RSA rsa,
Jwk jwk, Jwk jwk,
JwsHeader protectedHeader, THeader protectedHeader,
T? payload, TPayload? payload,
[NotNullWhen(true)] out JwsMessage? message, [NotNullWhen(true)] out JwsMessage? message,
[NotNullWhen(false)] out string? errorMessage [NotNullWhen(false)] out string? errorMessage
) { ) where THeader : JwsHeader {
try { try {
protectedHeader.Algorithm = JwkAlgorithm.Rs256.Name; protectedHeader.Algorithm = JwkAlgorithm.Rs256.Name;