From 283c9951f0821fef4cf65eae9127d19d1f08247b Mon Sep 17 00:00:00 2001 From: Eddie Lin <5827855+fszlin@users.noreply.github.com> Date: Thu, 12 Jul 2018 21:41:00 -0400 Subject: [PATCH] [CLI] add `--issuer` option for exporting PFX (#143) --- docs/CHANGELOG.md | 5 +++++ docs/docstrap | 2 +- src/Certes.Cli/Commands/CertificatePfxCommand.cs | 14 +++++++++++++- src/Certes.Cli/Strings.Designer.cs | 9 +++++++++ src/Certes.Cli/Strings.resx | 3 +++ .../Cli/Commands/CertificatePfxCommandTests.cs | 16 +++++++++++++++- 6 files changed, 46 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1dece60d..4925154f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog All notable changes to this project will be documented in this file. +## [Unreleased] +### Added +- [CLI] Add `--issuer` option to `cert pfx` command ([#142][i142]) + ## [2.3.0] - 2018-06-15 ### Added - Support `tls-alpn-01` challenge ([#125][i125]) @@ -99,3 +103,4 @@ All notable changes to this project will be documented in this file. [i112]: https://github.com/fszlin/certes/issues/112 [i125]: https://github.com/fszlin/certes/issues/125 [i109]: https://github.com/fszlin/certes/issues/109 +[i142]: https://github.com/fszlin/certes/issues/142 diff --git a/docs/docstrap b/docs/docstrap index 4536e7a9..74b223da 160000 --- a/docs/docstrap +++ b/docs/docstrap @@ -1 +1 @@ -Subproject commit 4536e7a988c6352fa194f06794f476373f2b0e55 +Subproject commit 74b223da9f641ccdfab85add3b50010eaf869f7f diff --git a/src/Certes.Cli/Commands/CertificatePfxCommand.cs b/src/Certes.Cli/Commands/CertificatePfxCommand.cs index 3c20f655..f3d86f8b 100644 --- a/src/Certes.Cli/Commands/CertificatePfxCommand.cs +++ b/src/Certes.Cli/Commands/CertificatePfxCommand.cs @@ -1,6 +1,7 @@ using System; using System.CommandLine; using System.Globalization; +using System.Text; using System.Threading.Tasks; using Certes.Cli.Settings; using NLog; @@ -13,6 +14,7 @@ internal class CertificatePfxCommand : CertificateCommand, ICliCommand private const string PasswordParam = "password"; private const string PrivateKeyOption = "private-key"; private const string OutOption = "out"; + private const string IssuerOption = "issuer"; private static readonly ILogger logger = LogManager.GetLogger(nameof(CertificatePfxCommand)); private readonly IEnvironmentVariables environment; @@ -36,6 +38,7 @@ public ArgumentCommand Define(ArgumentSyntax syntax) .DefineKeyOption() .DefineOption(OutOption, help: Strings.HelpCertificateOut) .DefineOption(PrivateKeyOption, help: Strings.HelpPrivateKey) + .DefineOption(IssuerOption, help: Strings.HelpCertificateIssuer) .DefineUriParameter(OrderIdParam, help: Strings.HelpOrderId) .DefineParameter(PasswordParam, help: Strings.HelpPfxPassword); @@ -46,11 +49,20 @@ public async Task Execute(ArgumentSyntax syntax) { var keyPath = syntax.GetParameter(PrivateKeyOption, true); var pwd = syntax.GetParameter(PasswordParam, true); + var issuer = syntax.GetParameter(IssuerOption); var (location, cert) = await DownloadCertificate(syntax); var pfxName = string.Format(CultureInfo.InvariantCulture, "[certes] {0:yyyyMMddhhmmss}", DateTime.UtcNow); var privKey = await syntax.ReadKey(PrivateKeyOption, "CERTES_CERT_KEY", File, environment, true); - var pfx = cert.ToPfx(privKey).Build(pfxName, pwd); + + var pfxBuilder = cert.ToPfx(privKey); + if (!string.IsNullOrWhiteSpace(issuer)) + { + var issuerPem = await File.ReadAllText(issuer); + pfxBuilder.AddIssuers(Encoding.UTF8.GetBytes(issuerPem)); + } + + var pfx = pfxBuilder.Build(pfxName, pwd); var outPath = syntax.GetOption(OutOption); if (string.IsNullOrWhiteSpace(outPath)) diff --git a/src/Certes.Cli/Strings.Designer.cs b/src/Certes.Cli/Strings.Designer.cs index d696c810..169a0220 100644 --- a/src/Certes.Cli/Strings.Designer.cs +++ b/src/Certes.Cli/Strings.Designer.cs @@ -196,6 +196,15 @@ internal static string HelpAzureTenantId { } } + /// + /// Looks up a localized string similar to Additional certificates for building the certificate chain.. + /// + internal static string HelpCertificateIssuer { + get { + return ResourceManager.GetString("HelpCertificateIssuer", resourceCulture); + } + } + /// /// Looks up a localized string similar to Path to save the certificate.. /// diff --git a/src/Certes.Cli/Strings.resx b/src/Certes.Cli/Strings.resx index 67314167..f3b07dd4 100644 --- a/src/Certes.Cli/Strings.resx +++ b/src/Certes.Cli/Strings.resx @@ -267,4 +267,7 @@ Configure default credentials for Microsoft Azure. + + Additional certificates for building the certificate chain. + \ No newline at end of file diff --git a/test/Certes.Tests/Cli/Commands/CertificatePfxCommandTests.cs b/test/Certes.Tests/Cli/Commands/CertificatePfxCommandTests.cs index 71d9bd4e..8853a1b8 100644 --- a/test/Certes.Tests/Cli/Commands/CertificatePfxCommandTests.cs +++ b/test/Certes.Tests/Cli/Commands/CertificatePfxCommandTests.cs @@ -1,5 +1,6 @@ using System; using System.CommandLine; +using System.Linq; using System.Threading.Tasks; using Certes.Acme; using Certes.Acme.Resource; @@ -75,16 +76,29 @@ public async Task CanProcessCommand() JsonConvert.SerializeObject(ret)); fileMock.Verify(m => m.WriteAllBytes(outPath, It.IsAny()), Times.Once); + fileMock.ResetCalls(); + + // Export PFX with external issuers + var leafCert = certChain.Certificate.ToPem(); + orderMock.Setup(m => m.Download()).ReturnsAsync(new CertificateChain(leafCert)); + + var issuersPem = string.Join(Environment.NewLine, certChain.Issuers.Select(i => i.ToPem())); + fileMock.Setup(m => m.ReadAllText("./issuers.pem")).ReturnsAsync(issuersPem); + + syntax = DefineCommand($"pfx {orderLoc} --private-key {privateKeyPath} abcd1234 --out {outPath} --issuer ./issuers.pem"); + ret = await cmd.Execute(syntax); + fileMock.Verify(m => m.WriteAllBytes(outPath, It.IsAny()), Times.Once); } [Fact] public void CanDefineCommand() { - var args = $"pfx http://acme.com/o/1 --private-key ./my-key.pem abcd1234 --server {LetsEncryptStagingV2}"; + var args = $"pfx http://acme.com/o/1 --private-key ./my-key.pem abcd1234 --server {LetsEncryptStagingV2} --issuer ./root-cert.pem"; var syntax = DefineCommand(args); Assert.Equal("pfx", syntax.ActiveCommand.Value); ValidateOption(syntax, "server", LetsEncryptStagingV2); + ValidateOption(syntax, "issuer", "./root-cert.pem"); ValidateParameter(syntax, "order-id", new Uri("http://acme.com/o/1")); ValidateParameter(syntax, "private-key", "./my-key.pem"); ValidateParameter(syntax, "password", "abcd1234");