mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(refactor): simplified state and more linear logics
This commit is contained in:
parent
29c223652c
commit
f31176085c
@ -9,13 +9,9 @@ namespace MaksIT.LetsEncrypt.Entities.LetsEncrypt;
|
|||||||
public class State {
|
public class State {
|
||||||
public bool IsStaging { get; set; }
|
public bool IsStaging { get; set; }
|
||||||
public AcmeDirectory? Directory { get; set; }
|
public AcmeDirectory? Directory { get; set; }
|
||||||
public JwsService? JwsService { get; set; }
|
|
||||||
public Order? CurrentOrder { get; set; }
|
public Order? CurrentOrder { get; set; }
|
||||||
public List<AuthorizationChallengeChallenge> Challenges { get; } = new List<AuthorizationChallengeChallenge>();
|
public List<AuthorizationChallengeChallenge> Challenges { get; } = new List<AuthorizationChallengeChallenge>();
|
||||||
public RegistrationCache? Cache { get; set; }
|
public RegistrationCache? Cache { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Jwk? Jwk { get; set; }
|
public Jwk? Jwk { get; set; }
|
||||||
public RSA? Rsa { get; set; }
|
public RSA? Rsa { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* https://tools.ietf.org/html/rfc4648
|
|
||||||
* https://tools.ietf.org/html/rfc4648#section-5
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
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(ACMEJwsHeader protectedHeader);
|
|
||||||
JwsMessage Encode<TPayload>(TPayload payload, ACMEJwsHeader protectedHeader);
|
|
||||||
string GetKeyAuthorization(string token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class JwsService : IJwsService {
|
|
||||||
|
|
||||||
public Jwk _jwk;
|
|
||||||
private RSA _rsa;
|
|
||||||
|
|
||||||
public JwsService(RSA rsa) {
|
|
||||||
_rsa = rsa;
|
|
||||||
|
|
||||||
if (!JwkGenerator.TryGenerateFromRSA(rsa, out _jwk, out var errorMessage)) {
|
|
||||||
throw new Exception(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetKeyId(string location) {
|
|
||||||
_jwk.KeyId = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JwsMessage Encode(ACMEJwsHeader protectedHeader) {
|
|
||||||
|
|
||||||
Encode<string>(null, protectedHeader);
|
|
||||||
|
|
||||||
if (!JwsGenerator.TryEncode(_rsa, _jwk, protectedHeader, out var jwsMessage, out var errorMessage)) {
|
|
||||||
throw new Exception(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwsMessage;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public JwsMessage Encode<TPayload>(TPayload? payload, ACMEJwsHeader protectedHeader) {
|
|
||||||
|
|
||||||
if (!JwsGenerator.TryEncode(_rsa, _jwk, protectedHeader, payload, out var jwsMessage, out var errorMessage)) {
|
|
||||||
throw new Exception(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwsMessage;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetKeyAuthorization(string token) {
|
|
||||||
if (!JwkThumbprintUtility.TryGetKeyAuthorization(_jwk, token, out var keyAuthorization, out var errorMessage))
|
|
||||||
throw new Exception(errorMessage);
|
|
||||||
|
|
||||||
return keyAuthorization;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
using MaksIT.Core.Extensions;
|
using MaksIT.Core.Extensions;
|
||||||
using MaksIT.Core.Security;
|
using MaksIT.Core.Security;
|
||||||
|
using MaksIT.Core.Security.JWK;
|
||||||
|
using MaksIT.Core.Security.JWS;
|
||||||
using MaksIT.LetsEncrypt.Entities;
|
using MaksIT.LetsEncrypt.Entities;
|
||||||
using MaksIT.LetsEncrypt.Entities.Jws;
|
using MaksIT.LetsEncrypt.Entities.Jws;
|
||||||
using MaksIT.LetsEncrypt.Entities.LetsEncrypt;
|
using MaksIT.LetsEncrypt.Entities.LetsEncrypt;
|
||||||
@ -71,9 +73,11 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
if (state.Directory == null) {
|
if (state.Directory == null) {
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(DirectoryEndpoint, UriKind.Relative));
|
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(DirectoryEndpoint, UriKind.Relative));
|
||||||
|
|
||||||
//await HandleNonceAsync(sessionId, new Uri(DirectoryEndpoint, UriKind.Relative), state);
|
var requestResult = await SendAcmeRequest<AcmeDirectory>(request, state, HttpMethod.Get);
|
||||||
|
if (!requestResult.IsSuccess || requestResult.Value == null)
|
||||||
|
return requestResult;
|
||||||
|
|
||||||
var directory = await SendAcmeRequest<AcmeDirectory>(request, state, HttpMethod.Get);
|
var directory = requestResult.Value;
|
||||||
|
|
||||||
state.Directory = directory.Result ?? throw new InvalidOperationException("Directory response is null");
|
state.Directory = directory.Result ?? throw new InvalidOperationException("Directory response is null");
|
||||||
}
|
}
|
||||||
@ -121,12 +125,22 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
accountKey.ImportCspBlob(cache.AccountKey);
|
accountKey.ImportCspBlob(cache.AccountKey);
|
||||||
|
|
||||||
state.JwsService = new JwsService(accountKey);
|
if (!JwkGenerator.TryGenerateFromRSA(accountKey, out var jwk, out var errorMessage)) {
|
||||||
|
return Result.InternalServerError(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
state.JwsService.SetKeyId(cache.Location?.ToString() ?? string.Empty);
|
state.Rsa = accountKey;
|
||||||
|
state.Jwk = jwk;
|
||||||
|
|
||||||
|
state.Jwk.KeyId = cache.Location?.ToString() ?? string.Empty;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
state.JwsService = new JwsService(accountKey);
|
if (!JwkGenerator.TryGenerateFromRSA(accountKey, out var jwk, out var errorMessage)) {
|
||||||
|
return Result.InternalServerError(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Rsa = accountKey;
|
||||||
|
state.Jwk = jwk;
|
||||||
|
|
||||||
var letsEncryptOrder = new Account {
|
var letsEncryptOrder = new Account {
|
||||||
TermsOfServiceAgreed = true,
|
TermsOfServiceAgreed = true,
|
||||||
@ -135,25 +149,34 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewAccount);
|
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewAccount);
|
||||||
|
|
||||||
var nonceResult = await HandleNonceAsync(sessionId, state.Directory.NewAccount);
|
var nonceResult = await GetNonceAsync(sessionId, state.Directory.NewAccount);
|
||||||
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
||||||
return nonceResult;
|
return nonceResult;
|
||||||
|
|
||||||
var nonce = nonceResult.Value;
|
var nonce = nonceResult.Value;
|
||||||
|
|
||||||
var json = EncodeMessage(false, letsEncryptOrder, state, new ACMEJwsHeader {
|
var jsonResult = EncodeMessage(sessionId, false, letsEncryptOrder, new ACMEJwsHeader {
|
||||||
Url = state.Directory.NewAccount.ToString(),
|
Url = state.Directory.NewAccount.ToString(),
|
||||||
Nonce = nonce
|
Nonce = nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!jsonResult.IsSuccess || jsonResult.Value == null)
|
||||||
|
return jsonResult;
|
||||||
|
|
||||||
|
var json = jsonResult.Value;
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var result = await SendAcmeRequest<Account>(request, state, HttpMethod.Post);
|
var requestResult = await SendAcmeRequest<Account>(request, state, HttpMethod.Post);
|
||||||
|
if (!requestResult.IsSuccess || requestResult.Value == null)
|
||||||
|
return requestResult;
|
||||||
|
|
||||||
state.JwsService.SetKeyId(result.Result?.Location?.ToString() ?? string.Empty);
|
var result = requestResult.Value;
|
||||||
|
|
||||||
|
state.Jwk.KeyId = result.Result?.Location?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
if (result.Result?.Status != "valid") {
|
if (result.Result?.Status != "valid") {
|
||||||
var errorMessage = $"Account status is not valid, was: {result.Result?.Status} \r\n {result.ResponseText}";
|
errorMessage = $"Account status is not valid, was: {result.Result?.Status} \r\n {result.ResponseText}";
|
||||||
_logger.LogError(errorMessage);
|
_logger.LogError(errorMessage);
|
||||||
return Result.InternalServerError(errorMessage);
|
return Result.InternalServerError(errorMessage);
|
||||||
}
|
}
|
||||||
@ -232,20 +255,29 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewOrder);
|
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewOrder);
|
||||||
|
|
||||||
var nonceResult = await HandleNonceAsync(sessionId, state.Directory.NewOrder);
|
var nonceResult = await GetNonceAsync(sessionId, state.Directory.NewOrder);
|
||||||
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
||||||
return nonceResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
|
return nonceResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
|
||||||
|
|
||||||
var nonce = nonceResult.Value;
|
var nonce = nonceResult.Value;
|
||||||
|
|
||||||
var json = EncodeMessage(false, letsEncryptOrder, state, new ACMEJwsHeader {
|
var jsonResult = EncodeMessage(sessionId, false, letsEncryptOrder, new ACMEJwsHeader {
|
||||||
Url = state.Directory.NewOrder.ToString(),
|
Url = state.Directory.NewOrder.ToString(),
|
||||||
Nonce = nonce
|
Nonce = nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!jsonResult.IsSuccess || jsonResult.Value == null)
|
||||||
|
return jsonResult.ToResultOfType <Dictionary<string, string>?>(_ => null);
|
||||||
|
|
||||||
|
var json = jsonResult.Value;
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
var requestResult = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
||||||
|
if (!requestResult.IsSuccess || requestResult.Value == null)
|
||||||
|
return requestResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
|
||||||
|
|
||||||
|
var order = requestResult.Value;
|
||||||
|
|
||||||
if (StatusEquals(order.Result?.Status, OrderStatus.Ready))
|
if (StatusEquals(order.Result?.Status, OrderStatus.Ready))
|
||||||
return Result<Dictionary<string, string>?>.Ok(new Dictionary<string, string>());
|
return Result<Dictionary<string, string>?>.Ok(new Dictionary<string, string>());
|
||||||
@ -265,20 +297,29 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
request = new HttpRequestMessage(HttpMethod.Post, item);
|
request = new HttpRequestMessage(HttpMethod.Post, item);
|
||||||
|
|
||||||
nonceResult = await HandleNonceAsync(sessionId, item);
|
nonceResult = await GetNonceAsync(sessionId, item);
|
||||||
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
||||||
return nonceResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
|
return nonceResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
|
||||||
|
|
||||||
nonce = nonceResult.Value;
|
nonce = nonceResult.Value;
|
||||||
|
|
||||||
json = EncodeMessage(true, null, state, new ACMEJwsHeader {
|
jsonResult = EncodeMessage(sessionId, true, null, new ACMEJwsHeader {
|
||||||
Url = item.ToString(),
|
Url = item.ToString(),
|
||||||
Nonce = nonce
|
Nonce = nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!jsonResult.IsSuccess || jsonResult.Value == null)
|
||||||
|
return jsonResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
|
||||||
|
|
||||||
|
json = jsonResult.Value;
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var challengeResponse = await SendAcmeRequest<AuthorizationChallengeResponse>(request, state, HttpMethod.Post);
|
var challengeResult = await SendAcmeRequest<AuthorizationChallengeResponse>(request, state, HttpMethod.Post);
|
||||||
|
if (!challengeResult.IsSuccess || challengeResult.Value == null)
|
||||||
|
return challengeResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
|
||||||
|
|
||||||
|
var challengeResponse = challengeResult.Value;
|
||||||
|
|
||||||
if (StatusEquals(challengeResponse.Result?.Status, OrderStatus.Valid))
|
if (StatusEquals(challengeResponse.Result?.Status, OrderStatus.Valid))
|
||||||
continue;
|
continue;
|
||||||
@ -301,16 +342,13 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
if (state.Cache != null)
|
if (state.Cache != null)
|
||||||
state.Cache.ChallengeType = challengeType;
|
state.Cache.ChallengeType = challengeType;
|
||||||
|
|
||||||
var keyToken = state.JwsService != null
|
if (!JwkThumbprintUtility.TryGetKeyAuthorization(state.Jwk, challenge.Token, out var keyToken, out var errorMessage))
|
||||||
? state.JwsService.GetKeyAuthorization(challenge.Token)
|
return Result<Dictionary<string, string>?>.InternalServerError(null, errorMessage);
|
||||||
: string.Empty;
|
|
||||||
|
|
||||||
switch (challengeType) {
|
switch (challengeType) {
|
||||||
case "dns-01":
|
case "dns-01":
|
||||||
using (var sha256 = SHA256.Create()) {
|
using (var sha256 = SHA256.Create()) {
|
||||||
var dnsToken = state.JwsService != null
|
var dnsToken = Base64UrlUtility.Encode(sha256.ComputeHash(Encoding.UTF8.GetBytes(keyToken ?? string.Empty)));
|
||||||
? Base64UrlUtility.Encode(sha256.ComputeHash(Encoding.UTF8.GetBytes(keyToken ?? string.Empty)))
|
|
||||||
: string.Empty;
|
|
||||||
|
|
||||||
results[challengeResponse.Result?.Identifier?.Value ?? string.Empty] = dnsToken;
|
results[challengeResponse.Result?.Identifier?.Value ?? string.Empty] = dnsToken;
|
||||||
}
|
}
|
||||||
@ -352,22 +390,27 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
|
var request = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
|
||||||
|
|
||||||
var nonceResult = await HandleNonceAsync(sessionId, challenge.Url);
|
var nonceResult = await GetNonceAsync(sessionId, challenge.Url);
|
||||||
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
||||||
return nonceResult;
|
return nonceResult;
|
||||||
|
|
||||||
var nonce = nonceResult.Value;
|
var nonce = nonceResult.Value;
|
||||||
|
|
||||||
var json = EncodeMessage(false, "{}", state, new ACMEJwsHeader {
|
var jsonResult = EncodeMessage(sessionId, false, "{}", new ACMEJwsHeader {
|
||||||
Url = challenge.Url.ToString(),
|
Url = challenge.Url.ToString(),
|
||||||
Nonce = nonce
|
Nonce = nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!jsonResult.IsSuccess || jsonResult.Value == null)
|
||||||
|
return jsonResult;
|
||||||
|
|
||||||
|
var json = jsonResult.Value;
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var authChallenge = await SendAcmeRequest<AuthorizationChallengeResponse>(request, state, HttpMethod.Post);
|
var authChallenge = await SendAcmeRequest<AuthorizationChallengeResponse>(request, state, HttpMethod.Post);
|
||||||
|
|
||||||
var result = await PollChallengeStatus(sessionId, challenge, state);
|
var result = await PollChallengeStatus(sessionId, challenge);
|
||||||
|
|
||||||
if (!result.IsSuccess)
|
if (!result.IsSuccess)
|
||||||
return result;
|
return result;
|
||||||
@ -397,20 +440,29 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory!.NewOrder);
|
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory!.NewOrder);
|
||||||
|
|
||||||
var nonceResult = await HandleNonceAsync(sessionId, state.Directory.NewOrder);
|
var nonceResult = await GetNonceAsync(sessionId, state.Directory.NewOrder);
|
||||||
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
||||||
return nonceResult;
|
return nonceResult;
|
||||||
|
|
||||||
var nonce = nonceResult.Value;
|
var nonce = nonceResult.Value;
|
||||||
|
|
||||||
var json = EncodeMessage(false, letsEncryptOrder, state, new ACMEJwsHeader {
|
var jsonResult = EncodeMessage(sessionId, false, letsEncryptOrder, new ACMEJwsHeader {
|
||||||
Url = state.Directory.NewOrder.ToString(),
|
Url = state.Directory.NewOrder.ToString(),
|
||||||
Nonce = nonce
|
Nonce = nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!jsonResult.IsSuccess || jsonResult.Value == null)
|
||||||
|
return jsonResult;
|
||||||
|
|
||||||
|
var json = jsonResult.Value;
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
var requestResult = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
||||||
|
if (!requestResult.IsSuccess || requestResult.Value == null)
|
||||||
|
return requestResult;
|
||||||
|
|
||||||
|
var order = requestResult.Value;
|
||||||
|
|
||||||
state.CurrentOrder = order.Result;
|
state.CurrentOrder = order.Result;
|
||||||
|
|
||||||
@ -462,38 +514,56 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
if (StatusEquals(status, OrderStatus.Ready)) {
|
if (StatusEquals(status, OrderStatus.Ready)) {
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, state.CurrentOrder.Finalize);
|
var request = new HttpRequestMessage(HttpMethod.Post, state.CurrentOrder.Finalize);
|
||||||
|
|
||||||
var nonceResult = await HandleNonceAsync(sessionId, state.CurrentOrder.Finalize);
|
var nonceResult = await GetNonceAsync(sessionId, state.CurrentOrder.Finalize);
|
||||||
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
||||||
return nonceResult;
|
return nonceResult;
|
||||||
|
|
||||||
var nonce = nonceResult.Value;
|
var nonce = nonceResult.Value;
|
||||||
|
|
||||||
var json = EncodeMessage(false, letsEncryptOrder, state, new ACMEJwsHeader {
|
var jsonResult = EncodeMessage(sessionId, false, letsEncryptOrder, new ACMEJwsHeader {
|
||||||
Url = state.CurrentOrder.Finalize.ToString(),
|
Url = state.CurrentOrder.Finalize.ToString(),
|
||||||
Nonce = nonce
|
Nonce = nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!jsonResult.IsSuccess || jsonResult.Value == null)
|
||||||
|
return jsonResult;
|
||||||
|
|
||||||
|
var json = jsonResult.Value;
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
var orderResult = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
||||||
|
if (!orderResult.IsSuccess || orderResult.Value == null)
|
||||||
|
return orderResult;
|
||||||
|
|
||||||
|
var order = orderResult.Value;
|
||||||
|
|
||||||
if (StatusEquals(order.Result?.Status, OrderStatus.Processing)) {
|
if (StatusEquals(order.Result?.Status, OrderStatus.Processing)) {
|
||||||
request = new HttpRequestMessage(HttpMethod.Post, state.CurrentOrder.Location!);
|
request = new HttpRequestMessage(HttpMethod.Post, state.CurrentOrder.Location!);
|
||||||
|
|
||||||
nonceResult = await HandleNonceAsync(sessionId, state.CurrentOrder.Location);
|
nonceResult = await GetNonceAsync(sessionId, state.CurrentOrder.Location);
|
||||||
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
||||||
return nonceResult;
|
return nonceResult;
|
||||||
|
|
||||||
nonce = nonceResult.Value;
|
nonce = nonceResult.Value;
|
||||||
|
|
||||||
json = EncodeMessage(true, null, state, new ACMEJwsHeader {
|
jsonResult = EncodeMessage(sessionId, true, null, new ACMEJwsHeader {
|
||||||
Url = state.CurrentOrder.Location.ToString(),
|
Url = state.CurrentOrder.Location.ToString(),
|
||||||
Nonce = nonce
|
Nonce = nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!jsonResult.IsSuccess || jsonResult.Value == null)
|
||||||
|
return jsonResult;
|
||||||
|
|
||||||
|
json = jsonResult.Value;
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
orderResult = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
||||||
|
if (!orderResult.IsSuccess || orderResult.Value == null)
|
||||||
|
return orderResult;
|
||||||
|
|
||||||
|
order = orderResult.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StatusEquals(order.Result?.Status, OrderStatus.Valid)) {
|
if (StatusEquals(order.Result?.Status, OrderStatus.Valid)) {
|
||||||
@ -513,20 +583,29 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
var finalRequest = new HttpRequestMessage(HttpMethod.Post, certificateUrl!);
|
var finalRequest = new HttpRequestMessage(HttpMethod.Post, certificateUrl!);
|
||||||
|
|
||||||
var finalNonceResult = await HandleNonceAsync(sessionId, certificateUrl);
|
var finalNonceResult = await GetNonceAsync(sessionId, certificateUrl);
|
||||||
if (!finalNonceResult.IsSuccess || finalNonceResult.Value == null)
|
if (!finalNonceResult.IsSuccess || finalNonceResult.Value == null)
|
||||||
return finalNonceResult;
|
return finalNonceResult;
|
||||||
|
|
||||||
var finalNonce = finalNonceResult.Value;
|
var finalNonce = finalNonceResult.Value;
|
||||||
|
|
||||||
var finalJson = EncodeMessage(true, null, state, new ACMEJwsHeader {
|
var finalJsonResult = EncodeMessage(sessionId, true, null, new ACMEJwsHeader {
|
||||||
Url = certificateUrl.ToString(),
|
Url = certificateUrl.ToString(),
|
||||||
Nonce = finalNonce
|
Nonce = finalNonce
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!finalJsonResult.IsSuccess || finalJsonResult.Value == null)
|
||||||
|
return finalJsonResult;
|
||||||
|
|
||||||
|
var finalJson = finalJsonResult.Value;
|
||||||
|
|
||||||
PrepareRequestContent(finalRequest, finalJson, HttpMethod.Post);
|
PrepareRequestContent(finalRequest, finalJson, HttpMethod.Post);
|
||||||
|
|
||||||
var pem = await SendAcmeRequest<string>(finalRequest, state, HttpMethod.Post);
|
var requestResult = await SendAcmeRequest<string>(finalRequest, state, HttpMethod.Post);
|
||||||
|
if (!requestResult.IsSuccess || requestResult.Value == null)
|
||||||
|
return requestResult;
|
||||||
|
|
||||||
|
var pem = requestResult.Value;
|
||||||
|
|
||||||
if (state.Cache == null) {
|
if (state.Cache == null) {
|
||||||
_logger.LogError($"{nameof(state.Cache)} is null");
|
_logger.LogError($"{nameof(state.Cache)} is null");
|
||||||
@ -555,10 +634,13 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Key change
|
||||||
public Task<Result> KeyChange(Guid sessionId) {
|
public Task<Result> KeyChange(Guid sessionId) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region RevokeCertificate
|
||||||
public async Task<Result> RevokeCertificate(Guid sessionId, string subject, RevokeReason reason) {
|
public async Task<Result> RevokeCertificate(Guid sessionId, string subject, RevokeReason reason) {
|
||||||
try {
|
try {
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
@ -590,7 +672,7 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory!.RevokeCert);
|
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory!.RevokeCert);
|
||||||
|
|
||||||
var nonceResult = await HandleNonceAsync(sessionId, state.Directory.RevokeCert);
|
var nonceResult = await GetNonceAsync(sessionId, state.Directory.RevokeCert);
|
||||||
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
||||||
return nonceResult;
|
return nonceResult;
|
||||||
|
|
||||||
@ -601,7 +683,11 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
Nonce = nonce
|
Nonce = nonce
|
||||||
};
|
};
|
||||||
|
|
||||||
var json = state.JwsService.Encode(revokeRequest, jwsHeader).ToJson();
|
if (!JwsGenerator.TryEncode(state.Rsa, state.Jwk, jwsHeader, revokeRequest, out var jwsMessage, out var errorMessage)) {
|
||||||
|
return Result.InternalServerError(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = jwsMessage.ToJson();
|
||||||
|
|
||||||
request.Content = new StringContent(json);
|
request.Content = new StringContent(json);
|
||||||
|
|
||||||
@ -628,27 +714,36 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
return HandleUnhandledException(ex);
|
return HandleUnhandledException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region SendAsync
|
#region Internal helpers
|
||||||
private async Task<Result<string?>> HandleNonceAsync(Guid sessionId, Uri uri) {
|
private State GetOrCreateState(Guid sessionId) {
|
||||||
|
if (!_memoryCache.TryGetValue(sessionId, out State? state) || state == null) {
|
||||||
|
state = new State();
|
||||||
|
_memoryCache.Set(sessionId, state, TimeSpan.FromHours(1));
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Result<string?>> GetNonceAsync(Guid sessionId, Uri uri) {
|
||||||
if (uri == null)
|
if (uri == null)
|
||||||
throw new ArgumentNullException(nameof(uri));
|
return Result<string?>.InternalServerError(null, "URI is null");
|
||||||
|
|
||||||
if (uri.OriginalString == "directory")
|
|
||||||
return Result<string?>.Ok(null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(HandleNonceAsync)}...");
|
_logger.LogInformation($"Executing {nameof(GetNonceAsync)}...");
|
||||||
|
|
||||||
if (state.Directory?.NewNonce == null)
|
if (state.Directory?.NewNonce == null)
|
||||||
return Result<string?>.InternalServerError(null);
|
return Result<string?>.InternalServerError(null, $"{nameof(state.Directory.NewNonce)} is null");
|
||||||
|
|
||||||
var result = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, state.Directory.NewNonce));
|
var result = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, state.Directory.NewNonce));
|
||||||
|
|
||||||
var nonce = result.Headers.GetValues("Replay-Nonce").FirstOrDefault();
|
var nonce = result.Headers.GetValues("Replay-Nonce").FirstOrDefault();
|
||||||
|
|
||||||
|
if (nonce == null)
|
||||||
|
return Result<string?>.InternalServerError(null, "Nonce is null");
|
||||||
|
|
||||||
return Result<string?>.Ok(nonce);
|
return Result<string?>.Ok(nonce);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
@ -656,12 +751,44 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: Send ACME request and process response
|
||||||
|
private async Task<Result<SendResult<T>?>> SendAcmeRequest<T>(HttpRequestMessage request, State state, HttpMethod method) {
|
||||||
|
try {
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
HandleProblemResponseAsync(response, responseText);
|
||||||
|
|
||||||
|
var sendResult = ProcessResponseContent<T>(response, responseText);
|
||||||
|
|
||||||
|
return Result<SendResult<T>?>.Ok(sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex) {
|
||||||
|
return HandleUnhandledException<SendResult<T>?>(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result<string?> EncodeMessage(Guid sessionId, bool isPostAsGet, object? requestModel, ACMEJwsHeader protectedHeader) {
|
||||||
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
|
JwsMessage jwsMessage;
|
||||||
|
string errorMessage;
|
||||||
|
|
||||||
|
if (isPostAsGet) {
|
||||||
|
if (!JwsGenerator.TryEncode(state.Rsa, state.Jwk, protectedHeader, out jwsMessage, out errorMessage))
|
||||||
|
return Result<string?>.InternalServerError(errorMessage);
|
||||||
|
|
||||||
|
return Result<string?>.Ok(jwsMessage.ToJson());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!JwsGenerator.TryEncode(state.Rsa, state.Jwk, protectedHeader, requestModel, out jwsMessage, out errorMessage))
|
||||||
|
return Result<string?>.InternalServerError(errorMessage);
|
||||||
|
|
||||||
|
return Result<string?>.Ok(jwsMessage.ToJson());
|
||||||
|
}
|
||||||
|
|
||||||
private string EncodeMessage(bool isPostAsGet, object? requestModel, State state, ACMEJwsHeader jwsHeader) {
|
|
||||||
return isPostAsGet
|
|
||||||
? state.JwsService!.Encode(jwsHeader).ToJson()
|
|
||||||
: state.JwsService!.Encode(requestModel, jwsHeader).ToJson();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetContentType(ContentType type) => type.GetDisplayName();
|
private static string GetContentType(ContentType type) => type.GetDisplayName();
|
||||||
@ -674,56 +801,8 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
|
request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private State GetOrCreateState(Guid sessionId) {
|
|
||||||
if (!_memoryCache.TryGetValue(sessionId, out State? state) || state == null) {
|
|
||||||
state = new State();
|
|
||||||
_memoryCache.Set(sessionId, state, TimeSpan.FromHours(1));
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Send ACME request and process response
|
|
||||||
private async Task<SendResult<T>> SendAcmeRequest<T>(HttpRequestMessage request, State state, HttpMethod method) {
|
|
||||||
var response = await _httpClient.SendAsync(request);
|
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
HandleProblemResponseAsync(response, responseText);
|
|
||||||
|
|
||||||
return ProcessResponseContent<T>(response, responseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Helper: Poll challenge status until valid or timeout
|
// Helper: Poll challenge status until valid or timeout
|
||||||
private async Task<Result> PollChallengeStatus(Guid sessionId, AuthorizationChallengeChallenge challenge, State state) {
|
private async Task<Result> PollChallengeStatus(Guid sessionId, AuthorizationChallengeChallenge challenge) {
|
||||||
if (challenge?.Url == null)
|
if (challenge?.Url == null)
|
||||||
return Result.InternalServerError("Challenge URL is null");
|
return Result.InternalServerError("Challenge URL is null");
|
||||||
|
|
||||||
@ -732,17 +811,22 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
while (true) {
|
while (true) {
|
||||||
var pollRequest = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
|
var pollRequest = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
|
||||||
|
|
||||||
var nonceResult = await HandleNonceAsync(sessionId, challenge.Url);
|
var nonceResult = await GetNonceAsync(sessionId, challenge.Url);
|
||||||
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
if (!nonceResult.IsSuccess || nonceResult.Value == null)
|
||||||
return nonceResult;
|
return nonceResult;
|
||||||
|
|
||||||
var nonce = nonceResult.Value;
|
var nonce = nonceResult.Value;
|
||||||
|
|
||||||
var pollJson = EncodeMessage(true, null, state, new ACMEJwsHeader {
|
var pollJsonResult = EncodeMessage(sessionId, true, null, new ACMEJwsHeader {
|
||||||
Url = challenge.Url.ToString(),
|
Url = challenge.Url.ToString(),
|
||||||
Nonce = nonce
|
Nonce = nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!pollJsonResult.IsSuccess || pollJsonResult.Value == null)
|
||||||
|
return pollJsonResult;
|
||||||
|
|
||||||
|
var pollJson = pollJsonResult.Value;
|
||||||
|
|
||||||
PrepareRequestContent(pollRequest, pollJson, HttpMethod.Post);
|
PrepareRequestContent(pollRequest, pollJson, HttpMethod.Post);
|
||||||
|
|
||||||
var pollResponse = await _httpClient.SendAsync(pollRequest);
|
var pollResponse = await _httpClient.SendAsync(pollRequest);
|
||||||
@ -763,22 +847,6 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void HandleProblemResponseAsync(HttpResponseMessage response, string responseText) {
|
private void HandleProblemResponseAsync(HttpResponseMessage response, string responseText) {
|
||||||
if (response.Content.Headers.ContentType?.MediaType == GetContentType(ContentType.ProblemJson)) {
|
if (response.Content.Headers.ContentType?.MediaType == GetContentType(ContentType.ProblemJson)) {
|
||||||
var problem = responseText.ToObject<Problem>();
|
var problem = responseText.ToObject<Problem>();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user