mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): revoke init
This commit is contained in:
parent
8aa535447e
commit
1119c01be0
@ -55,10 +55,6 @@ export default function Page() {
|
||||
init.current = true
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(editingAccount)
|
||||
}, [editingAccount])
|
||||
|
||||
const handleAccountUpdate = (updatedAccount: CacheAccount) => {
|
||||
setAccounts(
|
||||
accounts.map((account) =>
|
||||
@ -70,15 +66,8 @@ export default function Page() {
|
||||
}
|
||||
|
||||
const deleteAccount = (accountId: string) => {
|
||||
httpService
|
||||
.delete(GetApiRoute(ApiRoutes.ACCOUNT_ID, accountId))
|
||||
.then((response) => {
|
||||
if (response.isSuccess) {
|
||||
setAccounts(
|
||||
accounts.filter((account) => account.accountId !== accountId)
|
||||
)
|
||||
}
|
||||
})
|
||||
setAccounts(accounts.filter((account) => account.accountId !== accountId))
|
||||
setEditingAccount(null)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -255,7 +255,18 @@ const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
||||
}
|
||||
|
||||
const handleDelete = (accountId: string) => {
|
||||
onDelete?.(accountId)
|
||||
httpService
|
||||
.delete(GetApiRoute(ApiRoutes.ACCOUNT_ID, accountId))
|
||||
.then((response) => {
|
||||
if (response.isSuccess) {
|
||||
onDelete?.(accountId)
|
||||
} else {
|
||||
// Optionally, handle the error case, e.g., show an error message
|
||||
dispatch(
|
||||
showToast({ message: 'Failed to detele account.', type: 'error' })
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -212,7 +212,8 @@ class HttpService {
|
||||
): ProblemDetails {
|
||||
return {
|
||||
Title: title,
|
||||
Detail: detail instanceof Error ? detail.message : String(detail),
|
||||
Detail:
|
||||
detail instanceof Error ? detail.message : JSON.parse(detail)?.detail,
|
||||
Status: status
|
||||
}
|
||||
}
|
||||
@ -252,6 +253,8 @@ class HttpService {
|
||||
): Promise<HttpResponse<TResponse>> {
|
||||
// Clean the data before sending the patch request
|
||||
const cleanedData = this.cleanObject(data)
|
||||
|
||||
console.log('Cleaned patch data:', cleanedData)
|
||||
return await this.request<TResponse>('PATCH', url, cleanedData)
|
||||
}
|
||||
|
||||
|
||||
18
src/LetsEncrypt/Entities/LetsEncrypt/RevokeReason.cs
Normal file
18
src/LetsEncrypt/Entities/LetsEncrypt/RevokeReason.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaksIT.LetsEncrypt.Entities.LetsEncrypt {
|
||||
public enum RevokeReason {
|
||||
Unspecified = 0,
|
||||
KeyCompromise = 1,
|
||||
CaCompromise = 2,
|
||||
AffiliationChanged = 3,
|
||||
Superseded = 4,
|
||||
CessationOfOperation = 5,
|
||||
PrivilegeWithdrawn = 6,
|
||||
AaCompromise = 7
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ public class AcmeDirectory {
|
||||
public Uri NewNonce { get; set; }
|
||||
public Uri NewOrder { get; set; }
|
||||
public Uri RenewalInfo { get; set; }
|
||||
public Uri RevokeCertificate { get; set; }
|
||||
public Uri RevokeCert { get; set; }
|
||||
}
|
||||
|
||||
public class AcmeDirectoryMeta {
|
||||
|
||||
12
src/LetsEncrypt/Models/Responses/RevokeRequest.cs
Normal file
12
src/LetsEncrypt/Models/Responses/RevokeRequest.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaksIT.LetsEncrypt.Models.Responses {
|
||||
public class RevokeRequest {
|
||||
public string Certificate { get; set; } = string.Empty;
|
||||
public int Reason { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* https://datatracker.ietf.org/doc/html/rfc8555
|
||||
* https://datatracker.ietf.org/doc/html/draft-ietf-acme-acme-12
|
||||
*/
|
||||
|
||||
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
@ -16,6 +22,7 @@ using MaksIT.LetsEncrypt.Models.Interfaces;
|
||||
using MaksIT.LetsEncrypt.Models.Requests;
|
||||
using MaksIT.LetsEncrypt.Entities.Jws;
|
||||
using MaksIT.LetsEncrypt.Entities.LetsEncrypt;
|
||||
using System.Net.Mime;
|
||||
|
||||
namespace MaksIT.LetsEncrypt.Services;
|
||||
|
||||
@ -28,6 +35,7 @@ public interface ILetsEncryptService {
|
||||
Task<IDomainResult> CompleteChallenges(Guid sessionId);
|
||||
Task<IDomainResult> GetOrder(Guid sessionId, string[] hostnames);
|
||||
Task<IDomainResult> GetCertificate(Guid sessionId, string subject);
|
||||
Task<IDomainResult> RevokeCertificate(Guid sessionId, string subject, RevokeReason reason);
|
||||
}
|
||||
|
||||
public class LetsEncryptService : ILetsEncryptService {
|
||||
@ -278,7 +286,7 @@ public class LetsEncryptService : ILetsEncryptService {
|
||||
_logger.LogInformation($"Executing {nameof(CompleteChallenges)}...");
|
||||
|
||||
if (state.CurrentOrder?.Identifiers == null) {
|
||||
return IDomainResult.Failed();
|
||||
return IDomainResult.Failed("Current order identifiers are null");
|
||||
}
|
||||
|
||||
for (var index = 0; index < state.Challenges.Count; index++) {
|
||||
@ -449,10 +457,61 @@ public class LetsEncryptService : ILetsEncryptService {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IDomainResult> RevokeCertificate(Guid sessionId) {
|
||||
throw new NotImplementedException();
|
||||
public async Task<IDomainResult> RevokeCertificate(Guid sessionId, string subject, RevokeReason reason) {
|
||||
try {
|
||||
var state = GetOrCreateState(sessionId);
|
||||
|
||||
_logger.LogInformation($"Executing {nameof(RevokeCertificate)}...");
|
||||
|
||||
if (state.Cache == null || state.Cache.CachedCerts == null || !state.Cache.CachedCerts.TryGetValue(subject, out var certificateCache)) {
|
||||
_logger.LogError("Certificate not found in cache");
|
||||
return IDomainResult.Failed("Certificate not found");
|
||||
}
|
||||
|
||||
|
||||
|
||||
string Base64UrlEncode(byte[] input) {
|
||||
return Convert.ToBase64String(input)
|
||||
.TrimEnd('=')
|
||||
.Replace('+', '-')
|
||||
.Replace('/', '_');
|
||||
}
|
||||
|
||||
|
||||
// Load the certificate from PEM format and convert it to DER format
|
||||
var certificate = new X509Certificate2(Encoding.UTF8.GetBytes(certificateCache.Cert));
|
||||
var derEncodedCert = certificate.Export(X509ContentType.Cert);
|
||||
var base64UrlEncodedCert = Base64UrlEncode(derEncodedCert);
|
||||
|
||||
// Convert the certificate to DER format and Base64 encode it
|
||||
var base64Cert = Convert.ToBase64String(certificate.Export(X509ContentType.Cert));
|
||||
|
||||
var revokeRequest = new RevokeRequest {
|
||||
Certificate = certificateCache.Cert,
|
||||
Reason = (int)reason
|
||||
};
|
||||
|
||||
var (revokeResult, domainResult) = await SendAsync<object>(sessionId, HttpMethod.Post, state.Directory.RevokeCert, false, revokeRequest);
|
||||
if (!domainResult.IsSuccess) {
|
||||
return domainResult;
|
||||
}
|
||||
|
||||
// Remove the certificate from the cache after successful revocation
|
||||
state.Cache.CachedCerts.Remove(subject);
|
||||
|
||||
_logger.LogInformation("Certificate revoked successfully");
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = "Let's Encrypt client unhandled exception";
|
||||
_logger.LogError(ex, message);
|
||||
return IDomainResult.CriticalDependencyError(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#region SendAsync
|
||||
/// <summary>
|
||||
///
|
||||
@ -563,6 +622,7 @@ public class LetsEncryptService : ILetsEncryptService {
|
||||
var jwsHeader = CreateJwsHeader(uri, state.Nonce);
|
||||
var json = EncodeMessage(isPostAsGet, requestModel, state, jwsHeader);
|
||||
PrepareRequestContent(request, json, method);
|
||||
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
|
||||
@ -47,8 +47,6 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices {
|
||||
|
||||
private async Task<IDomainResult> ProcessAccountAsync(RegistrationCache cache) {
|
||||
|
||||
|
||||
|
||||
var hostnames = cache.GetHostsWithUpcomingSslExpiry();
|
||||
if (hostnames == null) {
|
||||
_logger.LogError("Unexpected hostnames null");
|
||||
@ -61,7 +59,7 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices {
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
|
||||
var renewResult = await RenewCertificatesForHostnames(cache.AccountId, cache.Description, cache.Contacts, hostnames, cache.ChallengeType, cache.IsStaging);
|
||||
var (_, renewResult) = await _certsFlowService.FullFlow(cache.IsStaging, cache.AccountId, cache.Description, cache.Contacts, cache.ChallengeType, hostnames);
|
||||
if (!renewResult.IsSuccess)
|
||||
return renewResult;
|
||||
|
||||
@ -70,53 +68,7 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices {
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
|
||||
private async Task<IDomainResult> RenewCertificatesForHostnames(Guid accountId, string description, string[] contacts, string[] hostnames, string challengeType, bool isStaging) {
|
||||
var (sessionId, configureClientResult) = await _certsFlowService.ConfigureClientAsync(isStaging);
|
||||
if (!configureClientResult.IsSuccess || sessionId == null) {
|
||||
LogErrors(configureClientResult.Errors);
|
||||
return configureClientResult;
|
||||
}
|
||||
|
||||
var sessionIdValue = sessionId.Value;
|
||||
|
||||
var (_, initResult) = await _certsFlowService.InitAsync(sessionIdValue, accountId, description, contacts);
|
||||
if (!initResult.IsSuccess) {
|
||||
LogErrors(initResult.Errors);
|
||||
return initResult;
|
||||
}
|
||||
|
||||
var (_, newOrderResult) = await _certsFlowService.NewOrderAsync(sessionIdValue, hostnames, challengeType);
|
||||
if (!newOrderResult.IsSuccess) {
|
||||
LogErrors(newOrderResult.Errors);
|
||||
return newOrderResult;
|
||||
}
|
||||
|
||||
var challengeResult = await _certsFlowService.CompleteChallengesAsync(sessionIdValue);
|
||||
if (!challengeResult.IsSuccess) {
|
||||
LogErrors(challengeResult.Errors);
|
||||
return challengeResult;
|
||||
}
|
||||
|
||||
var getOrderResult = await _certsFlowService.GetOrderAsync(sessionIdValue, hostnames);
|
||||
if (!getOrderResult.IsSuccess) {
|
||||
LogErrors(getOrderResult.Errors);
|
||||
return getOrderResult;
|
||||
}
|
||||
|
||||
var certs = await _certsFlowService.GetCertificatesAsync(sessionIdValue, hostnames);
|
||||
if (!certs.IsSuccess) {
|
||||
LogErrors(certs.Errors);
|
||||
return certs;
|
||||
}
|
||||
|
||||
var (_, applyCertsResult) = await _certsFlowService.ApplyCertificatesAsync(sessionIdValue, hostnames);
|
||||
if (!applyCertsResult.IsSuccess) {
|
||||
LogErrors(applyCertsResult.Errors);
|
||||
return applyCertsResult;
|
||||
}
|
||||
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
|
||||
private void LogErrors(IEnumerable<string> errors) {
|
||||
foreach (var error in errors) {
|
||||
|
||||
@ -42,7 +42,7 @@ public class AccountController : ControllerBase {
|
||||
return result.ToActionResult();
|
||||
}
|
||||
|
||||
[HttpDelete("account/{accountd:guid}")]
|
||||
[HttpDelete("account/{accountId:guid}")]
|
||||
public async Task<IActionResult> DeleteAccount(Guid accountId) {
|
||||
var result = await _accountService.DeleteAccountAsync(accountId);
|
||||
return result.ToActionResult();
|
||||
|
||||
@ -133,10 +133,6 @@ public class AccountService : IAccountService {
|
||||
cache.Contacts = contacts.ToArray();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var hostnamesToAdd = new List<string>();
|
||||
var hostnamesToRemove = new List<string>();
|
||||
|
||||
@ -174,13 +170,11 @@ public class AccountService : IAccountService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache);
|
||||
if (!saveResult.IsSuccess) {
|
||||
return (null, saveResult);
|
||||
}
|
||||
|
||||
|
||||
if (hostnamesToAdd.Count > 0) {
|
||||
var (_, newCertsResult) = await _certsFlowService.FullFlow(
|
||||
cache.IsStaging,
|
||||
@ -195,13 +189,18 @@ public class AccountService : IAccountService {
|
||||
return (null, newCertsResult);
|
||||
}
|
||||
|
||||
|
||||
if (hostnamesToRemove.Count > 0) {
|
||||
hostnamesToRemove.ForEach(hostname => {
|
||||
cache.CachedCerts?.Remove(hostname);
|
||||
});
|
||||
}
|
||||
var revokeResult = await _certsFlowService.FullRevocationFlow(
|
||||
cache.IsStaging,
|
||||
cache.AccountId,
|
||||
cache.Description,
|
||||
cache.Contacts,
|
||||
hostnamesToRemove.ToArray()
|
||||
);
|
||||
|
||||
if (!revokeResult.IsSuccess)
|
||||
return (null, revokeResult);
|
||||
}
|
||||
|
||||
(cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache == null) {
|
||||
|
||||
@ -6,6 +6,7 @@ using MaksIT.LetsEncrypt.Entities;
|
||||
using MaksIT.LetsEncrypt.Services;
|
||||
using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests;
|
||||
using System.Security.Cryptography;
|
||||
using MaksIT.LetsEncrypt.Entities.LetsEncrypt;
|
||||
|
||||
|
||||
namespace MaksIT.LetsEncryptServer.Services;
|
||||
@ -22,8 +23,11 @@ public interface ICertsInternalService : ICertsCommonService {
|
||||
Task<(List<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType);
|
||||
Task<IDomainResult> GetOrderAsync(Guid sessionId, string[] hostnames);
|
||||
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||
Task<IDomainResult> RevokeCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||
Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||
Task<(Guid?, IDomainResult)> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[] hostnames);
|
||||
Task<IDomainResult> FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames);
|
||||
|
||||
}
|
||||
|
||||
public interface ICertsRestService : ICertsCommonService {
|
||||
@ -145,6 +149,7 @@ public class CertsFlowService : ICertsFlowService {
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
// TODO: Move to separate method
|
||||
// Persist the cache
|
||||
var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId);
|
||||
if (!getCacheResult.IsSuccess || cache == null)
|
||||
@ -161,6 +166,33 @@ public class CertsFlowService : ICertsFlowService {
|
||||
return await _letsEncryptService.GetOrder(sessionId, hostnames);
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> RevokeCertificatesAsync(Guid sessionId, string[] hostnames) {
|
||||
foreach (var hostname in hostnames) {
|
||||
var result = await _letsEncryptService.RevokeCertificate(sessionId, hostname, RevokeReason.Unspecified);
|
||||
if (!result.IsSuccess)
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Move to separate method
|
||||
// Persist the cache
|
||||
var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId);
|
||||
if (!getCacheResult.IsSuccess || cache == null)
|
||||
return getCacheResult;
|
||||
|
||||
var saveResult = await _cacheService.SaveToCacheAsync(cache.AccountId, cache);
|
||||
if (!saveResult.IsSuccess)
|
||||
return saveResult;
|
||||
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public async Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames) {
|
||||
|
||||
var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId);
|
||||
@ -221,6 +253,22 @@ public class CertsFlowService : ICertsFlowService {
|
||||
return IDomainResult.Success(accountId);
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames) {
|
||||
var (sessionId, configureClientResult) = await ConfigureClientAsync(isStaging);
|
||||
if (!configureClientResult.IsSuccess || sessionId == null)
|
||||
return configureClientResult;
|
||||
|
||||
var (_, initResult) = await InitAsync(sessionId.Value, accountId, description, contacts);
|
||||
if (!initResult.IsSuccess)
|
||||
return initResult;
|
||||
|
||||
var revokeResult = await RevokeCertificatesAsync(sessionId.Value, hostnames);
|
||||
if (!revokeResult.IsSuccess)
|
||||
return revokeResult;
|
||||
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests;
|
||||
public class PatchHostnameRequest {
|
||||
public PatchAction<string> Hostname { get; set; }
|
||||
public PatchAction<string>? Hostname { get; set; }
|
||||
|
||||
public PatchAction<bool> IsDisabled { get; set; }
|
||||
public PatchAction<bool>? IsDisabled { get; set; }
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user