From bc7e499349bc44d4f4a608f582d249a72955a7d2 Mon Sep 17 00:00:00 2001 From: Eugene Bekker Date: Mon, 7 Oct 2019 10:27:09 -0400 Subject: [PATCH 1/8] bumping up version for added support for POST-for-GET Thanks @WouterTinus ! --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index c915a83..9a1dab2 100644 --- a/version.props +++ b/version.props @@ -2,7 +2,7 @@ $(APPVEYOR_BUILD_NUMBER) 0 - 2.0.1.$(BuildNumber) + 2.1.0.$(BuildNumber) From c065a0d19c3ef76140d6c73be3d0292736ef2955 Mon Sep 17 00:00:00 2001 From: Eugene Bekker Date: Mon, 7 Oct 2019 11:45:56 -0400 Subject: [PATCH 2/8] adding some tests for POST-as-GET feature --- global.json | 5 ++++ .../AcmeAccountTests.cs | 18 ++++++++++++-- .../AcmeMultiNameOrderTests.cs | 2 +- .../AcmeOrderTests.cs | 24 +++++++++++++++---- .../AcmeSimplePkiEcdsaOrderTests.cs | 2 +- .../AcmeSimplePkiRsaOrderTests.cs | 2 +- .../AcmeSingleNameOrderTests.cs | 12 +++++++++- .../AcmeWildcardNameOrderTests.cs | 2 +- 8 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 0000000..3b7918c --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "2.1.*" + } +} \ No newline at end of file diff --git a/test/ACMESharp.IntegrationTests/AcmeAccountTests.cs b/test/ACMESharp.IntegrationTests/AcmeAccountTests.cs index dc02c7c..ea82feb 100644 --- a/test/ACMESharp.IntegrationTests/AcmeAccountTests.cs +++ b/test/ACMESharp.IntegrationTests/AcmeAccountTests.cs @@ -14,13 +14,27 @@ namespace ACMESharp.IntegrationTests { + [Collection(nameof(AcmeAccountWithPostAsGetTests))] + [CollectionDefinition(nameof(AcmeAccountWithPostAsGetTests))] + [TestOrder(0_051)] + public class AcmeAccountWithPostAsGetTests : AcmeAccountTests + { + public AcmeAccountWithPostAsGetTests(ITestOutputHelper output, StateFixture state, ClientsFixture clients) + : base(output, state, clients) + { + _usePostAsGet = true; + } + } + [Collection(nameof(AcmeAccountTests))] [CollectionDefinition(nameof(AcmeAccountTests))] - [TestOrder(0_05)] + [TestOrder(0_050)] public class AcmeAccountTests : IntegrationTest, IClassFixture, IClassFixture { + protected bool _usePostAsGet = false; + public AcmeAccountTests(ITestOutputHelper output, StateFixture state, ClientsFixture clients) : base(state, clients) { @@ -48,7 +62,7 @@ public void InitAcmeClient() { BaseAddress = Clients.BaseAddress }; - Clients.Acme = new AcmeProtocolClient(Clients.Http); + Clients.Acme = new AcmeProtocolClient(Clients.Http, usePostAsGet: _usePostAsGet); } [Fact] diff --git a/test/ACMESharp.IntegrationTests/AcmeMultiNameOrderTests.cs b/test/ACMESharp.IntegrationTests/AcmeMultiNameOrderTests.cs index 191e51a..13a5d92 100644 --- a/test/ACMESharp.IntegrationTests/AcmeMultiNameOrderTests.cs +++ b/test/ACMESharp.IntegrationTests/AcmeMultiNameOrderTests.cs @@ -18,7 +18,7 @@ namespace ACMESharp.IntegrationTests { [Collection(nameof(AcmeOrderTests))] [CollectionDefinition(nameof(AcmeOrderTests))] - [TestOrder(0_20)] + [TestOrder(0_200)] public class AcmeMultiNameOrderTests : AcmeOrderTests { public AcmeMultiNameOrderTests(ITestOutputHelper output, diff --git a/test/ACMESharp.IntegrationTests/AcmeOrderTests.cs b/test/ACMESharp.IntegrationTests/AcmeOrderTests.cs index 1633dd2..bfe5823 100644 --- a/test/ACMESharp.IntegrationTests/AcmeOrderTests.cs +++ b/test/ACMESharp.IntegrationTests/AcmeOrderTests.cs @@ -20,14 +20,30 @@ namespace ACMESharp.IntegrationTests { + [Collection(nameof(AcmeOrderWithPostAsGetTests))] + [CollectionDefinition(nameof(AcmeOrderWithPostAsGetTests))] + [TestOrder(0_101)] + public abstract class AcmeOrderWithPostAsGetTests : AcmeOrderTests + { + public AcmeOrderWithPostAsGetTests(ITestOutputHelper output, + StateFixture state, ClientsFixture clients, AwsFixture aws, ILogger log = null) + : base(output, state, clients, aws, + log ?? state.Factory.CreateLogger(typeof(AcmeOrderWithPostAsGetTests).FullName)) + { + _usePostAsGet = true; + } + } + [Collection(nameof(AcmeOrderTests))] [CollectionDefinition(nameof(AcmeOrderTests))] - [TestOrder(0_10)] + [TestOrder(0_100)] public abstract class AcmeOrderTests : IntegrationTest, IClassFixture, IClassFixture, IClassFixture { + protected bool _usePostAsGet = false; + public AcmeOrderTests(ITestOutputHelper output, StateFixture state, ClientsFixture clients, AwsFixture aws, ILogger log = null) : base(state, clients) @@ -72,7 +88,7 @@ public async Task InitAcmeClient() { Log.LogInformation("Durable Account data does not exist -- CREATING"); - Clients.Acme = new AcmeProtocolClient(Clients.Http); + Clients.Acme = new AcmeProtocolClient(Clients.Http, usePostAsGet: _usePostAsGet); SetTestContext(); // To update the ACME client's Before/After hooks await InitDirectoryAndNonce(); acct = await Clients.Acme.CreateAccountAsync(_contacts, true); @@ -87,7 +103,7 @@ public async Task InitAcmeClient() { Log.LogInformation("Found existing persisted Account data -- LOADING"); - Clients.Acme = new AcmeProtocolClient(Clients.Http, acct: acct); + Clients.Acme = new AcmeProtocolClient(Clients.Http, acct: acct, usePostAsGet: _usePostAsGet); Clients.Acme.Signer.Import(keys); SetTestContext(); // To update the ACME client's Before/After hooks await InitDirectoryAndNonce(); @@ -181,7 +197,7 @@ protected async Task ValidateDnsTxtRecord(string name, } else { - throw new InvalidOperationException("Unhandled DNS response: " + x.ErrorMessage); + throw new InvalidOperationException($"Unhandled DNS response (targetMissing={targetMissing}): {x.ErrorMessage}"); } } else diff --git a/test/ACMESharp.IntegrationTests/AcmeSimplePkiEcdsaOrderTests.cs b/test/ACMESharp.IntegrationTests/AcmeSimplePkiEcdsaOrderTests.cs index c3ed9f2..4b1d2b0 100644 --- a/test/ACMESharp.IntegrationTests/AcmeSimplePkiEcdsaOrderTests.cs +++ b/test/ACMESharp.IntegrationTests/AcmeSimplePkiEcdsaOrderTests.cs @@ -20,7 +20,7 @@ namespace ACMESharp.IntegrationTests { [Collection(nameof(AcmeOrderTests))] [CollectionDefinition(nameof(AcmeOrderTests))] - [TestOrder(0_20)] + [TestOrder(0_200)] public class AcmeSimplePkiEcdsaOrderTests : AcmeOrderTests { public AcmeSimplePkiEcdsaOrderTests(ITestOutputHelper output, diff --git a/test/ACMESharp.IntegrationTests/AcmeSimplePkiRsaOrderTests.cs b/test/ACMESharp.IntegrationTests/AcmeSimplePkiRsaOrderTests.cs index 0a2640e..6a8bdf0 100644 --- a/test/ACMESharp.IntegrationTests/AcmeSimplePkiRsaOrderTests.cs +++ b/test/ACMESharp.IntegrationTests/AcmeSimplePkiRsaOrderTests.cs @@ -20,7 +20,7 @@ namespace ACMESharp.IntegrationTests { [Collection(nameof(AcmeOrderTests))] [CollectionDefinition(nameof(AcmeOrderTests))] - [TestOrder(0_20)] + [TestOrder(0_200)] public class AcmeSimplePkiRsaOrderTests : AcmeOrderTests { public AcmeSimplePkiRsaOrderTests(ITestOutputHelper output, diff --git a/test/ACMESharp.IntegrationTests/AcmeSingleNameOrderTests.cs b/test/ACMESharp.IntegrationTests/AcmeSingleNameOrderTests.cs index 29687c1..b13c63a 100644 --- a/test/ACMESharp.IntegrationTests/AcmeSingleNameOrderTests.cs +++ b/test/ACMESharp.IntegrationTests/AcmeSingleNameOrderTests.cs @@ -14,9 +14,19 @@ namespace ACMESharp.IntegrationTests { + public class AcmeSingleNameOrderWithPostAsGetTests : AcmeOrderWithPostAsGetTests + { + public AcmeSingleNameOrderWithPostAsGetTests(ITestOutputHelper output, + StateFixture state, ClientsFixture clients, AwsFixture aws) + : base(output, state, clients, aws, + state.Factory.CreateLogger(typeof(AcmeSingleNameOrderWithPostAsGetTests).FullName)) + { } + } + + [Collection(nameof(AcmeOrderTests))] [CollectionDefinition(nameof(AcmeOrderTests))] - [TestOrder(0_10)] + [TestOrder(0_100)] public class AcmeSingleNameOrderTests : AcmeOrderTests { public AcmeSingleNameOrderTests(ITestOutputHelper output, diff --git a/test/ACMESharp.IntegrationTests/AcmeWildcardNameOrderTests.cs b/test/ACMESharp.IntegrationTests/AcmeWildcardNameOrderTests.cs index 58e5613..4f05c95 100644 --- a/test/ACMESharp.IntegrationTests/AcmeWildcardNameOrderTests.cs +++ b/test/ACMESharp.IntegrationTests/AcmeWildcardNameOrderTests.cs @@ -18,7 +18,7 @@ namespace ACMESharp.IntegrationTests { [Collection(nameof(AcmeOrderTests))] [CollectionDefinition(nameof(AcmeOrderTests))] - [TestOrder(0_20)] + [TestOrder(0_200)] public class AcmeWildcardNameOrderTests : AcmeOrderTests { public AcmeWildcardNameOrderTests(ITestOutputHelper output, From 256833cd6f2e7a17047559a14eeaeeea5e97a29d Mon Sep 17 00:00:00 2001 From: Eugene Bekker Date: Mon, 7 Oct 2019 11:52:38 -0400 Subject: [PATCH 3/8] Delete global.json --- global.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 global.json diff --git a/global.json b/global.json deleted file mode 100644 index 3b7918c..0000000 --- a/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sdk": { - "version": "2.1.*" - } -} \ No newline at end of file From c8e7c4782d95ffb708da70b1e459e8314824fec4 Mon Sep 17 00:00:00 2001 From: Olivier Bossaer Date: Sun, 13 Oct 2019 20:26:39 +0200 Subject: [PATCH 4/8] - Fix ExportPfx: Adds private key (Only RSA supported) - Adds support for PFX password --- src/examples/ACMECLI/Program.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/examples/ACMECLI/Program.cs b/src/examples/ACMECLI/Program.cs index b229bd6..a6ea2c4 100644 --- a/src/examples/ACMECLI/Program.cs +++ b/src/examples/ACMECLI/Program.cs @@ -116,7 +116,9 @@ class Program [Option(ShortName = "", Description = "Save the certificate chain and private key (PKCS12) to the named file path")] public string ExportPfx { get; } - + + [Option(ShortName = "", Description = "Save the certificate chain and private key (PKCS12) with the specified password")] + public string ExportPfxPassword { get; } private string _statePath; @@ -536,11 +538,17 @@ void AddStatusCount(string status, int add) if (ExportPfx != null) { Console.WriteLine("Exporting Certificate as PKCS12..."); - using (var cert = new X509Certificate2(LoadRaw( - true, Constants.AcmeOrderCertFmt, orderId))) + using (var cert = new X509Certificate2(LoadRaw(true, Constants.AcmeOrderCertFmt, orderId))) + using (var privateKey = new RSACryptoServiceProvider()) { - await File.WriteAllBytesAsync(ExportPfx, - cert.Export(X509ContentType.Pkcs12)); + string certKeys = default; + LoadStateInto(ref certKeys, failThrow: true, Constants.AcmeOrderCertKeyFmt, orderId); + var rsaParameters = JsonConvert.DeserializeObject(certKeys); + privateKey.ImportParameters(rsaParameters); + using(var certificateWithPrivateKey = cert.CopyWithPrivateKey(privateKey)) + { + await File.WriteAllBytesAsync(ExportPfx, certificateWithPrivateKey.Export(X509ContentType.Pkcs12, ExportPfxPassword)); + } } } } From 94bc10dcf6bb57ce52bdc2cfb93ff15aacb67b55 Mon Sep 17 00:00:00 2001 From: Eugene Bekker Date: Mon, 14 Oct 2019 22:54:47 -0400 Subject: [PATCH 5/8] added support for importing BCL X509Certificate2 --- src/PKISharp.SimplePKI/PkiCertificate.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/PKISharp.SimplePKI/PkiCertificate.cs b/src/PKISharp.SimplePKI/PkiCertificate.cs index 72aee32..30d3a18 100644 --- a/src/PKISharp.SimplePKI/PkiCertificate.cs +++ b/src/PKISharp.SimplePKI/PkiCertificate.cs @@ -33,6 +33,16 @@ public BclCertificate ToBclCertificate() return new BclCertificate(NativeCertificate.GetEncoded()); } + public static PkiCertificate From(BclCertificate bclCert) + { + var derEncoding = System.Security.Cryptography.X509Certificates.X509ContentType.Cert; + var der = bclCert.Export(derEncoding); + return new PkiCertificate + { + NativeCertificate = new X509CertificateParser().ReadCertificate(der), + }; + } + public byte[] Export(PkiEncodingFormat format) { switch (format) From 4007384bc51b837b74f22af1e420f06c9165a2cc Mon Sep 17 00:00:00 2001 From: Eugene Bekker Date: Mon, 14 Oct 2019 22:57:18 -0400 Subject: [PATCH 6/8] renaming to avoid conflict with possible future general class --- src/examples/ACMECLI/Program.cs | 2 -- src/examples/ACMEKestrel/AcmeHostedService.cs | 4 ++-- src/examples/ACMEKestrel/AcmeState.cs | 2 +- .../{AccountKey.cs => ExamplesAccountKey.cs} | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) rename src/examples/Examples.Common.PKI/{AccountKey.cs => ExamplesAccountKey.cs} (96%) diff --git a/src/examples/ACMECLI/Program.cs b/src/examples/ACMECLI/Program.cs index a6ea2c4..bfac205 100644 --- a/src/examples/ACMECLI/Program.cs +++ b/src/examples/ACMECLI/Program.cs @@ -117,7 +117,6 @@ class Program [Option(ShortName = "", Description = "Save the certificate chain and private key (PKCS12) to the named file path")] public string ExportPfx { get; } - [Option(ShortName = "", Description = "Save the certificate chain and private key (PKCS12) with the specified password")] public string ExportPfxPassword { get; } @@ -174,7 +173,6 @@ public async Task OnExecute() } IJwsTool accountSigner = default; - AccountKey accountKey = default; string accountKeyHash = default; if (LoadStateInto(ref accountKey, failThrow: false, Constants.AcmeAccountKeyFile)) diff --git a/src/examples/ACMEKestrel/AcmeHostedService.cs b/src/examples/ACMEKestrel/AcmeHostedService.cs index a96be8f..cdfd31f 100644 --- a/src/examples/ACMEKestrel/AcmeHostedService.cs +++ b/src/examples/ACMEKestrel/AcmeHostedService.cs @@ -59,7 +59,7 @@ public Task StartAsync(CancellationToken cancellationToken) (_, _state.ServiceDirectory) = Load(_state.ServiceDirectoryFile); (_, _state.Account) = Load(_state.AccountFile); - (_, _state.AccountKey) = Load(_state.AccountKeyFile); + (_, _state.AccountKey) = Load(_state.AccountKeyFile); (_, _state.Order) = Load(_state.OrderFile); (_, _state.Authorizations) = Load>( _state.AuthorizationsFile); @@ -153,7 +153,7 @@ protected async Task ResolveAccount(AcmeProtocolClient acme) _state.Account = await acme.CreateAccountAsync( contacts: contacts, termsOfServiceAgreed: _options.AcceptTermsOfService); - _state.AccountKey = new AccountKey + _state.AccountKey = new ExamplesAccountKey { KeyType = acme.Signer.JwsAlg, KeyExport = acme.Signer.Export(), diff --git a/src/examples/ACMEKestrel/AcmeState.cs b/src/examples/ACMEKestrel/AcmeState.cs index 665c1e6..b4b8575 100644 --- a/src/examples/ACMEKestrel/AcmeState.cs +++ b/src/examples/ACMEKestrel/AcmeState.cs @@ -32,7 +32,7 @@ public class AcmeState public string AccountKeyFile { get; set; } - public AccountKey AccountKey { get; set; } + public ExamplesAccountKey AccountKey { get; set; } public string OrderFile { get; set; } diff --git a/src/examples/Examples.Common.PKI/AccountKey.cs b/src/examples/Examples.Common.PKI/ExamplesAccountKey.cs similarity index 96% rename from src/examples/Examples.Common.PKI/AccountKey.cs rename to src/examples/Examples.Common.PKI/ExamplesAccountKey.cs index 5422632..989bd3e 100644 --- a/src/examples/Examples.Common.PKI/AccountKey.cs +++ b/src/examples/Examples.Common.PKI/ExamplesAccountKey.cs @@ -5,7 +5,7 @@ namespace Examples.Common.PKI { - public class AccountKey + public class ExamplesAccountKey { public string KeyType { get; set; } public string KeyExport { get; set; } From 96ce26a665a259d48bdec8f58eef89761d81124e Mon Sep 17 00:00:00 2001 From: Eugene Bekker Date: Mon, 14 Oct 2019 22:58:26 -0400 Subject: [PATCH 7/8] switching to use SimplePKI to better support exports for all key types --- src/examples/ACMECLI/ACMECLI.csproj | 2 +- src/examples/ACMECLI/Program.cs | 100 ++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/examples/ACMECLI/ACMECLI.csproj b/src/examples/ACMECLI/ACMECLI.csproj index c1ceaf6..f6d850a 100644 --- a/src/examples/ACMECLI/ACMECLI.csproj +++ b/src/examples/ACMECLI/ACMECLI.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/examples/ACMECLI/Program.cs b/src/examples/ACMECLI/Program.cs index bfac205..801ddc6 100644 --- a/src/examples/ACMECLI/Program.cs +++ b/src/examples/ACMECLI/Program.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net.Http; @@ -9,17 +8,15 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using ACMESharp; using ACMESharp.Authorizations; -using ACMESharp.Crypto; using ACMESharp.Crypto.JOSE; using ACMESharp.Protocol; -using ACMESharp.Protocol.Messages; using ACMESharp.Protocol.Resources; using Examples.Common; using Examples.Common.PKI; using McMaster.Extensions.CommandLineUtils; using Newtonsoft.Json; +using PKISharp.SimplePKI; namespace ACMECLI { @@ -29,7 +26,7 @@ class Program [Option(ShortName = "", Description = "Directory to store stateful information; defaults to current")] public string State { get; } = "."; - [Option(ShortName = "", Description = "Name of a predefined ACME CA base endpoint")] + [Option(ShortName = "", Description = "Name of a predefined ACME CA base endpoint (specify invalid value to see list)")] [AllowedValues( Constants.LetsEncryptName, Constants.LetsEncryptStagingName, @@ -114,9 +111,10 @@ class Program [Option(ShortName = "", Description = "Save the certificate chain (PEM) to the named file path")] public string ExportCert { get; } - [Option(ShortName = "", Description = "Save the certificate chain and private key (PKCS12) to the named file path")] + [Option(ShortName = "", Description = "Save the certificate chain as PFX (PKCS12) to the named file path")] public string ExportPfx { get; } + [Option(ShortName = "", Description = "Includes the private key to the PFX (PKCS12) and secures with specified password (use ' ' for no password)")] public string ExportPfxPassword { get; } @@ -173,6 +171,7 @@ public async Task OnExecute() } IJwsTool accountSigner = default; + ExamplesAccountKey accountKey = default; string accountKeyHash = default; if (LoadStateInto(ref accountKey, failThrow: false, Constants.AcmeAccountKeyFile)) @@ -213,7 +212,7 @@ public async Task OnExecute() account = await _acme.CreateAccountAsync(Email.Select(x => "mailto:" + x), AcceptTos); accountSigner = _acme.Signer; - accountKey = new AccountKey + accountKey = new ExamplesAccountKey { KeyType = accountSigner.JwsAlg, KeyExport = accountSigner.Export(), @@ -425,6 +424,9 @@ void AddStatusCount(string status, int add) throw new Exception("Cannot finalize Order until all Authorizations are valid"); } + PkiKeyPair keyPair = null; + PkiCertificateSigningRequest csr = null; + string certKeys = null; byte[] certCsr = null; @@ -441,23 +443,19 @@ void AddStatusCount(string status, int add) switch (KeyAlgor) { case Constants.RsaKeyType: - certKeys = CryptoHelper.Rsa.GenerateKeys(KeySize ?? Constants.DefaultAlgorKeySizeMap[KeyAlgor]); - using (var rsa = CryptoHelper.Rsa.GenerateAlgorithm(certKeys)) - { - certCsr = CryptoHelper.Rsa.GenerateCsr(Dns, rsa); - } + keyPair = PkiKeyPair.GenerateRsaKeyPair(KeySize ?? Constants.DefaultAlgorKeySizeMap[KeyAlgor]); break; case Constants.EcKeyType: - certKeys = CryptoHelper.Ec.GenerateKeys(KeySize ?? Constants.DefaultAlgorKeySizeMap[KeyAlgor]); - using (var ec = CryptoHelper.Ec.GenerateAlgorithm(certKeys)) - { - certCsr = CryptoHelper.Ec.GenerateCsr(Dns, ec); - } + keyPair = PkiKeyPair.GenerateEcdsaKeyPair(KeySize ?? Constants.DefaultAlgorKeySizeMap[KeyAlgor]); break; default: throw new Exception($"Unknown key algorithm type [{KeyAlgor}]"); } + csr = GenerateCsr(Dns, keyPair); + certKeys = Save(keyPair); + certCsr = csr.ExportSigningRequest(PkiEncodingFormat.Der); + SaveStateFrom(certKeys, Constants.AcmeOrderCertKeyFmt, orderId); SaveStateFrom(certCsr, Constants.AcmeOrderCertCsrFmt, orderId); } @@ -535,18 +533,37 @@ void AddStatusCount(string status, int add) if (ExportPfx != null) { - Console.WriteLine("Exporting Certificate as PKCS12..."); - using (var cert = new X509Certificate2(LoadRaw(true, Constants.AcmeOrderCertFmt, orderId))) - using (var privateKey = new RSACryptoServiceProvider()) + Console.WriteLine("Exporting Certificate as PFX (PKCS12)..."); + + PkiKey privateKey = null; + var pfxPassword = ExportPfxPassword; + if (pfxPassword != null) { + Console.WriteLine("...including private key in export"); string certKeys = default; - LoadStateInto(ref certKeys, failThrow: true, Constants.AcmeOrderCertKeyFmt, orderId); - var rsaParameters = JsonConvert.DeserializeObject(certKeys); - privateKey.ImportParameters(rsaParameters); - using(var certificateWithPrivateKey = cert.CopyWithPrivateKey(privateKey)) + LoadStateInto(ref certKeys, failThrow: true, + Constants.AcmeOrderCertKeyFmt, orderId); + var keyPair = Load(certKeys); + privateKey = keyPair.PrivateKey; + if (pfxPassword == " ") { - await File.WriteAllBytesAsync(ExportPfx, certificateWithPrivateKey.Export(X509ContentType.Pkcs12, ExportPfxPassword)); + Console.WriteLine("...WITH NO PASSWORD"); + pfxPassword = null; } + else + { + Console.WriteLine("...securing with password"); + } + } + + using (var cert = new X509Certificate2(LoadRaw(true, Constants.AcmeOrderCertFmt, orderId))) + { + var pkiCert = PkiCertificate.From(cert); + var pfx = pkiCert.Export(PkiArchiveFormat.Pkcs12, + privateKey: privateKey, + password: pfxPassword?.ToCharArray()); + + await File.WriteAllBytesAsync(ExportPfx, pfx); } } } @@ -580,7 +597,8 @@ private async Task ProcessDns01(IJwsTool accountSigner, Authorization authz, while (true) { string err = null; - var dnsValues = (await DnsUtil.LookupRecordAsync(dnsCd.DnsRecordType, dnsCd.DnsRecordName)).Select(x => x.Trim('"')); + var lookup = await DnsUtil.LookupRecordAsync(dnsCd.DnsRecordType, dnsCd.DnsRecordName); + var dnsValues = lookup?.Select(x => x.Trim('"')); if (dnsValues == null) { err = "Could not resolve *any* DNS entries for Challenge record name"; @@ -763,5 +781,35 @@ private string ComputeHash(string value) return BitConverter.ToString(hash).Replace("-", ""); } } + + private string Save(PkiKeyPair keyPair) + { + using (var ms = new MemoryStream()) + { + keyPair.Save(ms); + return Convert.ToBase64String(ms.ToArray()); + } + } + + private PkiKeyPair Load(string b64) + { + using (var ms = new MemoryStream(Convert.FromBase64String(b64))) + { + return PkiKeyPair.Load(ms); + } + } + + private PkiCertificateSigningRequest GenerateCsr(IEnumerable dnsNames, + PkiKeyPair keyPair) + { + var firstDns = dnsNames.First(); + var csr = new PkiCertificateSigningRequest($"CN={firstDns}", keyPair, + PkiHashAlgorithm.Sha256); + + csr.CertificateExtensions.Add( + PkiCertificateExtension.CreateDnsSubjectAlternativeNames(dnsNames)); + + return csr; + } } } From 485e53f5aa264515d988e5a7ba8b78e90a892188 Mon Sep 17 00:00:00 2001 From: Eugene Bekker Date: Mon, 14 Oct 2019 23:10:34 -0400 Subject: [PATCH 8/8] updating CLI usage and sample CLI calls --- src/examples/ACMECLI/README.md | 54 ++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/examples/ACMECLI/README.md b/src/examples/ACMECLI/README.md index 1d65cb8..1feb4bf 100644 --- a/src/examples/ACMECLI/README.md +++ b/src/examples/ACMECLI/README.md @@ -26,29 +26,31 @@ The tool provides for the following parameters and options: Usage: ACMECLI [options] Options: - --state Directory to store stateful information; defaults to current - --ca-name Name of a predefined ACME CA base endpoint - --ca-url Full URL of an ACME CA endpoint; this option overrides CaName - --refresh-dir Flag indicates to refresh the current cached ACME Directory of service endpoints for the target CA - --email One or more emails to be registered as account contact info (can be repeated) - --accept-tos Flag indicates that you agree to CA's terms of service - --dns One or more DNS names to include in the cert; the first is primary subject name, subsequent are subject alternative names (can be repeated) - --refresh-order Flag indicates to refresh the state of pending ACME Order - --challenge-type Indicates that only one specific Challenge type should be handled - --refresh-challenges Flag indicates to refresh the state of the Challenges of the pending ACME Order - --test-challenges Flag indicates to check if the Challenges have been handled correctly - --wait-for-test: Flag indicates to wait until Challenge tests are successfully validated, optionally override the default timeout of 300 (seconds) - --answer-challenges Flag indicates to submit Answers to pending Challenges - --wait-for-authz: Flag indicates to wait until Authorizations become Valid, optionally override the default timeout of 300 (seconds) - --finalize Flag indicates to finalize the pending ACME Order - --key-algor Indicates the encryption algorithm of certificate keys, defaults to RSA - --key-size Indicates the encryption algorithm key size, defaults to 2048 (RSA) or 256 (EC) - --regenerate-csr Flag indicates to regenerate a certificate key pair and CSR - --refresh-cert Flag indicates to refresh the local cache of an issued certificate - --wait-for-cert: Flag indicates to wait until Certificate is available, optionally override the default timeout of 300 (seconds) - --export-cert Save the certificate chain (PEM) to the named file path - --export-pfx Save the certificate chain and private key (PKCS12) to the named file path - -?|-h|--help Show help information + --state Directory to store stateful information; defaults to current + --ca-name Name of a predefined ACME CA base endpoint (specify invalid value to see list) + --ca-url Full URL of an ACME CA endpoint; this option overrides CaName + --refresh-dir Flag indicates to refresh the current cached ACME Directory of service endpoints for the target CA + --email One or more emails to be registered as account contact info (can be repeated) + --accept-tos Flag indicates that you agree to CA's terms of service + --dns One or more DNS names to include in the cert; the first is primary subject name, subsequent are subject alternative names (can be repeated) + --name-server One or more DNS name servers to be used to resolve host entries, such as during testing (can be repeated) + --refresh-order Flag indicates to refresh the state of pending ACME Order + --challenge-type Indicates that only one specific Challenge type should be handled + --refresh-challenges Flag indicates to refresh the state of the Challenges of the pending ACME Order + --test-challenges Flag indicates to check if the Challenges have been handled correctly + --wait-for-test[:] Flag indicates to wait until Challenge tests are successfully validated, optionally override the default timeout of 300 (seconds) + --answer-challenges Flag indicates to submit Answers to pending Challenges + --wait-for-authz[:] Flag indicates to wait until Authorizations become Valid, optionally override the default timeout of 300 (seconds) + --finalize Flag indicates to finalize the pending ACME Order + --key-algor Indicates the encryption algorithm of certificate keys, defaults to RSA + --key-size Indicates the encryption algorithm key size, defaults to 2048 (RSA) or 256 (EC) + --regenerate-csr Flag indicates to regenerate a certificate key pair and CSR + --refresh-cert Flag indicates to refresh the local cache of an issued certificate + --wait-for-cert[:] Flag indicates to wait until Certificate is available, optionally override the default timeout of 300 (seconds) + --export-cert Save the certificate chain (PEM) to the named file path + --export-pfx Save the certificate chain as PFX (PKCS12) to the named file path + --export-pfx-password Includes the private key to the PFX (PKCS12) and secures with specified password (use ' ' for no password) + -?|-h|--help Show help information ``` You can invoke it piecemeal and complete each step independently or you can combine all the @@ -94,9 +96,9 @@ private key and generate the CSR to submit to the CA. ``` Finally, save the complete certificate chain and corresponding -private key to a PKCS#12 format file. +private key to a PKCS#12 format file with NO password. ```shell -> acmecli --dns myapp.example.com --dns myapp-0.example.com --dns myapp-1.example.com --export-pfx mycertificate.pfx +> acmecli --dns myapp.example.com --dns myapp-0.example.com --dns myapp-1.example.com --export-pfx mycertificate.pfx --export-pfx-password " " ``` ## Invoke in _One Fell Swoop_ @@ -107,7 +109,7 @@ either the ACME CA or from your actions, i.e. by completing the Challenges. ```shell ## In this case all the DNS Identifiers are wildcards, so the ## CA will only issue DNS type Challenges as per the ACME spec -> acmecli --email jane.doe@example.com --email john.doe@email.com --accept-tos --dns *.example.com --dns *.example.net --test-challenges --wait-for-test:600 --answer-challenges --wait-for-authz --finalize --key-algor ec --key-size 256 --wait-for-cert --export-cert my-example.pem --export-pfx my-example.pfx +> acmecli --email jane.doe@example.com --email john.doe@email.com --accept-tos --dns *.example.com --dns *.example.net --test-challenges --wait-for-test:600 --answer-challenges --wait-for-authz --finalize --key-algor ec --key-size 256 --wait-for-cert --export-cert my-example.pem --export-pfx my-example.pfx --export-pfx-password " " ``` With the single command above: