Skip to content

Commit

Permalink
Merge pull request #2350 from win-acme/2.2.3
Browse files Browse the repository at this point in the history
2.2.3
  • Loading branch information
WouterTinus committed Apr 6, 2023
2 parents 4fcc51d + 129e8ba commit 25d0bbd
Show file tree
Hide file tree
Showing 91 changed files with 2,866 additions and 1,657 deletions.
3 changes: 2 additions & 1 deletion appveyor.yml
@@ -1,4 +1,4 @@
version: 2.2.2.{build}
version: 2.2.3.{build}
image: Visual Studio 2022
platform: Any CPU
shallow_clone: true
Expand Down Expand Up @@ -37,6 +37,7 @@ build_script:
- cmd: dotnet publish ./src/plugin.validation.dns.dreamhost/wacs.validation.dns.dreamhost.csproj -c Release
- cmd: dotnet publish ./src/plugin.validation.dns.godaddy/wacs.validation.dns.godaddy.csproj -c Release
- cmd: dotnet publish ./src/plugin.validation.dns.googledns/wacs.validation.dns.googledns.csproj -c Release
- cmd: dotnet publish ./src/plugin.validation.dns.infomaniak/wacs.validation.dns.infomaniak.csproj -c Release
- cmd: dotnet publish ./src/plugin.validation.dns.linode/wacs.validation.dns.linode.csproj -c Release
- cmd: dotnet publish ./src/plugin.validation.dns.luadns/wacs.validation.dns.luadns.csproj -c Release
- cmd: dotnet publish ./src/plugin.validation.dns.ns1/wacs.validation.dns.ns1.csproj -c Release
Expand Down
1 change: 1 addition & 0 deletions build/build.ps1
Expand Up @@ -44,6 +44,7 @@ foreach ($release in @("Release", "ReleaseTrimmed")) {
& dotnet publish $RepoRoot\src\plugin.validation.dns.dreamhost\wacs.validation.dns.dreamhost.csproj -c "Release"
& dotnet publish $RepoRoot\src\plugin.validation.dns.godaddy\wacs.validation.dns.godaddy.csproj -c "Release"
& dotnet publish $RepoRoot\src\plugin.validation.dns.googledns\wacs.validation.dns.googledns.csproj -c "Release"
& dotnet publish $RepoRoot\src\plugin.validation.dns.infomaniak\wacs.validation.dns.infomaniak.csproj -c "Release"
& dotnet publish $RepoRoot\src\plugin.validation.dns.linode\wacs.validation.dns.linode.csproj -c "Release"
& dotnet publish $RepoRoot\src\plugin.validation.dns.luadns\wacs.validation.dns.luadns.csproj -c "Release"
& dotnet publish $RepoRoot\src\plugin.validation.dns.ns1\wacs.validation.dns.ns1.csproj -c "Release"
Expand Down
4 changes: 4 additions & 0 deletions build/create-artifacts.ps1
Expand Up @@ -167,6 +167,10 @@ PluginRelease plugin.validation.dns.googledns @(
"Google.Apis.Dns.v1.dll",
"PKISharp.WACS.Plugins.ValidationPlugins.GoogleDns.dll"
)
PluginRelease plugin.validation.dns.infomaniak @(
"PKISharp.WACS.Plugins.ValidationPlugins.InfoManiak.dll",
"Newtonsoft.Json.dll"
)
PluginRelease plugin.validation.dns.linode @(
"PKISharp.WACS.Plugins.ValidationPlugins.Linode.dll",
"Newtonsoft.Json.dll"
Expand Down
27 changes: 27 additions & 0 deletions src/main.lib/Clients/Acme/Account.cs
@@ -0,0 +1,27 @@
using ACMESharp.Protocol;

namespace PKISharp.WACS.Clients.Acme
{
internal class Account
{
/// <summary>
/// Constructor requires signer to be present
/// </summary>
/// <param name="signer"></param>
public Account(AccountDetails details, AccountSigner signer)
{
Details = details;
Signer = signer;
}

/// <summary>
/// Account information
/// </summary>
public AccountDetails Details { get; set; }

/// <summary>
/// Account "password"
/// </summary>
public AccountSigner Signer { get; set; }
}
}
236 changes: 149 additions & 87 deletions src/main.lib/Clients/Acme/AccountManager.cs
@@ -1,7 +1,9 @@
using ACMESharp.Protocol;
using PKISharp.WACS.Extensions;
using PKISharp.WACS.Services;
using PKISharp.WACS.Services.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text.Json;
Expand All @@ -18,8 +20,6 @@ class AccountManager

private readonly ILogService _log;
private readonly ISettingsService _settings;
private AccountSigner? _currentSigner;
private AccountDetails? _currentAccount;

public AccountManager(
ILogService log,
Expand All @@ -29,24 +29,12 @@ class AccountManager
_settings = settings;
}

/// <summary>
/// File that contains information about the signer, which
/// cryptographically signs the messages sent to the ACME
/// server so that the account can be authenticated
/// </summary>
private string SignerPath => Path.Combine(_settings.Client.ConfigurationPath, SignerFileName);

/// <summary>
/// File that contains information about the account
/// </summary>
private string AccountPath => Path.Combine(_settings.Client.ConfigurationPath, RegistrationFileName);

/// <summary>
/// Create a new signer using the specified algorithm
/// </summary>
/// <param name="keyType"></param>
/// <returns></returns>
public AccountSigner NewSigner(string keyType = "ES256")
private AccountSigner NewSigner(string keyType)
{
_log.Debug("Creating new {keyType} signer", keyType);
return new AccountSigner(keyType);
Expand All @@ -56,103 +44,167 @@ public AccountSigner NewSigner(string keyType = "ES256")
/// Create a new default signer
/// </summary>
/// <returns></returns>
public AccountSigner DefaultSigner()
internal Account NewAccount(string keyType = "ES256")
{
AccountSigner? signer;
try
{
return NewSigner("ES256");
signer = NewSigner(keyType);
}
catch (CryptographicException cex)
{
_log.Verbose("First chance error generating signer: {cex}", cex.Message);
return NewSigner("RS256");
if (keyType == "ES256")
{
_log.Verbose("First chance error generating signer: {cex}", cex.Message);
signer = NewSigner("RS256");
}
else
{
throw;
}
}
return new Account(default, signer);
}

/// <summary>
/// Get or set the currently stored signer
/// Load named account
/// </summary>
public AccountSigner? CurrentSigner
/// <param name="name"></param>
/// <returns></returns>
internal Account? LoadAccount(string? name = null)
{
get
var signerPath = GetPath(SignerFileName, name);
var detailsPath = GetPath(RegistrationFileName, name);
var signer = LoadSigner(signerPath);
var details = LoadDetails(detailsPath);
if (details == default)
{
if (_currentSigner == null)
{
if (File.Exists(SignerPath))
{
try
{
_log.Debug("Loading signer from {signerPath}", SignerPath);
var signerString = new ProtectedString(File.ReadAllText(SignerPath), _log);
if (signerString.Value != null)
{
_currentSigner = JsonSerializer.Deserialize(signerString.Value, AcmeClientJson.Insensitive.AccountSigner);
}
}
catch (Exception ex)
{
_log.Error(ex, "Unable to load signer");
}
}
else
{
_log.Debug("No signer found at {signerPath}", SignerPath);
}
}
return _currentSigner;
return null;
}
set
if (signer == null)
{
if (value != null)
{
_log.Debug("Saving signer to {SignerPath}", SignerPath);
var x = new ProtectedString(JsonSerializer.Serialize(value, AcmeClientJson.Default.AccountSigner));
File.WriteAllText(SignerPath, x.DiskValue(_settings.Security.EncryptConfig));
}
_currentSigner = value;
return null;
}
return new Account(details, signer);
}

/// <summary>
/// Get or set the currently stored account
/// Store named account
/// </summary>
public AccountDetails? CurrentAccount
/// <param name="account"></param>
/// <param name="name"></param>
internal void StoreAccount(Account account, string? name = null)
{
get
var signerPath = GetPath(SignerFileName, name);
var detailsPath = GetPath(RegistrationFileName, name);
StoreDetails(account.Details, detailsPath);
StoreSigner(account.Signer, signerPath);
}

/// <summary>
/// Optional prefix for the path
/// </summary>
/// <param name="file"></param>
/// <param name="name"></param>
/// <returns></returns>
private string GetPath(string file, string? name = null)
{
if (!string.IsNullOrWhiteSpace(name))
{
name = name.CleanPath();
file = $"{name}.{file}";
}
return Path.Combine(_settings.Client.ConfigurationPath, file);
}

/// <summary>
/// Load signer from disk
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private AccountSigner? LoadSigner(string path)
{
if (File.Exists(path))
{
if (_currentAccount == null)
try
{
if (File.Exists(AccountPath))
{
if (CurrentSigner != null)
{
_log.Debug("Loading account from {accountPath}", AccountPath);
_currentAccount = JsonSerializer.Deserialize(File.ReadAllText(AccountPath), AcmeClientJson.Insensitive.AccountDetails);
// Maybe we should update the account details
// on every start of the program to figure out
// if it hasn't been suspended or cancelled?
// UpdateAccount();
}
else
{
_log.Error("Account found but no valid signer could be loaded");
}
}
else
_log.Debug("Loading signer from {signerPath}", path);
var signerString = new ProtectedString(File.ReadAllText(path), _log);
if (signerString.Value != null)
{
_log.Debug("No account found at {accountPath}", AccountPath);
return JsonSerializer.Deserialize(signerString.Value, AcmeClientJson.Insensitive.AccountSigner);
}
}
return _currentAccount;
}
set
{
if (value != null)
catch (Exception ex)
{
_log.Debug("Saving account to {AccountPath}", AccountPath);
File.WriteAllText(AccountPath, JsonSerializer.Serialize(value.Value, AcmeClientJson.Insensitive.AccountDetails));
_log.Error(ex, "Unable to load signer");
}
_currentAccount = value;
}
else
{
_log.Debug("Signer not found at {signerPath}", path);
}
return null;
}

/// <summary>
/// Store signer to disk
/// </summary>
/// <param name="signer"></param>
/// <param name="path"></param>
private void StoreSigner(AccountSigner? signer, string path)
{
if (signer != null)
{
_log.Debug("Saving signer to {SignerPath}", path);
var x = new ProtectedString(JsonSerializer.Serialize(signer, AcmeClientJson.Default.AccountSigner));
File.WriteAllText(path, x.DiskValue(_settings.Security.EncryptConfig));
}
}

/// <summary>
/// Load details from disk
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private AccountDetails LoadDetails(string path)
{
if (File.Exists(path))
{
_log.Debug("Loading account from {path}", path);
return JsonSerializer.Deserialize(File.ReadAllText(path), AcmeClientJson.Insensitive.AccountDetails);
}
else
{
_log.Debug("Details not found at {path}", path);
}
return default;
}

/// <summary>
/// Store details to disk
/// </summary>
/// <param name="details"></param>
/// <param name="path"></param>
private void StoreDetails(AccountDetails details, string path)
{
if (details != default)
{
_log.Debug("Saving account to {AccountPath}", path);
File.WriteAllText(path, JsonSerializer.Serialize(details, AcmeClientJson.Insensitive.AccountDetails));
}
}

/// <summary>
/// List of available accounts
/// </summary>
/// <returns></returns>
internal IEnumerable<string> ListAccounts()
{
var dir = new DirectoryInfo(_settings.Client.ConfigurationPath);
foreach (var account in dir.EnumerateFiles($"*{RegistrationFileName}"))
{
yield return account.Name.Replace(RegistrationFileName, "").TrimEnd('.');
}
}

Expand All @@ -163,13 +215,23 @@ internal void Encrypt()
{
try
{
var signer = CurrentSigner;
CurrentSigner = signer; //forces a re-save of the signer
foreach (var name in ListAccounts())
{
var account = LoadAccount(name);
if (account != null)
{
StoreAccount(account, name); //forces a re-save of the signer
}
else
{
_log.Error($"Unable to load account {name}");
}
}
_log.Information("Signer re-saved");
}
catch
{
_log.Error("Cannot re-save signer as it is likely encrypted on a different machine");
_log.Error("Cannot re-save account (created on a different machine?)");
}
}
}
Expand Down

0 comments on commit 25d0bbd

Please sign in to comment.