From 5658636051ee808f8e171340778a1017eebe3fbe Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 Dec 2019 22:13:36 +0100 Subject: [PATCH] POST-as-GET methods implementation --- v2.0/LetsEncrypt/.vscode/launch.json | 2 +- v2.0/LetsEncrypt/App.cs | 27 +++++-- .../Entities/LetsEncrypt/JwsMessage.cs | 4 +- v2.0/LetsEncrypt/Helpers/AppSettings.cs | 1 + v2.0/LetsEncrypt/LetsEncrypt.csproj | 1 + v2.0/LetsEncrypt/Program.cs | 1 + v2.0/LetsEncrypt/Services/JwsService.cs | 23 +++++- .../Services/LetsEncryptService.cs | 59 ++++++++++----- v2.0/LetsEncrypt/Services/TerminalService.cs | 31 ++++++++ v2.0/LetsEncrypt/appsettings copy.json | 74 +++++++++++++++++++ v2.0/LetsEncrypt/appsettings.json | 33 ++------- 11 files changed, 200 insertions(+), 56 deletions(-) create mode 100644 v2.0/LetsEncrypt/Services/TerminalService.cs create mode 100644 v2.0/LetsEncrypt/appsettings copy.json diff --git a/v2.0/LetsEncrypt/.vscode/launch.json b/v2.0/LetsEncrypt/.vscode/launch.json index 125ba92..4089725 100644 --- a/v2.0/LetsEncrypt/.vscode/launch.json +++ b/v2.0/LetsEncrypt/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/bin/Debug/netcoreapp2.2/LetsEncrypt.dll", + "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/LetsEncrypt.dll", "args": [], "cwd": "${workspaceFolder}", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console diff --git a/v2.0/LetsEncrypt/App.cs b/v2.0/LetsEncrypt/App.cs index ec50c9c..d868a7e 100644 --- a/v2.0/LetsEncrypt/App.cs +++ b/v2.0/LetsEncrypt/App.cs @@ -7,6 +7,8 @@ using System.Linq; using Microsoft.Extensions.Options; +using Mono.Unix; + using LetsEncrypt.Services; using LetsEncrypt.Helpers; using LetsEncrypt.Entities; @@ -20,11 +22,13 @@ namespace LetsEncrypt private readonly AppSettings _appSettings; private readonly ILetsEncryptService _letsEncryptService; private readonly IKeyService _keyService; + private readonly ITerminalService _terminalService; - public App(IOptions appSettings, ILetsEncryptService letsEncryptService, IKeyService keyService) { + public App(IOptions appSettings, ILetsEncryptService letsEncryptService, IKeyService keyService, ITerminalService terminalService) { _appSettings = appSettings.Value; _letsEncryptService = letsEncryptService; _keyService = keyService; + _terminalService = terminalService; } public void Run() { @@ -46,7 +50,7 @@ namespace LetsEncrypt try { //define cache folder - string cache = Path.Combine(AppPath, "cache", customer.id); + string cache = Path.Combine(AppPath, env.cache, customer.id); if(!Directory.Exists(cache)) { Directory.CreateDirectory(cache); } @@ -70,16 +74,16 @@ namespace LetsEncrypt CachedCertificateResult certRes = new CachedCertificateResult(); if (_letsEncryptService.TryGetCachedCertificate(site.name, out certRes)) { string cert = Path.Combine(ssl, site.name + ".crt"); - if(!File.Exists(cert)) + //if(!File.Exists(cert)) File.WriteAllText(cert, certRes.Certificate); string key = Path.Combine(ssl, site.name + ".key"); - if(!File.Exists(key)) { + //if(!File.Exists(key)) { using (StreamWriter writer = File.CreateText(key)) _keyService.ExportPrivateKey(certRes.PrivateKey, writer); - } + //} - Console.WriteLine("Certificate and Key exists and valid."); + Console.WriteLine("Certificate and Key exists and valid. Restored from cache."); } else { //new nonce @@ -103,8 +107,8 @@ namespace LetsEncrypt throw new DirectoryNotFoundException(string.Format("Directory {0} wasn't created", acme)); } - foreach (FileInfo file in new DirectoryInfo(acme).GetFiles()) - file.Delete(); + //foreach (FileInfo file in new DirectoryInfo(acme).GetFiles()) + //file.Delete(); foreach (var result in orders.Result) { @@ -115,6 +119,9 @@ namespace LetsEncrypt File.WriteAllText(token, splitToken[1]); } + _terminalService.Exec("chgrp -R nginx /var/www"); + _terminalService.Exec("chmod -R g+rwx /var/www"); + break; } @@ -175,6 +182,10 @@ namespace LetsEncrypt Console.WriteLine(ex.Message.ToString()); } } + + + _terminalService.Exec("systemctl restart nginx"); + } catch (Exception ex) { Console.WriteLine(ex.Message.ToString()); diff --git a/v2.0/LetsEncrypt/Entities/LetsEncrypt/JwsMessage.cs b/v2.0/LetsEncrypt/Entities/LetsEncrypt/JwsMessage.cs index fd88c89..668eae6 100644 --- a/v2.0/LetsEncrypt/Entities/LetsEncrypt/JwsMessage.cs +++ b/v2.0/LetsEncrypt/Entities/LetsEncrypt/JwsMessage.cs @@ -7,8 +7,8 @@ namespace LetsEncrypt.Entities public class JwsMessage { - [JsonProperty("header")] - public JwsHeader Header { get; set; } + //[JsonProperty("header")] + //public JwsHeader Header { get; set; } [JsonProperty("protected")] public string Protected { get; set; } diff --git a/v2.0/LetsEncrypt/Helpers/AppSettings.cs b/v2.0/LetsEncrypt/Helpers/AppSettings.cs index 9fa7760..cacb951 100644 --- a/v2.0/LetsEncrypt/Helpers/AppSettings.cs +++ b/v2.0/LetsEncrypt/Helpers/AppSettings.cs @@ -9,6 +9,7 @@ namespace LetsEncrypt.Helpers public class Environment { public string name { get; set; } public string url { get; set; } + public string cache { get; set; } public string www { get; set; } public string acme { get; set; } public string ssl { get; set; } diff --git a/v2.0/LetsEncrypt/LetsEncrypt.csproj b/v2.0/LetsEncrypt/LetsEncrypt.csproj index b73bc2e..1e04307 100644 --- a/v2.0/LetsEncrypt/LetsEncrypt.csproj +++ b/v2.0/LetsEncrypt/LetsEncrypt.csproj @@ -13,6 +13,7 @@ + diff --git a/v2.0/LetsEncrypt/Program.cs b/v2.0/LetsEncrypt/Program.cs index 0f295de..ebb0616 100644 --- a/v2.0/LetsEncrypt/Program.cs +++ b/v2.0/LetsEncrypt/Program.cs @@ -44,6 +44,7 @@ namespace LetsEncrypt services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // add app services.AddTransient(); diff --git a/v2.0/LetsEncrypt/Services/JwsService.cs b/v2.0/LetsEncrypt/Services/JwsService.cs index 32ea3c8..859ab00 100644 --- a/v2.0/LetsEncrypt/Services/JwsService.cs +++ b/v2.0/LetsEncrypt/Services/JwsService.cs @@ -57,10 +57,31 @@ namespace LetsEncrypt.Services var message = new JwsMessage { - Payload = Base64UrlEncoded(JsonConvert.SerializeObject(payload)), + Payload = "", Protected = Base64UrlEncoded(JsonConvert.SerializeObject(protectedHeader)) }; + if(payload != null) { + if(payload is String) { + string value = payload.ToString(); + switch(value) { + case "POST-as-GET": + message.Payload = string.Empty; + break; + + default: + message.Payload = Base64UrlEncoded(value); + break; + } + + + } else { + message.Payload = Base64UrlEncoded(JsonConvert.SerializeObject(payload)); + } + + } + + message.Signature = Base64UrlEncoded( _rsa.SignData(Encoding.ASCII.GetBytes(message.Protected + "." + message.Payload), HashAlgorithmName.SHA256, diff --git a/v2.0/LetsEncrypt/Services/LetsEncryptService.cs b/v2.0/LetsEncrypt/Services/LetsEncryptService.cs index 1fa1c98..43965bd 100644 --- a/v2.0/LetsEncrypt/Services/LetsEncryptService.cs +++ b/v2.0/LetsEncrypt/Services/LetsEncryptService.cs @@ -1,3 +1,10 @@ +/** +* tools.itef.org/html/draft-itef-acme-acme-18 +* https://community.letsencrypt.org/t/trying-to-do-post-as-get-but-getting-post-jws-not-signed/108371 +* https://tools.ietf.org/html/rfc8555#section-6.2 +* +*/ + using System; using System.Threading; @@ -125,15 +132,17 @@ namespace LetsEncrypt.Services { //New Account request _jwsService.Init(_accountKey, null); - var (account, response) = await SendAsync(HttpMethod.Post, _directory.NewAccount, new Account + + var letsEncryptOrder = new Account { // we validate this in the UI before we get here, so that is fine TermsOfServiceAgreed = true, Contacts = contacts.Select(contact => string.Format("mailto:{0}", contact) ).ToArray() + }; - }, token); + var (account, response) = await SendAsync(HttpMethod.Post, _directory.NewAccount, letsEncryptOrder, token); _jwsService.SetKeyId(account); if (account.Status != "valid") @@ -195,7 +204,7 @@ namespace LetsEncrypt.Services { //update jws with account url _jwsService.Init(_accountKey, _cache.Location.ToString()); - var (order, response) = await SendAsync(HttpMethod.Post, _directory.NewOrder, new Order + var letsEncryptOrder = new Order { Expires = DateTime.UtcNow.AddDays(2), Identifiers = hostnames.Select(hostname => new OrderIdentifier @@ -203,7 +212,9 @@ namespace LetsEncrypt.Services { Type = "dns", Value = hostname }).ToArray() - }, token); + }; + + var (order, response) = await SendAsync(HttpMethod.Post, _directory.NewOrder, letsEncryptOrder, token); if (order.Status != "pending") throw new InvalidOperationException("Created new order and expected status 'pending', but got: " + order.Status + Environment.NewLine + @@ -213,7 +224,8 @@ namespace LetsEncrypt.Services { var results = new Dictionary(); foreach (var item in order.Authorizations) { - var (challengeResponse, responseText) = await SendAsync(HttpMethod.Get, item, null, token); + var (challengeResponse, responseText) = await SendAsync(HttpMethod.Post, item, "POST-as-GET", token); + if (challengeResponse.Status == "valid") continue; @@ -277,17 +289,22 @@ namespace LetsEncrypt.Services { { _jwsService.Init(_accountKey, _cache.Location.ToString()); - for (var index = 0; index < _challenges.Count; index++) - { - var challenge = _challenges[index]; - while (true) + + + for (var index = 0; index < _challenges.Count; index++) { + + var challenge = _challenges[index]; + + while (true) + { AuthorizeChallenge authorizeChallenge = new AuthorizeChallenge(); switch (challenge.Type) { case "dns-01": { authorizeChallenge.KeyAuthorization = _jwsService.GetKeyAuthorization(challenge.Token); + //var (result, responseText) = await SendAsync(HttpMethod.Post, challenge.Url, authorizeChallenge, token); break; } @@ -296,16 +313,18 @@ namespace LetsEncrypt.Services { } } - var (result, responseText) = await SendAsync(HttpMethod.Post, challenge.Url, authorizeChallenge, token); + var (result, responseText) = await SendAsync(HttpMethod.Post, challenge.Url, "{}", token); if (result.Status == "valid") break; if (result.Status != "pending") throw new InvalidOperationException("Failed autorization of " + _currentOrder.Identifiers[index].Value + Environment.NewLine + responseText); + - await Task.Delay(500); + await Task.Delay(1000); } + } } @@ -317,7 +336,7 @@ namespace LetsEncrypt.Services { //update jws _jwsService.Init(_accountKey, _cache.Location.ToString()); - var (order, response) = await SendAsync(HttpMethod.Post, _directory.NewOrder, new Order + var letsEncryptOrder = new Order { Expires = DateTime.UtcNow.AddDays(2), Identifiers = hostnames.Select(hostname => new OrderIdentifier @@ -325,7 +344,9 @@ namespace LetsEncrypt.Services { Type = "dns", Value = hostname }).ToArray() - }, token); + }; + + var (order, response) = await SendAsync(HttpMethod.Post, _directory.NewOrder, letsEncryptOrder, token); _currentOrder = order; } @@ -351,14 +372,16 @@ namespace LetsEncrypt.Services { csr.CertificateExtensions.Add(san.Build()); - var (response, responseText) = await SendAsync(HttpMethod.Post, _currentOrder.Finalize, new FinalizeRequest + var letsEncryptOrder = new FinalizeRequest { CSR = _jwsService.Base64UrlEncoded(csr.CreateSigningRequest()) - }, token); + }; + + var (response, responseText) = await SendAsync(HttpMethod.Post, _currentOrder.Finalize, letsEncryptOrder, token); while (response.Status != "valid") { - (response, responseText) = await SendAsync(HttpMethod.Get, response.Location, null, token); + (response, responseText) = await SendAsync(HttpMethod.Post, response.Location, "POST-as-GET", token); if(response.Status == "processing") { @@ -368,7 +391,7 @@ namespace LetsEncrypt.Services { throw new InvalidOperationException("Invalid order status: " + response.Status + Environment.NewLine + responseText); } - var (pem, _) = await SendAsync(HttpMethod.Get, response.Certificate, null, token); + var (pem, _) = await SendAsync(HttpMethod.Post, response.Certificate, "POST-as-GET", token); var cert = new X509Certificate2(Encoding.UTF8.GetBytes(pem)); @@ -450,6 +473,8 @@ namespace LetsEncrypt.Services { request.Content.Headers.Add("Content-Type", requestType); } + + var response = await _client.SendAsync(request, token).ConfigureAwait(false); if (method == HttpMethod.Post) diff --git a/v2.0/LetsEncrypt/Services/TerminalService.cs b/v2.0/LetsEncrypt/Services/TerminalService.cs new file mode 100644 index 0000000..20f8934 --- /dev/null +++ b/v2.0/LetsEncrypt/Services/TerminalService.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics; + +namespace LetsEncrypt { + + public interface ITerminalService { + void Exec(string cmd); + } + + public class TerminalService : ITerminalService { + + public void Exec(string cmd) { + var escapedArgs = cmd.Replace("\"", "\\\""); + + var pc = new Process { + StartInfo = new ProcessStartInfo { + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + FileName = "/bin/bash", + Arguments = $"-c \"{escapedArgs}\"" + } + }; + + pc.Start(); + pc.WaitForExit(); + } + } + +} \ No newline at end of file diff --git a/v2.0/LetsEncrypt/appsettings copy.json b/v2.0/LetsEncrypt/appsettings copy.json new file mode 100644 index 0000000..8d4fa58 --- /dev/null +++ b/v2.0/LetsEncrypt/appsettings copy.json @@ -0,0 +1,74 @@ +{ + "AppSettings": { + + "active": "StagingV2", + + "environments": [ + { + "name": "StagingV2", + "url": "https://acme-staging-v02.api.letsencrypt.org/directory", + + "www": "/var/www", + "acme": ".well-known/acme-challenge", + "ssl": "/home/maksym/source/temp" + }, + { + "name": "ProductionV2", + "url": "https://acme-v02.api.letsencrypt.org/directory", + + "www": "/var/www", + "acme": ".well-known/acme-challenge", + "ssl": "/etc/nginx/ssl" + } + ], + + "customers": [ + { + "id": "9b4c8584-dc83-4388-b45f-2942e34dca9d", + "contacts": [ "maksym.sadovnychyy@gmail.com" ], + "name": "Maksym", + "lastname": "Sadovnychyy", + + "sites": [ + { + "name": "maks-it.com", + "hosts": [ + "maks-it.com", + "www.maks-it.com", + "it.maks-it.com", + "www.it.maks-it.com", + "ru.maks-it.com", + "www.ru.maks-it.com", + "api.maks-it.com", + "www.api.maks-it.com" + ], + "challenge": "http-01" + } + ] + }, + { + "id": "d6be989c-3b68-480d-9f4f-b7317674847a", + "contacts": [ "anastasiia.pavlovskaia@gmail.com" ], + + "name": "Anastasiia", + "lastname": "Pavlovskaia", + + "sites": [ + { + + "name": "nastyarey.com", + "hosts": [ + "nastyarey.com", + "www.nastyarey.com", + "it.nastyarey.com", + "www.it.nastyarey.com", + "ru.nastyarey.com", + "www.ru.nastyarey.com" + ], + "challenge": "http-01" + } + ] + } + ] + } +} diff --git a/v2.0/LetsEncrypt/appsettings.json b/v2.0/LetsEncrypt/appsettings.json index ccf5556..db6c35b 100644 --- a/v2.0/LetsEncrypt/appsettings.json +++ b/v2.0/LetsEncrypt/appsettings.json @@ -8,24 +8,26 @@ "name": "StagingV2", "url": "https://acme-staging-v02.api.letsencrypt.org/directory", + "cache": "staging_cache", "www": "/var/www", "acme": ".well-known/acme-challenge", - "ssl": "/etc/nginx/ssl" + "ssl": "/home/maksym/source/temp" }, { "name": "ProductionV2", "url": "https://acme-v02.api.letsencrypt.org/directory", + "cache": "production_cache", "www": "/var/www", "acme": ".well-known/acme-challenge", - "ssl": "/etc/nginx/ssl" + "ssl": "/etc/nginx/ssl/" } ], "customers": [ { "id": "9b4c8584-dc83-4388-b45f-2942e34dca9d", - "contacts": [ "maksym.sadovnychyy@gmail.com" ], + "contacts": [ "maksym.sadovnychyy@google.com" ], "name": "Maksym", "lastname": "Sadovnychyy", @@ -45,30 +47,7 @@ "challenge": "http-01" } ] - }, - { - "id": "d6be989c-3b68-480d-9f4f-b7317674847a", - "contacts": [ "anastasiia.pavlovskaia@gmail.com" ], - - "name": "Anastasiia", - "lastname": "Pavlovskaia", - - "sites": [ - { - - "name": "nastyarey.com", - "hosts": [ - "nastyarey.com", - "www.nastyarey.com", - "it.nastyarey.com", - "www.it.nastyarey.com", - "ru.nastyarey.com", - "www.ru.nastyarey.com" - ], - "challenge": "http-01" - } - ] } ] } -} \ No newline at end of file +}