(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**:
- 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<THeader>(
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<T>(
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<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
- 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.
---

View File

@ -8,7 +8,7 @@
<!-- NuGet package metadata -->
<PackageId>MaksIT.Core</PackageId>
<Version>1.5.8</Version>
<Version>1.5.9</Version>
<Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company>
<Product>MaksIT.Core</Product>

View File

@ -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;
}
}

View File

@ -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<THeader>(
RSA rsa,
Jwk jwk,
JwsHeader protectedHeader,
THeader protectedHeader,
[NotNullWhen(true)] out JwsMessage? message,
[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,
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;