mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): improve logging and result messages
This commit is contained in:
parent
b413f2bf3a
commit
950b858af7
@ -13,7 +13,6 @@ using MaksIT.LetsEncrypt.Models.Interfaces;
|
|||||||
using MaksIT.LetsEncrypt.Models.Requests;
|
using MaksIT.LetsEncrypt.Models.Requests;
|
||||||
using MaksIT.LetsEncrypt.Models.Responses;
|
using MaksIT.LetsEncrypt.Models.Responses;
|
||||||
using MaksIT.Results;
|
using MaksIT.Results;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
@ -68,33 +67,51 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
// Helper: Send ACME request and process response
|
// Helper: Send ACME request and process response
|
||||||
private async Task<SendResult<T>> SendAcmeRequest<T>(HttpRequestMessage request, State state, HttpMethod method) {
|
private async Task<SendResult<T>> SendAcmeRequest<T>(HttpRequestMessage request, State state, HttpMethod method) {
|
||||||
var response = await _httpClient.SendAsync(request);
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
|
||||||
UpdateStateNonceIfNeeded(response, state, method);
|
UpdateStateNonceIfNeeded(response, state, method);
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
HandleProblemResponseAsync(response, responseText);
|
HandleProblemResponseAsync(response, responseText);
|
||||||
|
|
||||||
return ProcessResponseContent<T>(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, State state) {
|
||||||
if (challenge?.Url == null) return Result.InternalServerError("Challenge URL is null");
|
if (challenge?.Url == null)
|
||||||
|
return Result.InternalServerError("Challenge URL is null");
|
||||||
|
|
||||||
var start = DateTime.UtcNow;
|
var start = DateTime.UtcNow;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var pollRequest = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
|
var pollRequest = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, challenge.Url, state);
|
await HandleNonceAsync(sessionId, challenge.Url, state);
|
||||||
|
|
||||||
var pollJson = EncodeMessage(true, null, state, new JwsHeader {
|
var pollJson = EncodeMessage(true, null, state, new JwsHeader {
|
||||||
Url = challenge.Url,
|
Url = challenge.Url,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
PrepareRequestContent(pollRequest, pollJson, HttpMethod.Post);
|
PrepareRequestContent(pollRequest, pollJson, HttpMethod.Post);
|
||||||
|
|
||||||
var pollResponse = await _httpClient.SendAsync(pollRequest);
|
var pollResponse = await _httpClient.SendAsync(pollRequest);
|
||||||
|
|
||||||
UpdateStateNonceIfNeeded(pollResponse, state, HttpMethod.Post);
|
UpdateStateNonceIfNeeded(pollResponse, state, HttpMethod.Post);
|
||||||
|
|
||||||
var pollResponseText = await pollResponse.Content.ReadAsStringAsync();
|
var pollResponseText = await pollResponse.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
HandleProblemResponseAsync(pollResponse, pollResponseText);
|
HandleProblemResponseAsync(pollResponse, pollResponseText);
|
||||||
|
|
||||||
var authChallenge = ProcessResponseContent<AuthorizationChallengeResponse>(pollResponse, pollResponseText);
|
var authChallenge = ProcessResponseContent<AuthorizationChallengeResponse>(pollResponse, pollResponseText);
|
||||||
|
|
||||||
if (authChallenge.Result?.Status != "pending")
|
if (authChallenge.Result?.Status != "pending")
|
||||||
return authChallenge.Result?.Status == "valid" ? Result.Ok() : Result.InternalServerError();
|
return authChallenge.Result?.Status == "valid" ? Result.Ok() : Result.InternalServerError();
|
||||||
|
|
||||||
if ((DateTime.UtcNow - start).Seconds > 120)
|
if ((DateTime.UtcNow - start).Seconds > 120)
|
||||||
return Result.InternalServerError("Timeout");
|
return Result.InternalServerError("Timeout");
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,16 +120,30 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
public async Task<Result> ConfigureClient(Guid sessionId, bool isStaging) {
|
public async Task<Result> ConfigureClient(Guid sessionId, bool isStaging) {
|
||||||
try {
|
try {
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
state.IsStaging = isStaging;
|
state.IsStaging = isStaging;
|
||||||
|
|
||||||
_httpClient.BaseAddress ??= new Uri(isStaging ? _appSettings.Staging : _appSettings.Production);
|
_httpClient.BaseAddress ??= new Uri(isStaging ? _appSettings.Staging : _appSettings.Production);
|
||||||
|
|
||||||
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);
|
await HandleNonceAsync(sessionId, new Uri(DirectoryEndpoint, UriKind.Relative), state);
|
||||||
|
|
||||||
var directory = await SendAcmeRequest<AcmeDirectory>(request, state, HttpMethod.Get);
|
var directory = await SendAcmeRequest<AcmeDirectory>(request, state, HttpMethod.Get);
|
||||||
|
|
||||||
state.Directory = directory.Result ?? throw new InvalidOperationException("Directory response is null");
|
state.Directory = directory.Result ?? throw new InvalidOperationException("Directory response is null");
|
||||||
}
|
}
|
||||||
return Result.Ok();
|
|
||||||
} catch (Exception ex) {
|
return Result.Ok("Client configured successfully.");
|
||||||
|
}
|
||||||
|
catch (LetsEncrytException ex) {
|
||||||
|
List<string> messages = ["Let's Encrypt client encountered a problem"];
|
||||||
|
_logger.LogError(ex, messages.FirstOrDefault());
|
||||||
|
messages.Add(ex.Message);
|
||||||
|
return Result.InternalServerError([.. messages]);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
const string message = "Let's Encrypt client unhandled exception";
|
const string message = "Let's Encrypt client unhandled exception";
|
||||||
_logger.LogError(ex, message);
|
_logger.LogError(ex, message);
|
||||||
return Result.InternalServerError(message);
|
return Result.InternalServerError(message);
|
||||||
@ -123,45 +154,68 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
#region Init
|
#region Init
|
||||||
public async Task<Result> Init(Guid sessionId, Guid accountId, string description, string[] contacts, RegistrationCache? cache) {
|
public async Task<Result> Init(Guid sessionId, Guid accountId, string description, string[] contacts, RegistrationCache? cache) {
|
||||||
if (sessionId == Guid.Empty) {
|
if (sessionId == Guid.Empty) {
|
||||||
_logger.LogError("Invalid sessionId");
|
const string message = "Invalid sessionId";
|
||||||
return Result.InternalServerError();
|
_logger.LogError(message);
|
||||||
|
return Result.InternalServerError(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contacts == null || contacts.Length == 0) {
|
if (contacts == null || contacts.Length == 0) {
|
||||||
_logger.LogError("Contacts are null or empty");
|
const string message = "Contacts are null or empty";
|
||||||
return Result.InternalServerError();
|
_logger.LogError(message);
|
||||||
|
return Result.InternalServerError(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
if (state.Directory == null) {
|
if (state.Directory == null) {
|
||||||
_logger.LogError("State directory is null");
|
const string message = "State directory is null";
|
||||||
return Result.InternalServerError();
|
_logger.LogError(message);
|
||||||
|
return Result.InternalServerError(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(Init)}...");
|
_logger.LogInformation($"Executing {nameof(Init)}...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var accountKey = new RSACryptoServiceProvider(4096);
|
var accountKey = new RSACryptoServiceProvider(4096);
|
||||||
|
|
||||||
if (cache != null && cache.AccountKey != null) {
|
if (cache != null && cache.AccountKey != null) {
|
||||||
state.Cache = cache;
|
state.Cache = cache;
|
||||||
|
|
||||||
accountKey.ImportCspBlob(cache.AccountKey);
|
accountKey.ImportCspBlob(cache.AccountKey);
|
||||||
|
|
||||||
state.JwsService = new JwsService(accountKey);
|
state.JwsService = new JwsService(accountKey);
|
||||||
|
|
||||||
state.JwsService.SetKeyId(cache.Location?.ToString() ?? string.Empty);
|
state.JwsService.SetKeyId(cache.Location?.ToString() ?? string.Empty);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
state.JwsService = new JwsService(accountKey);
|
state.JwsService = new JwsService(accountKey);
|
||||||
|
|
||||||
var letsEncryptOrder = new Account {
|
var letsEncryptOrder = new Account {
|
||||||
TermsOfServiceAgreed = true,
|
TermsOfServiceAgreed = true,
|
||||||
Contacts = contacts.Select(contact => $"mailto:{contact}").ToArray()
|
Contacts = [.. contacts.Select(contact => $"mailto:{contact}")]
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewAccount);
|
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewAccount);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, state.Directory.NewAccount, state);
|
await HandleNonceAsync(sessionId, state.Directory.NewAccount, state);
|
||||||
|
|
||||||
var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader {
|
var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader {
|
||||||
Url = state.Directory.NewAccount,
|
Url = state.Directory.NewAccount,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var result = await SendAcmeRequest<Account>(request, state, HttpMethod.Post);
|
var result = await SendAcmeRequest<Account>(request, state, HttpMethod.Post);
|
||||||
|
|
||||||
state.JwsService.SetKeyId(result.Result?.Location?.ToString() ?? string.Empty);
|
state.JwsService.SetKeyId(result.Result?.Location?.ToString() ?? string.Empty);
|
||||||
|
|
||||||
if (result.Result?.Status != "valid") {
|
if (result.Result?.Status != "valid") {
|
||||||
_logger.LogError($"Account status is not valid, was: {result.Result?.Status} \r\n {result.ResponseText}");
|
var errorMessage = $"Account status is not valid, was: {result.Result?.Status} \r\n {result.ResponseText}";
|
||||||
return Result.InternalServerError();
|
_logger.LogError(errorMessage);
|
||||||
|
return Result.InternalServerError(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Cache = new RegistrationCache {
|
state.Cache = new RegistrationCache {
|
||||||
AccountId = accountId,
|
AccountId = accountId,
|
||||||
Description = description,
|
Description = description,
|
||||||
@ -173,8 +227,16 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
Key = result.Result.Key
|
Key = result.Result.Key
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return Result.Ok();
|
|
||||||
} catch (Exception ex) {
|
return Result.Ok("Initialization successful.");
|
||||||
|
}
|
||||||
|
catch (LetsEncrytException ex) {
|
||||||
|
List<string> messages = ["Let's Encrypt client encountered a problem"];
|
||||||
|
_logger.LogError(ex, messages.FirstOrDefault());
|
||||||
|
messages.Add(ex.Message);
|
||||||
|
return Result.InternalServerError([.. messages]);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
const string message = "Let's Encrypt client unhandled exception";
|
const string message = "Let's Encrypt client unhandled exception";
|
||||||
_logger.LogError(ex, message);
|
_logger.LogError(ex, message);
|
||||||
return Result.InternalServerError(message);
|
return Result.InternalServerError(message);
|
||||||
@ -184,8 +246,10 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
public Result<RegistrationCache?> GetRegistrationCache(Guid sessionId) {
|
public Result<RegistrationCache?> GetRegistrationCache(Guid sessionId) {
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
if(state?.Cache == null)
|
if(state?.Cache == null)
|
||||||
return Result<RegistrationCache?>.InternalServerError(null);
|
return Result<RegistrationCache?>.InternalServerError(null);
|
||||||
|
|
||||||
return Result<RegistrationCache?>.Ok(state.Cache);
|
return Result<RegistrationCache?>.Ok(state.Cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,15 +257,20 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
public Result<string?> GetTermsOfServiceUri(Guid sessionId) {
|
public Result<string?> GetTermsOfServiceUri(Guid sessionId) {
|
||||||
try {
|
try {
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(GetTermsOfServiceUri)}...");
|
_logger.LogInformation($"Executing {nameof(GetTermsOfServiceUri)}...");
|
||||||
|
|
||||||
if (state.Directory?.Meta?.TermsOfService == null) {
|
if (state.Directory?.Meta?.TermsOfService == null) {
|
||||||
return Result<string?>.Ok(null);
|
return Result<string?>.Ok(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result<string?>.Ok(state.Directory.Meta.TermsOfService);
|
return Result<string?>.Ok(state.Directory.Meta.TermsOfService);
|
||||||
} catch (Exception ex) {
|
}
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
catch (Exception ex) {
|
||||||
_logger.LogError(ex, message);
|
List<string> messages = new List<string> { "Let's Encrypt client unhandled exception" };
|
||||||
return Result<string?>.InternalServerError(message);
|
_logger.LogError(ex, messages.FirstOrDefault());
|
||||||
|
messages.Add(ex.Message);
|
||||||
|
return Result<string?>.InternalServerError(null, [.. messages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@ -210,61 +279,96 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
public async Task<Result<Dictionary<string, string>?>> NewOrder(Guid sessionId, string[] hostnames, string challengeType) {
|
public async Task<Result<Dictionary<string, string>?>> NewOrder(Guid sessionId, string[] hostnames, string challengeType) {
|
||||||
try {
|
try {
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(NewOrder)}...");
|
_logger.LogInformation($"Executing {nameof(NewOrder)}...");
|
||||||
|
|
||||||
state.Challenges.Clear();
|
state.Challenges.Clear();
|
||||||
|
|
||||||
var letsEncryptOrder = new Order {
|
var letsEncryptOrder = new Order {
|
||||||
Expires = DateTime.UtcNow.AddDays(2),
|
Expires = DateTime.UtcNow.AddDays(2),
|
||||||
Identifiers = hostnames?.Where(h => h != null).Select(hostname => new OrderIdentifier {
|
Identifiers = hostnames?.Where(h => h != null).Select(hostname => new OrderIdentifier {
|
||||||
Type = DnsType,
|
Type = DnsType,
|
||||||
Value = hostname ?? string.Empty
|
Value = hostname ?? string.Empty
|
||||||
}).ToArray() ?? Array.Empty<OrderIdentifier>()
|
}).ToArray() ?? []
|
||||||
};
|
};
|
||||||
|
|
||||||
if (state.Directory == null || state.Directory.NewOrder == null)
|
if (state.Directory == null || state.Directory.NewOrder == null)
|
||||||
return Result<Dictionary<string, string>?>.InternalServerError(null);
|
return Result<Dictionary<string, string>?>.InternalServerError(null);
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewOrder);
|
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewOrder);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, state.Directory.NewOrder, state);
|
await HandleNonceAsync(sessionId, state.Directory.NewOrder, state);
|
||||||
|
|
||||||
var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader {
|
var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader {
|
||||||
Url = state.Directory.NewOrder,
|
Url = state.Directory.NewOrder,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
||||||
|
|
||||||
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>());
|
||||||
|
|
||||||
if (!StatusEquals(order.Result?.Status, OrderStatus.Pending)) {
|
if (!StatusEquals(order.Result?.Status, OrderStatus.Pending)) {
|
||||||
_logger.LogError($"Created new order and expected status '{OrderStatus.Pending.GetDisplayName()}', but got: {order.Result?.Status} \r\n {order.Result}");
|
_logger.LogError($"Created new order and expected status '{OrderStatus.Pending.GetDisplayName()}', but got: {order.Result?.Status} \r\n {order.Result}");
|
||||||
return Result<Dictionary<string, string>?>.InternalServerError(null);
|
return Result<Dictionary<string, string>?>.InternalServerError(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.CurrentOrder = order.Result;
|
state.CurrentOrder = order.Result;
|
||||||
|
|
||||||
var results = new Dictionary<string, string>();
|
var results = new Dictionary<string, string>();
|
||||||
|
|
||||||
foreach (var item in state.CurrentOrder?.Authorizations ?? Array.Empty<Uri>()) {
|
foreach (var item in state.CurrentOrder?.Authorizations ?? Array.Empty<Uri>()) {
|
||||||
if (item == null) continue;
|
if (item == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
request = new HttpRequestMessage(HttpMethod.Post, item);
|
request = new HttpRequestMessage(HttpMethod.Post, item);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, item, state);
|
await HandleNonceAsync(sessionId, item, state);
|
||||||
|
|
||||||
json = EncodeMessage(true, null, state, new JwsHeader {
|
json = EncodeMessage(true, null, state, new JwsHeader {
|
||||||
Url = item,
|
Url = item,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var challengeResponse = await SendAcmeRequest<AuthorizationChallengeResponse>(request, state, HttpMethod.Post);
|
var challengeResponse = await SendAcmeRequest<AuthorizationChallengeResponse>(request, state, HttpMethod.Post);
|
||||||
|
|
||||||
if (StatusEquals(challengeResponse.Result?.Status, OrderStatus.Valid))
|
if (StatusEquals(challengeResponse.Result?.Status, OrderStatus.Valid))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!StatusEquals(challengeResponse.Result?.Status, OrderStatus.Pending)) {
|
if (!StatusEquals(challengeResponse.Result?.Status, OrderStatus.Pending)) {
|
||||||
_logger.LogError($"Expected authorization status '{OrderStatus.Pending.GetDisplayName()}', but got: {state.CurrentOrder?.Status} \r\n {challengeResponse.ResponseText}");
|
_logger.LogError($"Expected authorization status '{OrderStatus.Pending.GetDisplayName()}', but got: {state.CurrentOrder?.Status} \r\n {challengeResponse.ResponseText}");
|
||||||
return Result<Dictionary<string, string>?>.InternalServerError(null);
|
return Result<Dictionary<string, string>?>.InternalServerError(null);
|
||||||
}
|
}
|
||||||
var challenge = challengeResponse.Result?.Challenges?.FirstOrDefault(x => x?.Type == challengeType);
|
|
||||||
|
var challenge = challengeResponse.Result?.Challenges?
|
||||||
|
.FirstOrDefault(x => x?.Type == challengeType);
|
||||||
|
|
||||||
if (challenge == null || challenge.Token == null) {
|
if (challenge == null || challenge.Token == null) {
|
||||||
_logger.LogError("Challenge or token is null");
|
_logger.LogError("Challenge or token is null");
|
||||||
return Result<Dictionary<string, string>?>.InternalServerError(null);
|
return Result<Dictionary<string, string>?>.InternalServerError(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Challenges.Add(challenge);
|
state.Challenges.Add(challenge);
|
||||||
if (state.Cache != null) state.Cache.ChallengeType = challengeType;
|
|
||||||
var keyToken = state.JwsService != null ? state.JwsService.GetKeyAuthorization(challenge.Token) : string.Empty;
|
if (state.Cache != null)
|
||||||
|
state.Cache.ChallengeType = challengeType;
|
||||||
|
|
||||||
|
var keyToken = state.JwsService != null
|
||||||
|
? state.JwsService.GetKeyAuthorization(challenge.Token)
|
||||||
|
: 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 ? state.JwsService.Base64UrlEncoded(sha256.ComputeHash(Encoding.UTF8.GetBytes(keyToken ?? string.Empty))) : string.Empty;
|
var dnsToken = state.JwsService != null
|
||||||
|
? state.JwsService.Base64UrlEncoded(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;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -277,10 +381,12 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Result<Dictionary<string, string>?>.Ok(results);
|
return Result<Dictionary<string, string>?>.Ok(results);
|
||||||
} catch (Exception ex) {
|
}
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
catch (Exception ex) {
|
||||||
_logger.LogError(ex, message);
|
List<string> messages = new List<string> { "Let's Encrypt client unhandled exception" };
|
||||||
return Result<Dictionary<string, string>?>.InternalServerError(null, message);
|
_logger.LogError(ex, messages.FirstOrDefault());
|
||||||
|
messages.Add(ex.Message);
|
||||||
|
return Result<Dictionary<string, string>?>.InternalServerError(null, [.. messages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@ -289,33 +395,46 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
public async Task<Result> CompleteChallenges(Guid sessionId) {
|
public async Task<Result> CompleteChallenges(Guid sessionId) {
|
||||||
try {
|
try {
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(CompleteChallenges)}...");
|
_logger.LogInformation($"Executing {nameof(CompleteChallenges)}...");
|
||||||
|
|
||||||
if (state.CurrentOrder?.Identifiers == null) {
|
if (state.CurrentOrder?.Identifiers == null) {
|
||||||
return Result.InternalServerError("Current order identifiers are null");
|
return Result.InternalServerError("Current order identifiers are null");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var index = 0; index < state.Challenges.Count; index++) {
|
for (var index = 0; index < state.Challenges.Count; index++) {
|
||||||
var challenge = state.Challenges[index];
|
var challenge = state.Challenges[index];
|
||||||
|
|
||||||
if (challenge?.Url == null) {
|
if (challenge?.Url == null) {
|
||||||
_logger.LogError("Challenge URL is null");
|
_logger.LogError("Challenge URL is null");
|
||||||
return Result.InternalServerError();
|
return Result.InternalServerError();
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
|
var request = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, challenge.Url, state);
|
await HandleNonceAsync(sessionId, challenge.Url, state);
|
||||||
|
|
||||||
var json = EncodeMessage(false, "{}", state, new JwsHeader {
|
var json = EncodeMessage(false, "{}", state, new JwsHeader {
|
||||||
Url = challenge.Url,
|
Url = challenge.Url,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
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, state);
|
||||||
|
|
||||||
if (!result.IsSuccess)
|
if (!result.IsSuccess)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return Result.Ok();
|
return Result.Ok();
|
||||||
} catch (Exception ex) {
|
}
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
catch (Exception ex) {
|
||||||
_logger.LogError(ex, message);
|
List<string> messages = new List<string> { "Let's Encrypt client unhandled exception" };
|
||||||
return Result.InternalServerError(message);
|
_logger.LogError(ex, messages.FirstOrDefault());
|
||||||
|
messages.Add(ex.Message);
|
||||||
|
return Result.InternalServerError([.. messages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@ -324,28 +443,39 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
public async Task<Result> GetOrder(Guid sessionId, string[] hostnames) {
|
public async Task<Result> GetOrder(Guid sessionId, string[] hostnames) {
|
||||||
try {
|
try {
|
||||||
_logger.LogInformation($"Executing {nameof(GetOrder)}");
|
_logger.LogInformation($"Executing {nameof(GetOrder)}");
|
||||||
|
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
var letsEncryptOrder = new Order {
|
var letsEncryptOrder = new Order {
|
||||||
Expires = DateTime.UtcNow.AddDays(2),
|
Expires = DateTime.UtcNow.AddDays(2),
|
||||||
Identifiers = hostnames?.Where(h => h != null).Select(hostname => new OrderIdentifier {
|
Identifiers = hostnames?.Where(h => h != null).Select(hostname => new OrderIdentifier {
|
||||||
Type = "dns",
|
Type = "dns",
|
||||||
Value = hostname!
|
Value = hostname!
|
||||||
}).ToArray() ?? Array.Empty<OrderIdentifier>()
|
}).ToArray() ?? []
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory!.NewOrder);
|
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory!.NewOrder);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, state.Directory.NewOrder, state);
|
await HandleNonceAsync(sessionId, state.Directory.NewOrder, state);
|
||||||
|
|
||||||
var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader {
|
var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader {
|
||||||
Url = state.Directory.NewOrder,
|
Url = state.Directory.NewOrder,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
||||||
|
|
||||||
state.CurrentOrder = order.Result;
|
state.CurrentOrder = order.Result;
|
||||||
|
|
||||||
return Result.Ok();
|
return Result.Ok();
|
||||||
} catch (Exception ex) {
|
}
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
catch (Exception ex) {
|
||||||
_logger.LogError(ex, message);
|
List<string> messages = new List<string> { "Let's Encrypt client unhandled exception" };
|
||||||
return Result.InternalServerError(message);
|
_logger.LogError(ex, messages.FirstOrDefault());
|
||||||
|
messages.Add(ex.Message);
|
||||||
|
return Result.InternalServerError([.. messages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@ -354,85 +484,122 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
public async Task<Result> GetCertificate(Guid sessionId, string subject) {
|
public async Task<Result> GetCertificate(Guid sessionId, string subject) {
|
||||||
try {
|
try {
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(GetCertificate)}...");
|
_logger.LogInformation($"Executing {nameof(GetCertificate)}...");
|
||||||
|
|
||||||
if (state.CurrentOrder?.Identifiers == null) {
|
if (state.CurrentOrder?.Identifiers == null) {
|
||||||
return Result.InternalServerError();
|
return Result.InternalServerError();
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = new RSACryptoServiceProvider(4096);
|
var key = new RSACryptoServiceProvider(4096);
|
||||||
var csr = new CertificateRequest("CN=" + subject,
|
var csr = new CertificateRequest("CN=" + subject, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||||
key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
||||||
var san = new SubjectAlternativeNameBuilder();
|
var san = new SubjectAlternativeNameBuilder();
|
||||||
|
|
||||||
foreach (var host in state.CurrentOrder.Identifiers) {
|
foreach (var host in state.CurrentOrder.Identifiers) {
|
||||||
if (host?.Value != null)
|
if (host?.Value != null)
|
||||||
san.AddDnsName(host.Value);
|
san.AddDnsName(host.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
csr.CertificateExtensions.Add(san.Build());
|
csr.CertificateExtensions.Add(san.Build());
|
||||||
|
|
||||||
var letsEncryptOrder = new FinalizeRequest {
|
var letsEncryptOrder = new FinalizeRequest {
|
||||||
Csr = state.JwsService!.Base64UrlEncoded(csr.CreateSigningRequest())
|
Csr = state.JwsService!.Base64UrlEncoded(csr.CreateSigningRequest())
|
||||||
};
|
};
|
||||||
|
|
||||||
Uri? certificateUrl = default;
|
Uri? certificateUrl = default;
|
||||||
|
|
||||||
var start = DateTime.UtcNow;
|
var start = DateTime.UtcNow;
|
||||||
|
|
||||||
while (certificateUrl == null) {
|
while (certificateUrl == null) {
|
||||||
var hostnames = state.CurrentOrder.Identifiers?.Select(x => x?.Value).Where(x => x != null).Cast<string>().ToArray() ?? Array.Empty<string>();
|
var hostnames = state.CurrentOrder?.Identifiers?.Select(x => x?.Value).Where(x => x != null).Cast<string>().ToArray() ?? [];
|
||||||
|
|
||||||
await GetOrder(sessionId, hostnames);
|
await GetOrder(sessionId, hostnames);
|
||||||
|
|
||||||
var status = state.CurrentOrder?.Status;
|
var status = state.CurrentOrder?.Status;
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, state.CurrentOrder.Finalize!, state);
|
await HandleNonceAsync(sessionId, state.CurrentOrder.Finalize!, state);
|
||||||
|
|
||||||
var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader {
|
var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader {
|
||||||
Url = state.CurrentOrder.Finalize,
|
Url = state.CurrentOrder.Finalize,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
||||||
|
|
||||||
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!);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, state.CurrentOrder.Location!, state);
|
await HandleNonceAsync(sessionId, state.CurrentOrder.Location!, state);
|
||||||
|
|
||||||
json = EncodeMessage(true, null, state, new JwsHeader {
|
json = EncodeMessage(true, null, state, new JwsHeader {
|
||||||
Url = state.CurrentOrder.Location,
|
Url = state.CurrentOrder.Location,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
PrepareRequestContent(request, json, HttpMethod.Post);
|
PrepareRequestContent(request, json, HttpMethod.Post);
|
||||||
|
|
||||||
order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StatusEquals(order.Result?.Status, OrderStatus.Valid)) {
|
if (StatusEquals(order.Result?.Status, OrderStatus.Valid)) {
|
||||||
certificateUrl = order.Result.Certificate;
|
certificateUrl = order.Result.Certificate;
|
||||||
}
|
}
|
||||||
} else if (StatusEquals(status, OrderStatus.Valid)) {
|
}
|
||||||
|
else if (StatusEquals(status, OrderStatus.Valid)) {
|
||||||
certificateUrl = state.CurrentOrder.Certificate;
|
certificateUrl = state.CurrentOrder.Certificate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((DateTime.UtcNow - start).Seconds > 120)
|
if ((DateTime.UtcNow - start).Seconds > 120)
|
||||||
throw new TimeoutException();
|
throw new TimeoutException();
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalRequest = new HttpRequestMessage(HttpMethod.Post, certificateUrl!);
|
var finalRequest = new HttpRequestMessage(HttpMethod.Post, certificateUrl!);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, certificateUrl!, state);
|
await HandleNonceAsync(sessionId, certificateUrl!, state);
|
||||||
|
|
||||||
var finalJson = EncodeMessage(true, null, state, new JwsHeader {
|
var finalJson = EncodeMessage(true, null, state, new JwsHeader {
|
||||||
Url = certificateUrl,
|
Url = certificateUrl,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
});
|
});
|
||||||
|
|
||||||
PrepareRequestContent(finalRequest, finalJson, HttpMethod.Post);
|
PrepareRequestContent(finalRequest, finalJson, HttpMethod.Post);
|
||||||
|
|
||||||
var pem = await SendAcmeRequest<string>(finalRequest, state, HttpMethod.Post);
|
var pem = await SendAcmeRequest<string>(finalRequest, state, HttpMethod.Post);
|
||||||
|
|
||||||
if (state.Cache == null) {
|
if (state.Cache == null) {
|
||||||
_logger.LogError($"{nameof(state.Cache)} is null");
|
_logger.LogError($"{nameof(state.Cache)} is null");
|
||||||
return Result.InternalServerError();
|
return Result.InternalServerError();
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Cache.CachedCerts ??= new Dictionary<string, CertificateCache>();
|
state.Cache.CachedCerts ??= new Dictionary<string, CertificateCache>();
|
||||||
|
|
||||||
state.Cache.CachedCerts[subject] = new CertificateCache {
|
state.Cache.CachedCerts[subject] = new CertificateCache {
|
||||||
Cert = pem.Result ?? string.Empty,
|
Cert = pem.Result ?? string.Empty,
|
||||||
Private = key.ExportCspBlob(true),
|
Private = key.ExportCspBlob(true),
|
||||||
PrivatePem = key.ExportRSAPrivateKeyPem()
|
PrivatePem = key.ExportRSAPrivateKeyPem()
|
||||||
};
|
};
|
||||||
|
|
||||||
var certPem = pem.Result ?? string.Empty;
|
var certPem = pem.Result ?? string.Empty;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(certPem)) {
|
if (!string.IsNullOrEmpty(certPem)) {
|
||||||
var cert = new X509Certificate2(Encoding.UTF8.GetBytes(certPem));
|
var cert = new X509Certificate2(Encoding.UTF8.GetBytes(certPem));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Ok();
|
return Result.Ok();
|
||||||
} catch (Exception ex) {
|
}
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
catch (Exception ex) {
|
||||||
_logger.LogError(ex, message);
|
List<string> messages = new List<string> { "Let's Encrypt client unhandled exception" };
|
||||||
return Result.InternalServerError(message);
|
_logger.LogError(ex, messages.FirstOrDefault());
|
||||||
|
messages.Add(ex.Message);
|
||||||
|
return Result.InternalServerError([.. messages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@ -444,58 +611,86 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
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);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(RevokeCertificate)}...");
|
_logger.LogInformation($"Executing {nameof(RevokeCertificate)}...");
|
||||||
|
|
||||||
if (state.Cache?.CachedCerts == null || !state.Cache.CachedCerts.TryGetValue(subject, out var certificateCache) || certificateCache == null) {
|
if (state.Cache?.CachedCerts == null || !state.Cache.CachedCerts.TryGetValue(subject, out var certificateCache) || certificateCache == null) {
|
||||||
_logger.LogError("Certificate not found in cache");
|
_logger.LogError("Certificate not found in cache");
|
||||||
return Result.InternalServerError("Certificate not found");
|
return Result.InternalServerError("Certificate not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
var certPem = certificateCache.Cert ?? string.Empty;
|
var certPem = certificateCache.Cert ?? string.Empty;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(certPem)) {
|
if (string.IsNullOrEmpty(certPem)) {
|
||||||
_logger.LogError("Certificate PEM is null or empty");
|
_logger.LogError("Certificate PEM is null or empty");
|
||||||
return Result.InternalServerError("Certificate PEM is null or empty");
|
return Result.InternalServerError("Certificate PEM is null or empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
var certificate = new X509Certificate2(Encoding.UTF8.GetBytes(certPem));
|
var certificate = new X509Certificate2(Encoding.UTF8.GetBytes(certPem));
|
||||||
|
|
||||||
var derEncodedCert = certificate.Export(X509ContentType.Cert);
|
var derEncodedCert = certificate.Export(X509ContentType.Cert);
|
||||||
|
|
||||||
var base64UrlEncodedCert = state.JwsService!.Base64UrlEncoded(derEncodedCert);
|
var base64UrlEncodedCert = state.JwsService!.Base64UrlEncoded(derEncodedCert);
|
||||||
|
|
||||||
var revokeRequest = new RevokeRequest {
|
var revokeRequest = new RevokeRequest {
|
||||||
Certificate = base64UrlEncodedCert,
|
Certificate = base64UrlEncodedCert,
|
||||||
Reason = (int)reason
|
Reason = (int)reason
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory!.RevokeCert);
|
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory!.RevokeCert);
|
||||||
|
|
||||||
await HandleNonceAsync(sessionId, state.Directory.RevokeCert, state);
|
await HandleNonceAsync(sessionId, state.Directory.RevokeCert, state);
|
||||||
|
|
||||||
var jwsHeader = new JwsHeader {
|
var jwsHeader = new JwsHeader {
|
||||||
Url = state.Directory.RevokeCert,
|
Url = state.Directory.RevokeCert,
|
||||||
Nonce = state.Nonce
|
Nonce = state.Nonce
|
||||||
};
|
};
|
||||||
|
|
||||||
var json = state.JwsService.Encode(revokeRequest, jwsHeader).ToJson();
|
var json = state.JwsService.Encode(revokeRequest, jwsHeader).ToJson();
|
||||||
|
|
||||||
request.Content = new StringContent(json);
|
request.Content = new StringContent(json);
|
||||||
|
|
||||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue(GetContentType(ContentType.JoseJson));
|
request.Content.Headers.ContentType = new MediaTypeHeaderValue(GetContentType(ContentType.JoseJson));
|
||||||
|
|
||||||
var response = await _httpClient.SendAsync(request);
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
|
||||||
UpdateStateNonceIfNeeded(response, state, HttpMethod.Post);
|
UpdateStateNonceIfNeeded(response, state, HttpMethod.Post);
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
if (response.Content.Headers.ContentType?.MediaType == GetContentType(ContentType.ProblemJson)) {
|
if (response.Content.Headers.ContentType?.MediaType == GetContentType(ContentType.ProblemJson)) {
|
||||||
var erroObj = responseText.ToObject<Problem>();
|
var erroObj = responseText.ToObject<Problem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
Result.InternalServerError(responseText);
|
Result.InternalServerError(responseText);
|
||||||
|
|
||||||
state.Cache.CachedCerts.Remove(subject);
|
state.Cache.CachedCerts.Remove(subject);
|
||||||
_logger.LogInformation("Certificate revoked successfully");
|
_logger.LogInformation("Certificate revoked successfully");
|
||||||
|
|
||||||
return Result.Ok();
|
return Result.Ok();
|
||||||
} catch (Exception ex) {
|
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
}
|
||||||
_logger.LogError(ex, message);
|
catch (Exception ex) {
|
||||||
return Result.InternalServerError($"{message}: {ex.Message}");
|
List<string> messages = new List<string> { "Let's Encrypt client unhandled exception" };
|
||||||
|
_logger.LogError(ex, messages.FirstOrDefault());
|
||||||
|
messages.Add(ex.Message);
|
||||||
|
return Result.InternalServerError([.. messages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region SendAsync
|
#region SendAsync
|
||||||
private async Task HandleNonceAsync(Guid sessionId, Uri uri, State state) {
|
private async Task HandleNonceAsync(Guid sessionId, Uri uri, State state) {
|
||||||
if (uri == null) throw new ArgumentNullException(nameof(uri));
|
if (uri == null)
|
||||||
|
throw new ArgumentNullException(nameof(uri));
|
||||||
|
|
||||||
if (uri.OriginalString != "directory") {
|
if (uri.OriginalString != "directory") {
|
||||||
var newNonceResult = await NewNonce(sessionId);
|
var newNonceResult = await NewNonce(sessionId);
|
||||||
|
|
||||||
if (!newNonceResult.IsSuccess || newNonceResult.Value == null) {
|
if (!newNonceResult.IsSuccess || newNonceResult.Value == null) {
|
||||||
throw new InvalidOperationException("Failed to retrieve nonce.");
|
throw new InvalidOperationException("Failed to retrieve nonce.");
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Nonce = newNonceResult.Value;
|
state.Nonce = newNonceResult.Value;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -506,17 +701,23 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
private async Task<Result<string?>> NewNonce(Guid sessionId) {
|
private async Task<Result<string?>> NewNonce(Guid sessionId) {
|
||||||
try {
|
try {
|
||||||
var state = GetOrCreateState(sessionId);
|
var state = GetOrCreateState(sessionId);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(NewNonce)}...");
|
_logger.LogInformation($"Executing {nameof(NewNonce)}...");
|
||||||
|
|
||||||
if (state.Directory?.NewNonce == null)
|
if (state.Directory?.NewNonce == null)
|
||||||
return Result<string?>.InternalServerError(null);
|
return Result<string?>.InternalServerError(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();
|
||||||
|
|
||||||
return Result<string?>.Ok(nonce);
|
return Result<string?>.Ok(nonce);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
List<string> messages = new List<string> { "Let's Encrypt client unhandled exception" };
|
||||||
_logger.LogError(ex, message);
|
_logger.LogError(ex, messages.FirstOrDefault());
|
||||||
return Result<string?>.InternalServerError(null, message);
|
messages.Add(ex.Message);
|
||||||
|
return Result<string?>.InternalServerError(null, [.. messages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,13 +731,17 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
private void PrepareRequestContent(HttpRequestMessage request, string json, HttpMethod method) {
|
private void PrepareRequestContent(HttpRequestMessage request, string json, HttpMethod method) {
|
||||||
request.Content = new StringContent(json ?? string.Empty);
|
request.Content = new StringContent(json ?? string.Empty);
|
||||||
var contentType = method == HttpMethod.Post ? GetContentType(ContentType.JoseJson) : GetContentType(ContentType.Json);
|
var contentType = method == HttpMethod.Post
|
||||||
|
? GetContentType(ContentType.JoseJson)
|
||||||
|
: GetContentType(ContentType.Json);
|
||||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
|
request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)) {
|
||||||
throw new LetsEncrytException(responseText.ToObject<Problem>(), response);
|
var problem = responseText.ToObject<Problem>();
|
||||||
|
|
||||||
|
throw new LetsEncrytException(problem, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user