POST-as-GET methods implementation

This commit is contained in:
root 2019-12-26 22:13:36 +01:00
parent ea74b3f28f
commit 5658636051
11 changed files with 200 additions and 56 deletions

View File

@ -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

View File

@ -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> appSettings, ILetsEncryptService letsEncryptService, IKeyService keyService) {
public App(IOptions<AppSettings> 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());

View File

@ -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; }

View File

@ -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; }

View File

@ -13,6 +13,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.0.0" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="Newtonsoft.JSON" Version="12.0.2" />
</ItemGroup>

View File

@ -44,6 +44,7 @@ namespace LetsEncrypt
services.AddScoped<IKeyService, KeyService>();
services.AddScoped<IJwsService, JwsService>();
services.AddScoped<ILetsEncryptService, LetsEncryptService>();
services.AddScoped<ITerminalService,TerminalService>();
// add app
services.AddTransient<App>();

View File

@ -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,

View File

@ -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<Account>(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<Account>(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<Order>(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<Order>(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<string, string>();
foreach (var item in order.Authorizations)
{
var (challengeResponse, responseText) = await SendAsync<AuthorizationChallengeResponse>(HttpMethod.Get, item, null, token);
var (challengeResponse, responseText) = await SendAsync<AuthorizationChallengeResponse>(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<AuthorizationChallengeResponse>(HttpMethod.Post, challenge.Url, authorizeChallenge, token);
break;
}
@ -296,7 +313,7 @@ namespace LetsEncrypt.Services {
}
}
var (result, responseText) = await SendAsync<AuthorizationChallengeResponse>(HttpMethod.Post, challenge.Url, authorizeChallenge, token);
var (result, responseText) = await SendAsync<AuthorizationChallengeResponse>(HttpMethod.Post, challenge.Url, "{}", token);
if (result.Status == "valid")
break;
@ -304,8 +321,10 @@ namespace LetsEncrypt.Services {
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<Order>(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<Order>(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<Order>(HttpMethod.Post, _currentOrder.Finalize, new FinalizeRequest
var letsEncryptOrder = new FinalizeRequest
{
CSR = _jwsService.Base64UrlEncoded(csr.CreateSigningRequest())
}, token);
};
var (response, responseText) = await SendAsync<Order>(HttpMethod.Post, _currentOrder.Finalize, letsEncryptOrder, token);
while (response.Status != "valid")
{
(response, responseText) = await SendAsync<Order>(HttpMethod.Get, response.Location, null, token);
(response, responseText) = await SendAsync<Order>(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<string>(HttpMethod.Get, response.Certificate, null, token);
var (pem, _) = await SendAsync<string>(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)

View File

@ -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();
}
}
}

View File

@ -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"
}
]
}
]
}
}

View File

@ -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,29 +47,6 @@
"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"
}
]
}
]
}