diff --git a/.vs/ACME.net/v14/.suo b/.vs/ACME.net/v14/.suo index d41d029..7bc6b64 100644 Binary files a/.vs/ACME.net/v14/.suo and b/.vs/ACME.net/v14/.suo differ diff --git a/README.md b/README.md index 42aa741..26805ca 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,26 @@ take a look at these projects: [.net ACME protocol library](https://github.com/ebekker/letsencrypt-win/). [A simple ACME Client for Windows](https://github.com/Lone-Coder/letsencrypt-win-simple) +# Using acme.exe + +You can use acme.exe with or without IIS integration. With IIS integration, acme.exe autoamtically +configures your IIS to respond to the ACME domain validation challenge, and it updates your IIS +web site with the new SSL certificate. To use IIS integration, you must run acme.exe on your IIS web +server. + +Examples: + +Request a certificate for www.yourdomain.com and accept the terms of service of the ACME server (-a): +''' +acme.exe -a www.yourdomain.com +''' + +If you don't want to use IIS integration or can't use it / you are not using IIS, you can also +run acme.exe without IIS support. In that case, you need to manually copy the challenge file +that is required to validate domain ownership to your server. + +Request a certificate for www.yourdomain.com without IIS integration + # Projects in this repository ## Oocx.ACME diff --git a/src/Oocx.ACME.CLRConsole/Oocx.ACME.CLRConsole.csproj b/src/Oocx.ACME.CLRConsole/Oocx.ACME.CLRConsole.csproj index cf9c195..f0e4d81 100644 --- a/src/Oocx.ACME.CLRConsole/Oocx.ACME.CLRConsole.csproj +++ b/src/Oocx.ACME.CLRConsole/Oocx.ACME.CLRConsole.csproj @@ -61,7 +61,8 @@ ..\..\artifacts\bin\Oocx.ACME.Console\Debug\net46\Oocx.ACME.Console.dll True - + + False ..\..\artifacts\bin\Oocx.ACME.IIS\Debug\net46\Oocx.ACME.IIS.dll diff --git a/src/Oocx.ACME.Console/ContainerConfiguration.cs b/src/Oocx.ACME.Console/ContainerConfiguration.cs index 8f179d5..ff196a3 100644 --- a/src/Oocx.ACME.Console/ContainerConfiguration.cs +++ b/src/Oocx.ACME.Console/ContainerConfiguration.cs @@ -30,7 +30,7 @@ public IContainer Configure(Options options) builder.RegisterType().As().WithParameter("basePath", options.AccountKeyContainerLocation ?? Environment.CurrentDirectory); } - if ("manual".Equals(options.ChallengeProvider, StringComparison.OrdinalIgnoreCase)) + if ("manual-http-01".Equals(options.ChallengeProvider, StringComparison.OrdinalIgnoreCase)) { builder.RegisterType().As(); } @@ -50,7 +50,7 @@ public IContainer Configure(Options options) } else { - builder.RegisterType().As(); + builder.RegisterType().As(); } builder.RegisterType().As(); diff --git a/src/Oocx.ACME.Console/Options.cs b/src/Oocx.ACME.Console/Options.cs index 485bf17..87c3955 100644 --- a/src/Oocx.ACME.Console/Options.cs +++ b/src/Oocx.ACME.Console/Options.cs @@ -20,7 +20,7 @@ public class Options [Option('t', "termsOfServiceUri", HelpText = "The uri of the terms of service that you accept.", Default = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf")] public string TermsOfServiceUri { get; set; } - [Option('i', "ignoreSSLErrors", HelpText = "Ignore SSL certificate errors for the HTTPS connection to the ACME server (useful for debugging messages with fiddler)")] + [Option("ignoreSSLErrors", HelpText = "Ignore SSL certificate errors for the HTTPS connection to the ACME server (useful for debugging messages with fiddler)")] public bool IgnoreSSLCertificateErrors { get; set; } [Option('v', "verbosity", HelpText = "Configures the log level (Verbose, Info, Warning, Error, Disabled - casing is important).", Default = LogLevel.Info)] @@ -35,10 +35,10 @@ public class Options [Option('n', "accountKeyName", HelpText = "The name of the key file or key container used to store the acme registration key.", Default = "acme-key")] public string AccountKeyName { get; set; } - [Option('c', "challengeProvider", HelpText = "The type of web server integration to use for ACME challenges. Supported types are: 'manual' (no integration), 'iis-http-01' (IIS with http-01 challenge)", Default = "iis-http-01")] + [Option('c', "challengeProvider", HelpText = "The type of web server integration to use for ACME challenges. Supported types are: 'manual-http-01' (no integration with http-01 challenge), 'iis-http-01' (IIS with http-01 challenge)", Default = "iis-http-01")] public string ChallengeProvider { get; set; } - [Option('s', "serverConfigurationProvider", HelpText = "The type of web server configuration to use to install and configure certificates. Supported types are: 'manual' (no integration), 'iis' (installs certificates to localmachine\\my and configures IIS bindings)", Default = "iis")] + [Option('i', "serverConfigurationProvider", HelpText = "The type of web server configuration to use to install and configure certificates. Supported types are: 'manual' (no integration), 'iis' (installs certificates to localmachine\\my and configures IIS bindings)", Default = "iis")] public string ServerConfigurationProvider { get; set; } [Option('w', "iisWebSite", HelpText = "The IIS web site that should be configured to use the new certificate (used with --serverConfigurationProvider iis). If you do not specifiy a web site, the provider will try to find a matching site with a binding for your domain.")] diff --git a/src/Oocx.ACME.Console/Program.cs b/src/Oocx.ACME.Console/Program.cs index 71294d9..a73db0b 100644 --- a/src/Oocx.ACME.Console/Program.cs +++ b/src/Oocx.ACME.Console/Program.cs @@ -35,7 +35,7 @@ private void Execute(Options options) return; } - var process = container.Resolve(new Parameter[] { new NamedParameter("options", options) }); + var process = container.Resolve(new NamedParameter("options", options)); process.StartAsync().GetAwaiter().GetResult(); } catch (AggregateException ex) diff --git a/src/Oocx.ACME.IIS/Oocx.ACME.IIS.xproj b/src/Oocx.ACME.IIS/Oocx.ACME.IIS.xproj index b5270ed..381fae3 100644 --- a/src/Oocx.ACME.IIS/Oocx.ACME.IIS.xproj +++ b/src/Oocx.ACME.IIS/Oocx.ACME.IIS.xproj @@ -4,7 +4,6 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - cc1c7776-20ec-41a1-ad04-e7e19319b1a9 @@ -12,9 +11,11 @@ ..\..\artifacts\obj\$(MSBuildProjectName) ..\..\artifacts\bin\$(MSBuildProjectName)\ - 2.0 + + True + - + \ No newline at end of file diff --git a/src/Oocx.ACME/Client/AcmeClient.cs b/src/Oocx.ACME/Client/AcmeClient.cs index 0233e03..76fb7c9 100644 --- a/src/Oocx.ACME/Client/AcmeClient.cs +++ b/src/Oocx.ACME/Client/AcmeClient.cs @@ -76,7 +76,12 @@ public async Task RegisterAsync(string termsOfServiceUri, { var location = ex.Response.Headers.Location.ToString(); Info($"using existing registration: {location}"); - return await PostAsync(new Uri(location), new UpdateRegistrationRequest()); + var response = await PostAsync(new Uri(location), new UpdateRegistrationRequest()); + if (string.IsNullOrEmpty(response.Location)) + { + response.Location = location; + } + return response; } } @@ -151,8 +156,7 @@ public async Task NewCertificateRequestAsync(byte[] csr) var request = new CertificateRequest {Csr = csr.Base64UrlEncoded()}; var response = await PostAsync(directory.NewCertificate, request); - Verbose($"location: {response.Location}"); - Verbose($"link: {response.Link}"); + Verbose($"location: {response.Location}"); return response; } @@ -219,14 +223,30 @@ private async Task SendAsync(HttpMethod method, Uri uri, objec private static void GetHeaderValues(HttpResponseMessage response, TResult responseContent) { var properties = - typeof (TResult).GetProperties(BindingFlags.Public | BindingFlags.SetProperty) + typeof (TResult).GetProperties(BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance) .Where(p => p.PropertyType == typeof (string)) .ToDictionary(p => p.Name, p => p); foreach (var header in response.Headers) { - if (properties.ContainsKey(header.Key)) + if (properties.ContainsKey(header.Key) && header.Value.Count() == 1) + { + properties[header.Key].SetValue(responseContent, header.Value.First()); + } + + if (header.Key == "Link") { - properties[header.Key].SetValue(responseContent, header.Value); + foreach (var link in header.Value) + { + var parts = link.Split(';'); + if (parts.Length != 2) + { + continue; + } + if (parts[1] == "rel=\"terms-of-service\"" && properties.ContainsKey("Agreement")) + { + properties["Agreement"].SetValue(responseContent, parts[0].Substring(1, parts[0].Length - 2)); + } + } } } } diff --git a/src/Oocx.ACME/Client/ManualChallengeProvider.cs b/src/Oocx.ACME/Client/ManualChallengeProvider.cs index 29d7089..35a1782 100644 --- a/src/Oocx.ACME/Client/ManualChallengeProvider.cs +++ b/src/Oocx.ACME/Client/ManualChallengeProvider.cs @@ -40,9 +40,9 @@ public async Task AcceptChallengeAsync(string domain, string s return new PendingChallenge() { - Instructions = $"Copy {challengeFile} to https://{domain ?? siteName}/.well-known/acme/{challenge.Token}", + Instructions = $"Copy {challengeFile} to https://{domain ?? siteName}/.well-known/acme-challenge/{challenge.Token}", Complete = () => client.CompleteChallengeAsync(challenge) }; } - } + } } \ No newline at end of file diff --git a/src/Oocx.ACME/Client/ManualServerConfigurationPRovider.cs b/src/Oocx.ACME/Client/ManualServerConfigurationPRovider.cs new file mode 100644 index 0000000..43b3c59 --- /dev/null +++ b/src/Oocx.ACME/Client/ManualServerConfigurationPRovider.cs @@ -0,0 +1,19 @@ +using System; +using System.Security.Cryptography; +using Oocx.ACME.Common; +using Oocx.ACME.Services; + +namespace Oocx.ACME.Client +{ + public class ManualServerConfigurationProvider : IServerConfigurationProvider + { + public byte[] InstallCertificateWithPrivateKey(string certificatePath, string certificateStoreName, RSAParameters privateKey) + { + return null; + } + + public void ConfigureServer(string domain, byte[] certificateHash, string certificateStoreName, string siteName, string binding) + { + } + } +} \ No newline at end of file diff --git a/src/Oocx.ACME/Protocol/CertificateResponse.cs b/src/Oocx.ACME/Protocol/CertificateResponse.cs index f1f3470..c237f74 100644 --- a/src/Oocx.ACME/Protocol/CertificateResponse.cs +++ b/src/Oocx.ACME/Protocol/CertificateResponse.cs @@ -2,9 +2,7 @@ namespace Oocx.ACME.Protocol { public class CertificateResponse { - public string Location { get; set; } - - public string Link { get; set; } + public string Location { get; set; } public byte[] Certificate { get; set; } }