From 85d72b7b2851c7c22f5e0b3590a712f3648a2ccb Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Wed, 12 Nov 2025 18:40:49 +0100 Subject: [PATCH] (refactor): jwk and jws external classes usage --- src/LetsEncrypt/Entities/Jws/ACMEJwsHeader.cs | 11 ++ src/LetsEncrypt/Entities/Jws/Jwk.cs | 106 ------------------ src/LetsEncrypt/Entities/Jws/JwsMessage.cs | 35 ------ .../Entities/LetsEncrypt/RegistrationCache.cs | 2 +- src/LetsEncrypt/LetsEncrypt.csproj | 2 +- src/LetsEncrypt/Models/Responses/Account.cs | 3 +- src/LetsEncrypt/Services/JwsService.cs | 21 ++-- .../Services/LetsEncryptService.cs | 42 +++---- src/MaksIT.Webapi/Dockerfile | 2 +- src/Models/MaksIT.Models.csproj | 2 +- 10 files changed, 49 insertions(+), 177 deletions(-) create mode 100644 src/LetsEncrypt/Entities/Jws/ACMEJwsHeader.cs delete mode 100644 src/LetsEncrypt/Entities/Jws/Jwk.cs delete mode 100644 src/LetsEncrypt/Entities/Jws/JwsMessage.cs diff --git a/src/LetsEncrypt/Entities/Jws/ACMEJwsHeader.cs b/src/LetsEncrypt/Entities/Jws/ACMEJwsHeader.cs new file mode 100644 index 0000000..e36f26a --- /dev/null +++ b/src/LetsEncrypt/Entities/Jws/ACMEJwsHeader.cs @@ -0,0 +1,11 @@ +using MaksIT.Core.Security.JWS; +using System.Text.Json.Serialization; + +namespace MaksIT.LetsEncrypt.Entities.Jws; +public class ACMEJwsHeader : JwsHeader { + [JsonPropertyName("url")] + public string? Url { get; set; } + + [JsonPropertyName("nonce")] + public string? Nonce { get; set; } +} diff --git a/src/LetsEncrypt/Entities/Jws/Jwk.cs b/src/LetsEncrypt/Entities/Jws/Jwk.cs deleted file mode 100644 index 0601c59..0000000 --- a/src/LetsEncrypt/Entities/Jws/Jwk.cs +++ /dev/null @@ -1,106 +0,0 @@ -// https://tools.ietf.org/html/rfc7517 - -using System.Text.Json.Serialization; - - -namespace MaksIT.LetsEncrypt.Entities.Jws; - -public class Jwk { - /// - /// "kty" (Key Type) Parameter - /// - /// The "kty" (key type) parameter identifies the cryptographic algorithm - /// family used with the key, such as "RSA" or "EC". - /// - /// - [JsonPropertyName("kty")] - public string? KeyType { get; set; } - - /// - /// "kid" (Key ID) Parameter - /// - /// 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. - /// - /// - [JsonPropertyName("kid")] - public string? KeyId { get; set; } - - /// - /// "use" (Public Key Use) Parameter - /// - /// 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. - /// - /// - [JsonPropertyName("use")] - public string? Use { get; set; } - - /// - /// The the modulus value for the public RSA key. It is represented as the Base64URL encoding of value's big endian representation. - /// - [JsonPropertyName("n")] - public string? Modulus { get; set; } - - /// - /// The exponent value for the public RSA key. It is represented as the Base64URL encoding of value's big endian representation. - /// - [JsonPropertyName("e")] - public string? Exponent { get; set; } - - /// - /// The private exponent. It is represented as the Base64URL encoding of the value's big endian representation. - /// - [JsonPropertyName("d")] - public string? D { get; set; } - - /// - /// The first prime factor. It is represented as the Base64URL encoding of the value's big endian representation. - /// - [JsonPropertyName("p")] - public string? P { get; set; } - - /// - /// The second prime factor. It is represented as the Base64URL encoding of the value's big endian representation. - /// - [JsonPropertyName("q")] - public string? Q { get; set; } - - /// - /// The first factor Chinese Remainder Theorem exponent. It is represented as the Base64URL encoding of the value's big endian representation. - /// - [JsonPropertyName("dp")] - public string? DP { get; set; } - - /// - /// The second factor Chinese Remainder Theorem exponent. It is represented as the Base64URL encoding of the value's big endian representation. - /// - [JsonPropertyName("dq")] - public string? DQ { get; set; } - - /// - /// The first Chinese Remainder Theorem coefficient. It is represented as the Base64URL encoding of the value's big endian representation. - /// - [JsonPropertyName("qi")] - public string? InverseQ { get; set; } - - /// - /// The other primes information, should they exist, null or an empty list if not specified. - /// - [JsonPropertyName("oth")] - public string? OthInf { get; set; } - - /// - /// "alg" (Algorithm) Parameter - /// - /// The "alg" (algorithm) parameter identifies the algorithm intended for - /// use with the key. - /// - /// - [JsonPropertyName("alg")] - public string? Algorithm { get; set; } -} diff --git a/src/LetsEncrypt/Entities/Jws/JwsMessage.cs b/src/LetsEncrypt/Entities/Jws/JwsMessage.cs deleted file mode 100644 index ee964a5..0000000 --- a/src/LetsEncrypt/Entities/Jws/JwsMessage.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Text.Json.Serialization; - - -namespace MaksIT.LetsEncrypt.Entities.Jws; - -public class JwsMessage { - - public string? Protected { get; set; } - - public string? Payload { get; set; } - - public string? Signature { get; set; } -} - - -public class JwsHeader { - - [JsonPropertyName("alg")] - public string? Algorithm { get; set; } - - [JsonPropertyName("jwk")] - public Jwk? Key { get; set; } - - - [JsonPropertyName("kid")] - public string? KeyId { get; set; } - - public string? Nonce { get; set; } - - public Uri? Url { get; set; } - - - [JsonPropertyName("Host")] - public string? Host { get; set; } -} diff --git a/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs b/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs index a2ce0c5..5fa2fbd 100644 --- a/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs +++ b/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs @@ -2,7 +2,7 @@ using System.Text; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using MaksIT.LetsEncrypt.Entities.Jws; +using MaksIT.Core.Security.JWK; namespace MaksIT.LetsEncrypt.Entities; diff --git a/src/LetsEncrypt/LetsEncrypt.csproj b/src/LetsEncrypt/LetsEncrypt.csproj index dd0a0ec..f73bee1 100644 --- a/src/LetsEncrypt/LetsEncrypt.csproj +++ b/src/LetsEncrypt/LetsEncrypt.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/LetsEncrypt/Models/Responses/Account.cs b/src/LetsEncrypt/Models/Responses/Account.cs index b00fae4..690b7f4 100644 --- a/src/LetsEncrypt/Models/Responses/Account.cs +++ b/src/LetsEncrypt/Models/Responses/Account.cs @@ -1,4 +1,5 @@ -using MaksIT.LetsEncrypt.Entities.Jws; +using MaksIT.Core.Security.JWK; +using MaksIT.LetsEncrypt.Entities.Jws; using MaksIT.LetsEncrypt.Models.Interfaces; /* diff --git a/src/LetsEncrypt/Services/JwsService.cs b/src/LetsEncrypt/Services/JwsService.cs index ff90df7..266f014 100644 --- a/src/LetsEncrypt/Services/JwsService.cs +++ b/src/LetsEncrypt/Services/JwsService.cs @@ -7,14 +7,16 @@ using System.Text; using System.Security.Cryptography; using MaksIT.Core.Extensions; using MaksIT.LetsEncrypt.Entities.Jws; +using MaksIT.Core.Security.JWK; +using MaksIT.Core.Security.JWS; namespace MaksIT.LetsEncrypt.Services; public interface IJwsService { void SetKeyId(string location); - JwsMessage Encode(JwsHeader protectedHeader); - JwsMessage Encode(TPayload payload, JwsHeader protectedHeader); + JwsMessage Encode(ACMEJwsHeader protectedHeader); + JwsMessage Encode(TPayload payload, ACMEJwsHeader protectedHeader); string GetKeyAuthorization(string token); string Base64UrlEncoded(string s); string Base64UrlEncoded(byte[] arg); @@ -35,8 +37,8 @@ public class JwsService : IJwsService { _jwk = new Jwk() { KeyType = "RSA", - Exponent = Base64UrlEncoded(exp), - Modulus = Base64UrlEncoded(mod), + RsaExponent = Base64UrlEncoded(exp), + RsaModulus = Base64UrlEncoded(mod), }; } @@ -44,10 +46,10 @@ public class JwsService : IJwsService { _jwk.KeyId = location; } - public JwsMessage Encode(JwsHeader protectedHeader) => + public JwsMessage Encode(ACMEJwsHeader protectedHeader) => Encode(null, protectedHeader); - public JwsMessage Encode(T? payload, JwsHeader protectedHeader) { + public JwsMessage Encode(T? payload, ACMEJwsHeader protectedHeader) { protectedHeader.Algorithm = "RS256"; if (_jwk.KeyId != null) { @@ -69,7 +71,6 @@ public class JwsService : IJwsService { message.Payload = Base64UrlEncoded(payload.ToJson()); } - message.Signature = Base64UrlEncoded( _rsa.SignData(Encoding.ASCII.GetBytes($"{message.Protected}.{message.Payload}"), HashAlgorithmName.SHA256, @@ -84,12 +85,12 @@ public class JwsService : IJwsService { private string GetSha256Thumbprint() { var thumbprint = new { - e = _jwk.Exponent, + e = _jwk.RsaExponent, kty = "RSA", - n = _jwk.Modulus + n = _jwk.RsaModulus }; - var json = "{\"e\":\"" + _jwk.Exponent + "\",\"kty\":\"RSA\",\"n\":\"" + _jwk.Modulus + "\"}"; + var json = "{\"e\":\"" + _jwk.RsaExponent + "\",\"kty\":\"RSA\",\"n\":\"" + _jwk.RsaModulus + "\"}"; return Base64UrlEncoded(SHA256.HashData(Encoding.UTF8.GetBytes(json))); } diff --git a/src/LetsEncrypt/Services/LetsEncryptService.cs b/src/LetsEncrypt/Services/LetsEncryptService.cs index 1e25acc..eb81592 100644 --- a/src/LetsEncrypt/Services/LetsEncryptService.cs +++ b/src/LetsEncrypt/Services/LetsEncryptService.cs @@ -89,8 +89,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, challenge.Url, state); - var pollJson = EncodeMessage(true, null, state, new JwsHeader { - Url = challenge.Url, + var pollJson = EncodeMessage(true, null, state, new ACMEJwsHeader { + Url = challenge.Url.ToString(), Nonce = state.Nonce }); @@ -194,8 +194,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, state.Directory.NewAccount, state); - var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader { - Url = state.Directory.NewAccount, + var json = EncodeMessage(false, letsEncryptOrder, state, new ACMEJwsHeader { + Url = state.Directory.NewAccount.ToString(), Nonce = state.Nonce }); @@ -287,8 +287,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, state.Directory.NewOrder, state); - var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader { - Url = state.Directory.NewOrder, + var json = EncodeMessage(false, letsEncryptOrder, state, new ACMEJwsHeader { + Url = state.Directory.NewOrder.ToString(), Nonce = state.Nonce }); @@ -316,8 +316,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, item, state); - json = EncodeMessage(true, null, state, new JwsHeader { - Url = item, + json = EncodeMessage(true, null, state, new ACMEJwsHeader { + Url = item.ToString(), Nonce = state.Nonce }); @@ -399,8 +399,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, challenge.Url, state); - var json = EncodeMessage(false, "{}", state, new JwsHeader { - Url = challenge.Url, + var json = EncodeMessage(false, "{}", state, new ACMEJwsHeader { + Url = challenge.Url.ToString(), Nonce = state.Nonce }); @@ -440,8 +440,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, state.Directory.NewOrder, state); - var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader { - Url = state.Directory.NewOrder, + var json = EncodeMessage(false, letsEncryptOrder, state, new ACMEJwsHeader { + Url = state.Directory.NewOrder.ToString(), Nonce = state.Nonce }); @@ -501,8 +501,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, state.CurrentOrder.Finalize!, state); - var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader { - Url = state.CurrentOrder.Finalize, + var json = EncodeMessage(false, letsEncryptOrder, state, new ACMEJwsHeader { + Url = state.CurrentOrder.Finalize.ToString(), Nonce = state.Nonce }); @@ -515,8 +515,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, state.CurrentOrder.Location!, state); - json = EncodeMessage(true, null, state, new JwsHeader { - Url = state.CurrentOrder.Location, + json = EncodeMessage(true, null, state, new ACMEJwsHeader { + Url = state.CurrentOrder.Location.ToString(), Nonce = state.Nonce }); @@ -544,8 +544,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, certificateUrl!, state); - var finalJson = EncodeMessage(true, null, state, new JwsHeader { - Url = certificateUrl, + var finalJson = EncodeMessage(true, null, state, new ACMEJwsHeader { + Url = certificateUrl.ToString(), Nonce = state.Nonce }); @@ -617,8 +617,8 @@ public class LetsEncryptService : ILetsEncryptService { await HandleNonceAsync(sessionId, state.Directory.RevokeCert, state); - var jwsHeader = new JwsHeader { - Url = state.Directory.RevokeCert, + var jwsHeader = new ACMEJwsHeader { + Url = state.Directory.RevokeCert.ToString(), Nonce = state.Nonce }; @@ -691,7 +691,7 @@ public class LetsEncryptService : ILetsEncryptService { } } - private string EncodeMessage(bool isPostAsGet, object? requestModel, State state, JwsHeader jwsHeader) { + private string EncodeMessage(bool isPostAsGet, object? requestModel, State state, ACMEJwsHeader jwsHeader) { return isPostAsGet ? state.JwsService!.Encode(jwsHeader).ToJson() : state.JwsService!.Encode(requestModel, jwsHeader).ToJson(); diff --git a/src/MaksIT.Webapi/Dockerfile b/src/MaksIT.Webapi/Dockerfile index 8537442..0299e2c 100644 --- a/src/MaksIT.Webapi/Dockerfile +++ b/src/MaksIT.Webapi/Dockerfile @@ -8,7 +8,7 @@ EXPOSE 5000 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["Models/Models.csproj", "Models/"] +COPY ["MaksIT.Models/MaksIT.Models.csproj", "MaksIT.Models/"] COPY ["LetsEncrypt/LetsEncrypt.csproj", "LetsEncrypt/"] COPY ["MaksIT.Webapi/MaksIT.Webapi.csproj", "MaksIT.Webapi/"] RUN dotnet restore "./MaksIT.Webapi/MaksIT.Webapi.csproj" diff --git a/src/Models/MaksIT.Models.csproj b/src/Models/MaksIT.Models.csproj index ff6c3e0..36b534b 100644 --- a/src/Models/MaksIT.Models.csproj +++ b/src/Models/MaksIT.Models.csproj @@ -11,7 +11,7 @@ - +