From daf0632bc83d3c0abb440a231a7e896412a05e3f Mon Sep 17 00:00:00 2001 From: Eddie Lin <5827855+fszlin@users.noreply.github.com> Date: Sun, 4 Mar 2018 17:32:20 -0500 Subject: [PATCH 1/7] Fix NRE when server response has no Content-Type header (#62) * add `null` check when processing HTTP response * merge `full-chain-off` option for CLI fix --- appveyor.yml | 2 +- docs/CHANGELOG.md | 10 +++++++++- src/Certes.Cli/Processors/CertificateCommand.cs | 6 ++++++ src/Certes/Acme/AcmeHttpHandler.cs | 12 ++++++++---- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 1aeb65b1..afe70423 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.1.3.{build} +version: 1.1.4.{build} build: verbosity: minimal project: Certes.sln diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 895fce53..5ec8b91e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,7 +1,13 @@ # Changelog All notable changes to this project will be documented in this file. -## [Unreleased] +## [1.1.4] +### Changed +- Fix error when processing server response without `Content-Type` header +- Fix `full-chain-off` option for CLI + +## [1.1.3] - 2017-11-23 +### Changed - Fix MissingFieldException when running with BouncyCastle v1.8.1.3 ([#22][i22]) ## [1.1.2] - 2017-09-27 @@ -29,6 +35,8 @@ All notable changes to this project will be documented in this file. [1.1.0]: https://github.com/fszlin/certes/compare/v1.0.7...v1.1.0 [1.1.1]: https://github.com/fszlin/certes/compare/v1.1.0...v1.1.1 [1.1.2]: https://github.com/fszlin/certes/compare/v1.1.1...v1.1.2 +[1.1.3]: https://github.com/fszlin/certes/compare/v1.1.2...v1.1.3 +[1.1.4]: https://github.com/fszlin/certes/compare/v1.1.3...v1.1.4 [i5]: https://github.com/fszlin/certes/issues/5 [i22]: https://github.com/fszlin/certes/issues/22 diff --git a/src/Certes.Cli/Processors/CertificateCommand.cs b/src/Certes.Cli/Processors/CertificateCommand.cs index 92bca4d3..4a188a27 100644 --- a/src/Certes.Cli/Processors/CertificateCommand.cs +++ b/src/Certes.Cli/Processors/CertificateCommand.cs @@ -65,6 +65,12 @@ public override async Task Process(AcmeContext context) } var pfxBuilder = cert.ToPfx(); + + if (Options.NoChain) + { + pfxBuilder.FullChain = false; + } + var pfx = pfxBuilder.Build(Options.Name, Options.Password); await FileUtil.WriteAllBytes(Options.ExportPfx, pfx); } diff --git a/src/Certes/Acme/AcmeHttpHandler.cs b/src/Certes/Acme/AcmeHttpHandler.cs index c460c1e7..1c179dd8 100644 --- a/src/Certes/Acme/AcmeHttpHandler.cs +++ b/src/Certes/Acme/AcmeHttpHandler.cs @@ -165,7 +165,9 @@ private async Task GenerateRequestContent(object entity, IAccount private async Task> ReadResponse(HttpResponseMessage response, string resourceType = null) { var data = new AcmeRespone(); - if (IsJsonMedia(response.Content?.Headers.ContentType.MediaType)) + + ParseHeaders(data, response); + if (IsJsonMedia(response.Content?.Headers.ContentType?.MediaType)) { var json = await response.Content.ReadAsStringAsync(); data.Json = json; @@ -182,14 +184,16 @@ private async Task> ReadResponse(HttpResponseMessage response, { data.Error = JsonConvert.DeserializeObject(json, jsonSettings); } + + // take the replay-nonce from JOSN response + // it appears the nonces returned with certificate are invalid + nonce = data.ReplayNonce; } else if (response.Content?.Headers.ContentLength > 0) { data.Raw = await response.Content.ReadAsByteArrayAsync(); } - ParseHeaders(data, response); - this.nonce = data.ReplayNonce; return data; } @@ -231,7 +235,7 @@ private static void ParseHeaders(AcmeRespone data, HttpResponseMessage res .ToArray(); } - data.ContentType = response.Content?.Headers.ContentType.MediaType; + data.ContentType = response.Content?.Headers.ContentType?.MediaType; } private static bool IsJsonMedia(string mediaType) From 44ba2648e2d4b35273ae3431c62d39efce1fdb3b Mon Sep 17 00:00:00 2001 From: Eddie Lin <5827855+fszlin@users.noreply.github.com> Date: Tue, 13 Mar 2018 13:36:40 -0400 Subject: [PATCH 2/7] v2 release (#72) --- docs/CHANGELOG.md | 5 +++-- docs/README.md | 3 --- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 40e1d1fd..7f2de9c3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog All notable changes to this project will be documented in this file. -## [Unreleased] +## [2.0.0] - 2018-03-13 ### Added - [ACME v2](APIv2.md) support - Add support for JSON web signature using ECDSA key @@ -34,11 +34,12 @@ All notable changes to this project will be documented in this file. ### Changed - Fix error when parsing directory resource with *meta* property. ([#5][i5]) -[Unreleased]: https://github.com/fszlin/certes/compare/v1.1.3...HEAD +[Unreleased]: https://github.com/fszlin/certes/compare/v2.0.0...HEAD [1.1.0]: https://github.com/fszlin/certes/compare/v1.0.7...v1.1.0 [1.1.1]: https://github.com/fszlin/certes/compare/v1.1.0...v1.1.1 [1.1.2]: https://github.com/fszlin/certes/compare/v1.1.1...v1.1.2 [1.1.3]: https://github.com/fszlin/certes/compare/v1.1.2...v1.1.3 +[2.0.0]: https://github.com/fszlin/certes/compare/v1.1.3...v2.0.0 [i5]: https://github.com/fszlin/certes/issues/5 [i22]: https://github.com/fszlin/certes/issues/22 diff --git a/docs/README.md b/docs/README.md index 3c6ad6f8..dadf825e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,9 +4,6 @@ Certes is an [ACME](https://en.wikipedia.org/wiki/Automated_Certificate_Manageme client runs on .NET 4.5+ and .NET Standard 1.3+, supports ACME v2 and wildcard certificates. It is aimed to provide an easy to use API for managing certificates during deployment processes. -**Util Let's Encrypt releases [v2 endpoint](https://community.letsencrypt.org/t/acmev2-and-wildcard-launch-delay/53654), -please continue to use [v1 API](https://github.com/fszlin/certes/blob/master/docs/README.v1.md) for production.** - ## Usage Install [Certes](https://www.nuget.org/packages/Certes/) nuget package into your project: From 7e00123c87b0f97d7ec848cde78f80fc4b27dd5a Mon Sep 17 00:00:00 2001 From: Eddie Lin <5827855+fszlin@users.noreply.github.com> Date: Tue, 13 Mar 2018 14:47:59 -0400 Subject: [PATCH 3/7] update package versions (#73) * fix tag names * bump pkg versions * update :package: --- appveyor.yml | 4 ++-- src/Certes.Cli/Certes.Cli.csproj | 2 +- src/Certes/Certes.csproj | 2 +- test/Certes.Tests.Web/Certes.Tests.Web.csproj | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index f45e96fd..9e6d8e8a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -86,8 +86,8 @@ artifacts: deploy: - provider: GitHub - release: v$(CERTES_PACKAGE_VER) - description: 'Certes v$(CERTES_PACKAGE_VER)' + release: v$(CERTES_PACKAGE_VERSION) + description: 'Certes v$(CERTES_PACKAGE_VERSION)' auth_token: secure: B+lTI7i/tnZeg1ZSmho3HvOWjs0C4hptNy5cvWgF0Nn7b6v8nwT/mxEWVCfIJ7Fy artifact: nupkg,cli diff --git a/src/Certes.Cli/Certes.Cli.csproj b/src/Certes.Cli/Certes.Cli.csproj index 0fc9cb2c..a88a64eb 100644 --- a/src/Certes.Cli/Certes.Cli.csproj +++ b/src/Certes.Cli/Certes.Cli.csproj @@ -5,7 +5,7 @@ Exe netcoreapp1.0;netcoreapp2.0 netcoreapp2.0 - 1.0.0 + 1.0.1 $(AssemblyVersion)$(CertesPackageVersionSuffix) $(AssemblyVersion)$(CertesFileVersionSuffix) $(AssemblyVersion)$(CertesInformationalVersionSuffix) diff --git a/src/Certes/Certes.csproj b/src/Certes/Certes.csproj index 97c463f5..2c19a457 100644 --- a/src/Certes/Certes.csproj +++ b/src/Certes/Certes.csproj @@ -3,7 +3,7 @@ netstandard2.0;netstandard1.3;net45;net47 - 2.0.0 + 2.0.1 $(AssemblyVersion)$(CertesPackageVersionSuffix) $(AssemblyVersion)$(CertesFileVersionSuffix) $(AssemblyVersion)$(CertesInformationalVersionSuffix) diff --git a/test/Certes.Tests.Web/Certes.Tests.Web.csproj b/test/Certes.Tests.Web/Certes.Tests.Web.csproj index b3be4b48..dc743d2b 100644 --- a/test/Certes.Tests.Web/Certes.Tests.Web.csproj +++ b/test/Certes.Tests.Web/Certes.Tests.Web.csproj @@ -21,7 +21,7 @@ - + From 648df8a7ecd23c031414831393172283df379ce8 Mon Sep 17 00:00:00 2001 From: Eddie Lin <5827855+fszlin@users.noreply.github.com> Date: Tue, 13 Mar 2018 15:12:30 -0400 Subject: [PATCH 4/7] update cli (#71) * update pfx friendly name * fix az cert upload error --- src/Certes.Cli/Commands/AzureAppCommand.cs | 69 +++++++++++++++---- .../Commands/CertificatePemCommand.cs | 2 +- .../Commands/CertificatePfxCommand.cs | 7 +- .../Cli/Commands/AzureAppCommandTests.cs | 54 ++++++++++++++- 4 files changed, 114 insertions(+), 18 deletions(-) diff --git a/src/Certes.Cli/Commands/AzureAppCommand.cs b/src/Certes.Cli/Commands/AzureAppCommand.cs index 5fa90598..4b07c8b2 100644 --- a/src/Certes.Cli/Commands/AzureAppCommand.cs +++ b/src/Certes.Cli/Commands/AzureAppCommand.cs @@ -1,7 +1,10 @@ using System; using System.CommandLine; +using System.Globalization; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Certes.Cli.Settings; +using Certes.Pkcs; using Microsoft.Azure.Management.AppService.Fluent; using Microsoft.Azure.Management.AppService.Fluent.Models; @@ -70,29 +73,24 @@ public async Task Execute(ArgumentSyntax syntax) } var cert = await orderCtx.Download(); - - var pfxName = $"{order.Certificate} by certes"; - var pfxPassword = Guid.NewGuid().ToString("N"); - var pfx = cert.ToPfx(privKey); - var pfxBytes = pfx.Build(pfxName, pfxPassword); - - var certData = new CertificateInner - { - PfxBlob = pfxBytes, - Password = pfxPassword, - }; + var x509Cert = new X509Certificate2(cert.Certificate.ToDer()); + var thumbprint = x509Cert.Thumbprint; using (var client = clientFactory.Invoke(azureCredentials)) { client.SubscriptionId = azureCredentials.DefaultSubscriptionId; - var certUpdated = await client.Certificates.CreateOrUpdateAsync( - resourceGroup, pfxName, certData); + var certUploaded = await FindCertificate(client, resourceGroup, thumbprint); + if (certUploaded == null) + { + certUploaded = await UploadCertificate( + client, resourceGroup, appName, appSlot, cert.ToPfx(privKey), thumbprint); + } var hostNameBinding = new HostNameBindingInner { SslState = SslState.SniEnabled, - Thumbprint = certUpdated.Thumbprint, + Thumbprint = certUploaded.Thumbprint, }; var hostName = string.IsNullOrWhiteSpace(appSlot) ? @@ -107,5 +105,48 @@ await client.WebApps.CreateOrUpdateHostNameBindingSlotAsync( }; } } + + private static async Task UploadCertificate( + IWebSiteManagementClient client, string resourceGroup, string appName, string appSlot, PfxBuilder pfx, string thumbprint) + { + var pfxName = string.Format(CultureInfo.InvariantCulture, "[certes] {0:yyyyMMddhhmmss}", DateTime.UtcNow); + var pfxPassword = Guid.NewGuid().ToString("N"); + var pfxBytes = pfx.Build(pfxName, pfxPassword); + + var webApp = string.IsNullOrWhiteSpace(appSlot) ? + await client.WebApps.GetAsync(resourceGroup, appName) : + await client.WebApps.GetSlotAsync(resourceGroup, appName, appSlot); + + var certData = new CertificateInner + { + PfxBlob = pfxBytes, + Password = pfxPassword, + Location = webApp.Location, + }; + + return await client.Certificates.CreateOrUpdateAsync( + resourceGroup, thumbprint, certData); + } + + private static async Task FindCertificate( + IWebSiteManagementClient client, string resourceGroup, string thumbprint) + { + var certificates = await client.Certificates.ListByResourceGroupAsync(resourceGroup); + while (certificates != null) + { + foreach (var azCert in certificates) + { + if (string.Equals(azCert.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase)) + { + return azCert; + } + } + + certificates = certificates.NextPageLink == null ? null : + await client.Certificates.ListByResourceGroupNextAsync(certificates.NextPageLink); + } + + return null; + } } } diff --git a/src/Certes.Cli/Commands/CertificatePemCommand.cs b/src/Certes.Cli/Commands/CertificatePemCommand.cs index 4deffba8..8d9d526d 100644 --- a/src/Certes.Cli/Commands/CertificatePemCommand.cs +++ b/src/Certes.Cli/Commands/CertificatePemCommand.cs @@ -66,7 +66,7 @@ public async Task Execute(ArgumentSyntax syntax) return new { - location = location, + location, }; } diff --git a/src/Certes.Cli/Commands/CertificatePfxCommand.cs b/src/Certes.Cli/Commands/CertificatePfxCommand.cs index 6acd1c7b..3c20f655 100644 --- a/src/Certes.Cli/Commands/CertificatePfxCommand.cs +++ b/src/Certes.Cli/Commands/CertificatePfxCommand.cs @@ -1,4 +1,6 @@ -using System.CommandLine; +using System; +using System.CommandLine; +using System.Globalization; using System.Threading.Tasks; using Certes.Cli.Settings; using NLog; @@ -46,8 +48,9 @@ public async Task Execute(ArgumentSyntax syntax) var pwd = syntax.GetParameter(PasswordParam, true); 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($"{location} by certes", pwd); + var pfx = cert.ToPfx(privKey).Build(pfxName, pwd); var outPath = syntax.GetOption(OutOption); if (string.IsNullOrWhiteSpace(outPath)) diff --git a/test/Certes.Tests/Cli/Commands/AzureAppCommandTests.cs b/test/Certes.Tests/Cli/Commands/AzureAppCommandTests.cs index e040519c..6eb4cdcf 100644 --- a/test/Certes.Tests/Cli/Commands/AzureAppCommandTests.cs +++ b/test/Certes.Tests/Cli/Commands/AzureAppCommandTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.CommandLine; using System.IO; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Certes.Acme; @@ -11,6 +12,7 @@ using Microsoft.Azure.Management.AppService.Fluent.Models; using Microsoft.Rest.Azure; using Moq; +using Newtonsoft.Json; using Xunit; using static Certes.Acme.WellKnownServers; using static Certes.Cli.CliTestHelper; @@ -27,6 +29,7 @@ public async Task CanProcessCommand() var orderLoc = new Uri("http://acme.com/o/1"); var resourceGroup = "resGroup"; var appName = "my-app"; + var appSlot = "staging"; var keyPath = "./cert-key.pem"; var certChainContent = string.Join( @@ -75,6 +78,16 @@ public async Task CanProcessCommand() appSvcMock.SetupGet(m => m.Certificates).Returns(certOpMock.Object); appSvcMock.Setup(m => m.Dispose()); + certOpMock.Setup(m => m.ListByResourceGroupWithHttpMessagesAsync(resourceGroup, default, default)) + .ReturnsAsync(new AzureOperationResponse> + { + Body = JsonConvert.DeserializeObject>( + JsonConvert.SerializeObject(new + { + value = new CertificateInner[0] + }) + ) + }); certOpMock.Setup(m => m.CreateOrUpdateWithHttpMessagesAsync(resourceGroup, It.IsAny(), It.IsAny(), default, default)) .ReturnsAsync((string r, string n, CertificateInner c, Dictionary> h, CancellationToken t) => new AzureOperationResponse { Body = c }); @@ -83,6 +96,22 @@ public async Task CanProcessCommand() resourceGroup, appName, domain, It.IsAny(), default, default)) .ReturnsAsync((string r, string a, string n, HostNameBindingInner d, Dictionary> h, CancellationToken t) => new AzureOperationResponse { Body = d }); + webAppOpMock.Setup(m => m.GetWithHttpMessagesAsync(resourceGroup, appName, default, default)) + .ReturnsAsync(new AzureOperationResponse + { + Body = new SiteInner + { + Location = "Canada" + } + }); + webAppOpMock.Setup(m => m.GetSlotWithHttpMessagesAsync(resourceGroup, appName, appSlot, default, default)) + .ReturnsAsync(new AzureOperationResponse + { + Body = new SiteInner + { + Location = "Canada" + } + }); var envMock = new Mock(MockBehavior.Strict); @@ -100,7 +129,6 @@ public async Task CanProcessCommand() resourceGroup, appName, domain, It.IsAny(), default, default), Times.Once); // with deployment slot - var appSlot = "staging"; webAppOpMock.Setup(m => m.CreateOrUpdateHostNameBindingSlotWithHttpMessagesAsync( resourceGroup, appName, domain, It.IsAny(), appSlot, default, default)) .ReturnsAsync((string r, string a, string n, HostNameBindingInner d, string s, Dictionary> h, CancellationToken t) @@ -116,6 +144,30 @@ public async Task CanProcessCommand() webAppOpMock.Verify(m => m.CreateOrUpdateHostNameBindingSlotWithHttpMessagesAsync( resourceGroup, appName, domain, It.IsAny(), appSlot, default, default), Times.Once); + var cert = new X509Certificate2(certChain.Certificate.ToDer()); + certOpMock.Setup(m => m.ListByResourceGroupWithHttpMessagesAsync(resourceGroup, default, default)) + .ReturnsAsync(new AzureOperationResponse> + { + Body = JsonConvert.DeserializeObject>( + JsonConvert.SerializeObject(new + { + value = new CertificateInner[] + { + new CertificateInner("certes", thumbprint: "another-cert"), + new CertificateInner("certes", thumbprint: cert.Thumbprint) + } + }) + ) + }); + + args = $"app {orderLoc} {domain} {appName} --private-key {keyPath}" + + $" --talent-id talentId --client-id clientId --client-secret abcd1234" + + $" --subscription-id {Guid.NewGuid()} --resource-group {resourceGroup}"; + syntax = DefineCommand(args); + ret = await cmd.Execute(syntax); + Assert.NotNull(ret.data); + Assert.Equal(cert.Thumbprint, ret.data.Thumbprint); + // order incompleted orderMock.Setup(m => m.Resource()).ReturnsAsync(new Order()); args = $"app {orderLoc} {domain} {appName} --private-key {keyPath}" From 179686140356bdabfdd9358e8b97117ee47f93d7 Mon Sep 17 00:00:00 2001 From: Eddie Lin <5827855+fszlin@users.noreply.github.com> Date: Wed, 14 Mar 2018 00:10:55 -0400 Subject: [PATCH 5/7] update document for v2 (#74) * fix change log * add DNS validation sample * add CLI intro --- docs/CHANGELOG.md | 4 ++-- docs/README.md | 54 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b71074d4..4420c939 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. - [ACME v2](APIv2.md) support - Add support for JSON web signature using ECDSA key -## [1.1.4] +## [1.1.4] - 2018-03-04 ### Changed - Fix error when processing server response without `Content-Type` header - Fix `full-chain-off` option for CLI @@ -42,7 +42,7 @@ All notable changes to this project will be documented in this file. [1.1.2]: https://github.com/fszlin/certes/compare/v1.1.1...v1.1.2 [1.1.3]: https://github.com/fszlin/certes/compare/v1.1.2...v1.1.3 [1.1.4]: https://github.com/fszlin/certes/compare/v1.1.3...v1.1.4 -[2.0.0]: https://github.com/fszlin/certes/compare/v1.1.3...v2.0.0 +[2.0.0]: https://github.com/fszlin/certes/compare/v1.1.4...v2.0.0 [i5]: https://github.com/fszlin/certes/issues/5 [i22]: https://github.com/fszlin/certes/issues/22 diff --git a/docs/README.md b/docs/README.md index dadf825e..45c82c55 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,11 +7,11 @@ It is aimed to provide an easy to use API for managing certificates during deplo ## Usage Install [Certes](https://www.nuget.org/packages/Certes/) nuget package into your project: -``` +```PowerShell Install-Package Certes ``` or using .NET CLI: -``` +```Batchfile dotnet add package Certes ``` @@ -21,7 +21,22 @@ var acme = new AcmeContext(WellKnownServers.LetsEncryptStagingV2); var account = acme.NewAccount("admin@example.com", true); ``` -Place an order for certificate +Place a wildcard certificate order +*(DNS validation is required for wildcard certificates)* +```C# +var order = await acme.NewOrder(new[] { "*.your.domain.name" }); +``` + +Generate the value for DNS TXT record +```C# +var authz = (await order.Authorizations()).First(); +var dnsChallenge = await authz.Dns(); +var dnsTxt = acme.AccountKey.DnsTxt(dnsChallenge.Token); +``` +Add a DNS TXT record to `_acme-challenge.your.domain.name` +with `dnsTxt` value. + +For non-wildcard certificate, HTTP challenge is also available ```C# var order = await acme.NewOrder(new[] { "your.domain.name" }); ``` @@ -33,12 +48,12 @@ var httpChallenge = await authz.Http(); var keyAuthz = httpChallenge.KeyAuthz; ``` -Prepare for http challenge by saving the **key authorization string** -in a text file, and upload it to `http://your.domain.name/.well-known/acme-challenge/` +Save the **key authorization string** in a text file, +and upload it to `http://your.domain.name/.well-known/acme-challenge/` Ask the ACME server to validate our domain ownership ```C# -await httpChallenge.Validate(); +await challenge.Validate(); ``` Download the certificate once validation is done @@ -65,18 +80,41 @@ Check the [APIs](APIv2.md) for more details. *For ACME v1, please see [the doc here](README.v1.md).* +## CLI + +The CLI is available as a dotnet global tool. +.NET Core Runtime 2.1+ *(currently in [preview](https://www.microsoft.com/net/download/dotnet-core/runtime-2.1.0-preview1))* + is required to use dotnet tools. + +To install Certes CLI *(you may need to restart the console session if this is the first dotnet tool installed)* +```Batchfile +dotnet install tool --global dotnet-certes --version 1.0.1-master-812 +``` + +Use the `--help` option to get started +```Batchfile +certes --help +``` + +or check this [AppVeyor script][AppVeyorCliSample] for renewing certificate on Azure webapps. + ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/fszlin/certes/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags](https://github.com/fszlin/certes/tags) on this repository. Also check the [changelog](CHANGELOG.md) to see what's we are working on. ## CI Status -[![NuGet](https://img.shields.io/nuget/vpre/certes.svg)](https://www.nuget.org/packages/certes/absoluteLatest/) +[![NuGet](https://img.shields.io/nuget/vpre/certes.svg?label=Certes)](https://www.nuget.org/packages/certes/absoluteLatest/) [![NuGet](https://img.shields.io/nuget/dt/certes.svg)](https://www.nuget.org/packages/certes/) +[![NuGet](https://img.shields.io/nuget/vpre/dotnet-certes.svg?label=CLI)](https://www.nuget.org/packages/dotnet-certes/absoluteLatest/) +[![NuGet](https://img.shields.io/nuget/dt/dotnet-certes.svg)](https://www.nuget.org/packages/dotnet-certes/) + + [![AppVeyor](https://img.shields.io/appveyor/ci/fszlin/certes/master.svg)](https://ci.appveyor.com/project/fszlin/certes) [![AppVeyor](https://img.shields.io/appveyor/tests/fszlin/certes/master.svg)](https://ci.appveyor.com/project/fszlin/certes/build/tests) [![codecov](https://codecov.io/gh/fszlin/certes/branch/master/graph/badge.svg)](https://codecov.io/gh/fszlin/certes) [![BCH compliance](https://bettercodehub.com/edge/badge/fszlin/certes?branch=master)](https://bettercodehub.com/results/fszlin/certes) [tw]: https://twitter.com/share?url=https%3A%2F%2Fgithub.com%2Ffszlin%2Fcertes&via=certes_acme&related=fszlin&hashtags=certes%2Cssl%2Clets-encrypt%2Cacme%2Chttps&text=get%20free%20SSL%20via%20certes +[AppVeyorCliSample]: https://github.com/fszlin/lo0.in/blob/79fc1561ca4aa29de7741ad5590e53be8db34690/.appveyor.yml#L43-L56 From 3715609437d3c1a2257fc22a30c6549f98dc475e Mon Sep 17 00:00:00 2001 From: Eddie Lin <5827855+fszlin@users.noreply.github.com> Date: Thu, 15 Mar 2018 23:40:43 -0400 Subject: [PATCH 6/7] Fix content-type header for POST requests (#75) * remove `charset` from content-type header resolves #75 --- docs/CHANGELOG.md | 4 ++++ src/Certes/Acme/AcmeHttpClient.cs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4420c939..7a186643 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] +### Changed +- Fix `Content-Type` header for POST requests (#76) + ## [2.0.0] - 2018-03-13 ### Added - [ACME v2](APIv2.md) support diff --git a/src/Certes/Acme/AcmeHttpClient.cs b/src/Certes/Acme/AcmeHttpClient.cs index eb3ca0e1..11565cb1 100644 --- a/src/Certes/Acme/AcmeHttpClient.cs +++ b/src/Certes/Acme/AcmeHttpClient.cs @@ -72,6 +72,8 @@ public async Task> Post(Uri uri, object payload) { var payloadJson = JsonConvert.SerializeObject(payload, Formatting.None, jsonSettings); var content = new StringContent(payloadJson, Encoding.UTF8, MimeJoseJson); + // boulder will reject the request if sending charset=utf-8 + content.Headers.ContentType.CharSet = null; using (var response = await Http.PostAsync(uri, content)) { return await ProcessResponse(response); From 2ec5e57ba16c3c7809b0010759473893b2d7073f Mon Sep 17 00:00:00 2001 From: Eddie Lin <5827855+fszlin@users.noreply.github.com> Date: Fri, 16 Mar 2018 17:42:51 -0400 Subject: [PATCH 7/7] updates for draft-10 (#68) * remove `key-authz` from request for challenge * update entity statuses --- docs/CHANGELOG.md | 7 ++++++- src/Certes/Acme/ChallengeContext.cs | 12 +++++------- src/Certes/Acme/Resource/AuthorizationStatus.cs | 6 ++++-- src/Certes/Acme/Resource/ChallengeStatus.cs | 6 ++++++ src/Certes/Acme/Resource/OrderStatus.cs | 6 ++++++ .../Certes.Tests/Acme/Resource/AuthorizationTests.cs | 2 +- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7a186643..8ce31699 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,8 +2,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] + +### Added +- Add `Processing` status for challenges. + ### Changed -- Fix `Content-Type` header for POST requests (#76) +- Fix `Content-Type` header for POST requests ([#76][i76]) ## [2.0.0] - 2018-03-13 ### Added @@ -50,3 +54,4 @@ All notable changes to this project will be documented in this file. [i5]: https://github.com/fszlin/certes/issues/5 [i22]: https://github.com/fszlin/certes/issues/22 +[i76]: https://github.com/fszlin/certes/issues/76 diff --git a/src/Certes/Acme/ChallengeContext.cs b/src/Certes/Acme/ChallengeContext.cs index cd53c734..2165ea0a 100644 --- a/src/Certes/Acme/ChallengeContext.cs +++ b/src/Certes/Acme/ChallengeContext.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Certes.Acme.Resource; namespace Certes.Acme { @@ -7,7 +8,7 @@ namespace Certes.Acme /// Represents the context for ACME challenge operations. /// /// - internal class ChallengeContext : EntityContext, IChallengeContext + internal class ChallengeContext : EntityContext, IChallengeContext { /// /// Initializes a new instance of the class. @@ -57,13 +58,10 @@ public ChallengeContext( /// /// The challenge. /// - public async Task Validate() + public async Task Validate() { - var payload = await Context.Sign( - new Resource.Challenge { - KeyAuthorization = KeyAuthz - }, Location); - var resp = await Context.HttpClient.Post(Location, payload, true); + var payload = await Context.Sign(new { }, Location); + var resp = await Context.HttpClient.Post(Location, payload, true); return resp.Resource; } } diff --git a/src/Certes/Acme/Resource/AuthorizationStatus.cs b/src/Certes/Acme/Resource/AuthorizationStatus.cs index 5b623597..1428e93e 100644 --- a/src/Certes/Acme/Resource/AuthorizationStatus.cs +++ b/src/Certes/Acme/Resource/AuthorizationStatus.cs @@ -1,6 +1,7 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System; using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace Certes.Acme.Resource { @@ -19,6 +20,7 @@ public enum AuthorizationStatus /// /// The processing status. /// + [Obsolete("Use ChallengeStatus.Processing instead.")] [EnumMember(Value = "processing")] Processing, diff --git a/src/Certes/Acme/Resource/ChallengeStatus.cs b/src/Certes/Acme/Resource/ChallengeStatus.cs index cb29ad41..d7b043d3 100644 --- a/src/Certes/Acme/Resource/ChallengeStatus.cs +++ b/src/Certes/Acme/Resource/ChallengeStatus.cs @@ -15,6 +15,12 @@ public enum ChallengeStatus [JsonProperty("pending")] Pending, + /// + /// The processing status. + /// + [JsonProperty("processing")] + Processing, + /// /// The valid status. /// diff --git a/src/Certes/Acme/Resource/OrderStatus.cs b/src/Certes/Acme/Resource/OrderStatus.cs index c3723ee9..36d852ae 100644 --- a/src/Certes/Acme/Resource/OrderStatus.cs +++ b/src/Certes/Acme/Resource/OrderStatus.cs @@ -19,6 +19,12 @@ public enum OrderStatus [EnumMember(Value = "pending")] Pending, + /// + /// The ready status. + /// + [EnumMember(Value = "ready")] + Ready, + /// /// The processing status. /// diff --git a/test/Certes.Tests/Acme/Resource/AuthorizationTests.cs b/test/Certes.Tests/Acme/Resource/AuthorizationTests.cs index eaee48bf..498e6332 100644 --- a/test/Certes.Tests/Acme/Resource/AuthorizationTests.cs +++ b/test/Certes.Tests/Acme/Resource/AuthorizationTests.cs @@ -10,7 +10,7 @@ public class AuthorizationTests public void CanGetSetProperties() { var authz = new Authorization(); - authz.VerifyGetterSetter(a => a.Status, AuthorizationStatus.Processing); + authz.VerifyGetterSetter(a => a.Status, AuthorizationStatus.Deactivated); authz.VerifyGetterSetter(a => a.Challenges, new[] { new Challenge() }); authz.VerifyGetterSetter(a => a.Expires, DateTimeOffset.Now); authz.VerifyGetterSetter(a => a.Identifier, new Identifier());