code review, settings implementation

This commit is contained in:
root 2019-07-18 17:50:08 +02:00
parent 9047a482ba
commit febf5f35b7
8 changed files with 299 additions and 117 deletions

View File

@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace ACMEv2 namespace ACMEv2
{ {
public class Directory public class AcmeDirectory
{ {
//New nonce //New nonce
[JsonProperty("newNonce")] [JsonProperty("newNonce")]
@ -32,6 +32,12 @@ namespace ACMEv2
//Metadata object //Metadata object
[JsonProperty("meta")] [JsonProperty("meta")]
public DirectoryMeta Meta { get; set; } public AcmeDirectoryMeta Meta { get; set; }
}
public class AcmeDirectoryMeta
{
[JsonProperty("termsOfService")]
public string TermsOfService { get; set; }
} }
} }

View File

@ -1,12 +0,0 @@
using Newtonsoft.Json;
namespace ACMEv2
{
public class DirectoryMeta
{
[JsonProperty("termsOfService")]
public string TermsOfService { get; set; }
}
}

View File

@ -76,7 +76,7 @@ namespace ACMEv2
private RSACryptoServiceProvider _accountKey; private RSACryptoServiceProvider _accountKey;
private RegistrationCache _cache; private RegistrationCache _cache;
private HttpClient _client; private HttpClient _client;
private Directory _directory; private AcmeDirectory _directory;
private List<AuthorizationChallenge> _challenges = new List<AuthorizationChallenge>(); private List<AuthorizationChallenge> _challenges = new List<AuthorizationChallenge>();
private Order _currentOrder; private Order _currentOrder;
@ -84,6 +84,7 @@ namespace ACMEv2
/// Let's encrypt client object /// Let's encrypt client object
/// </summary> /// </summary>
/// <param name="url"></param> /// <param name="url"></param>
/// <param name="home"></param>
public LetsEncryptClient(string url, string home) public LetsEncryptClient(string url, string home)
{ {
_url = url ?? throw new ArgumentNullException(nameof(url)); _url = url ?? throw new ArgumentNullException(nameof(url));
@ -108,7 +109,7 @@ namespace ACMEv2
_client = GetCachedClient(_url); _client = GetCachedClient(_url);
// 1 - Get directory // 1 - Get directory
(_directory, _) = await SendAsync<Directory>(HttpMethod.Get, new Uri("directory", UriKind.Relative), null, token); (_directory, _) = await SendAsync<AcmeDirectory>(HttpMethod.Get, new Uri("directory", UriKind.Relative), null, token);
if (File.Exists(_path)) if (File.Exists(_path))
@ -502,7 +503,7 @@ namespace ACMEv2
/// <param name="hosts"></param> /// <param name="hosts"></param>
/// <param name="value"></param> /// <param name="value"></param>
/// <returns></returns> /// <returns></returns>
public bool TryGetCachedCertificate(List<string> hosts, out CachedCertificateResult value) public bool TryGetCachedCertificate(string [] hosts, out CachedCertificateResult value)
{ {
value = null; value = null;
if (_cache.CachedCerts.TryGetValue(hosts[0], out var cache) == false) if (_cache.CachedCerts.TryGetValue(hosts[0], out var cache) == false)

View File

@ -9,4 +9,10 @@
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@ -172,6 +173,33 @@ namespace LetsEncrypt
} }
} }
} }
public static string RestoreCon(string cmd)
{
var escapedArgs = cmd.Replace("\"", "\\\"");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "restorecon",
Arguments = $"-v \"{escapedArgs}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
}
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return result;
}
} }

View File

@ -1,88 +1,132 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
using ACMEv2; using ACMEv2;
using FS = System.IO;
namespace LetsEncrypt namespace LetsEncrypt
{ {
class Program class Program
{ {
private static readonly string AppPath = AppDomain.CurrentDomain.BaseDirectory;
static void Main(string[] args) static void Main(string[] args)
{ {
// save to http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN> try
var tokensPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ".well-known/acme-challenge"); {
if (!FS.Directory.Exists(tokensPath))
FS.Directory.CreateDirectory(tokensPath);
foreach (FileInfo file in new DirectoryInfo(tokensPath).GetFiles())
file.Delete();
var certsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "certs");
if (!FS.Directory.Exists(certsPath))
FS.Directory.CreateDirectory(certsPath);
List<string> contacts = new List<string>();
contacts.Add("maksym.sadovnychyy@gmail.com");
List<string> hosts = new List<string>();
hosts.Add("maks-it.com");
hosts.Add("www.maks-it.com");
Console.WriteLine("Let's Encrypt C# .Net Core Client"); Console.WriteLine("Let's Encrypt C# .Net Core Client");
try Settings settings = (new SettingsProvider(null)).settings;
{
LetsEncryptClient client = new LetsEncryptClient(LetsEncryptClient.ProductionV2, AppDomain.CurrentDomain.BaseDirectory); //loop all customers
foreach(Customer customer in settings.customers) {
try {
Console.WriteLine(string.Format("Managing customer: {0} - {1} {2}", customer.id, customer.name, customer.lastname));
//loop each customer website
foreach(Site site in customer.sites) {
Console.WriteLine(string.Format("Managing site: {0}", site.name));
try {
//define cache folder
string cache = Path.Combine(AppPath, "cache", customer.id, site.name);
if(!Directory.Exists(cache)) {
Directory.CreateDirectory(cache);
}
LetsEncryptClient client = new LetsEncryptClient(settings.url, cache);
//1. Client initialization
Console.WriteLine("1. Client Initialization..."); Console.WriteLine("1. Client Initialization...");
client.Init(customer.contacts).Wait();
Console.WriteLine(string.Format("Terms of service: {0}", client.GetTermsOfServiceUri()));
// 1 //create folder for ssl
client.Init(contacts.ToArray()).Wait(); string ssl = Path.Combine(settings.ssl, site.name);
Console.WriteLine(string.Format("Terms of service: {0}",client.GetTermsOfServiceUri())); if(!Directory.Exists(ssl)) {
Directory.CreateDirectory(ssl);
}
// get cached certificate and check if it's valid
// get cached certificate and chech if it's valid // if valid check if cert and key exists otherwise recreate
// else continue with new certificate request
CachedCertificateResult certRes = new CachedCertificateResult(); CachedCertificateResult certRes = new CachedCertificateResult();
if (client.TryGetCachedCertificate(hosts, out certRes)) if (client.TryGetCachedCertificate(site.hosts, out certRes))
{ {
File.WriteAllText(Path.Combine(certsPath, "maks-it.com.crt"), certRes.Certificate); string cert = Path.Combine(ssl, site.name + ".crt");
if(!File.Exists(cert))
File.WriteAllText(cert, certRes.Certificate);
using (StreamWriter writer = File.CreateText(Path.Combine(certsPath, "maks-it.com.key"))) string key = Path.Combine(ssl, site.name + ".key");
if(!File.Exists(key)) {
using (StreamWriter writer = File.CreateText(key))
Library.ExportPrivateKey(certRes.PrivateKey, writer); Library.ExportPrivateKey(certRes.PrivateKey, writer);
} }
else {
Console.WriteLine("Certificate and Key exists and valid.");
}
else {
if(!Directory.Exists(Path.Combine(settings.www, site.name))) {
throw new DirectoryNotFoundException(string.Format("Site {0} wasn't initialized", site.name));
}
//new nonce
client.NewNonce().Wait(); client.NewNonce().Wait();
// 2 //try to make new order
try try
{ {
//create new orders
Console.WriteLine("2. Client New Order..."); Console.WriteLine("2. Client New Order...");
Task<Dictionary<string, string>> orders = client.NewOrder(hosts.ToArray(), "http-01"); Task<Dictionary<string, string>> orders = client.NewOrder(site.hosts, site.challenge);
orders.Wait(); orders.Wait();
switch(site.challenge) {
case "http-01": {
//ensure to enable static file discovery on server in .well-known/acme-challenge
//and listen on 80 port
//create acme directory for web site
string acme = Path.Combine(settings.www, site.name, settings.acme);
if(!Directory.Exists(acme)) {
Directory.CreateDirectory(acme);
}
foreach (FileInfo file in new DirectoryInfo(acme).GetFiles())
file.Delete();
foreach (var result in orders.Result) foreach (var result in orders.Result)
{ {
Console.WriteLine("Key: " + result.Key + Environment.NewLine + "Value: " + result.Value); Console.WriteLine("Key: " + result.Key + Environment.NewLine + "Value: " + result.Value);
string[] splitToken = result.Value.Split('~'); string[] splitToken = result.Value.Split('~');
File.WriteAllText(FS.Path.Combine(tokensPath, splitToken[0]), splitToken[1]);
string token = Path.Combine(acme, splitToken[0]);
File.WriteAllText(token, splitToken[1]);
//for Selinux on centos7
Console.WriteLine(Library.RestoreCon(token));
} }
// 3
break;
}
case "dns-01": {
//Manage DNS server MX record, depends from provider
break;
}
default: {
break;
}
}
//complete challanges
Console.WriteLine("3. Client Complete Challange..."); Console.WriteLine("3. Client Complete Challange...");
client.CompleteChallenges().Wait(); client.CompleteChallenges().Wait();
Console.WriteLine("Challanges comleted."); Console.WriteLine("Challanges comleted.");
@ -90,43 +134,46 @@ namespace LetsEncrypt
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message.ToString()); Console.WriteLine(ex.Message.ToString());
client.GetOrder(hosts.ToArray()).Wait(); client.GetOrder(site.hosts).Wait();
} }
// 4 Download certificate // Download new certificate
Console.WriteLine("4. Download certificate..."); Console.WriteLine("4. Download certificate...");
client.GetCertificate().Wait(); client.GetCertificate().Wait();
// Write to filesystem
certRes = new CachedCertificateResult();
if (client.TryGetCachedCertificate(site.hosts, out certRes)) {
string cert = Path.Combine(ssl, site.name + ".crt");
File.WriteAllText(cert, certRes.Certificate);
// 5 Write to filesystem string key = Path.Combine(ssl, site.name + ".key");
//CachedCertificateResult certRes = new CachedCertificateResult(); using (StreamWriter writer = File.CreateText(key))
//if (client.TryGetCachedCertificate(hosts, out certRes)) { Library.ExportPrivateKey(certRes.PrivateKey, writer);
// File.WriteAllText(Path.Combine(certsPath, "maks-it.com.crt"), certRes.Certificate);
// using (StreamWriter writer = File.CreateText(Path.Combine(certsPath, "maks-it.com.key")))
// Library.ExportPrivateKey(certRes.PrivateKey, writer);
//}
Console.WriteLine("Certificate saved."); Console.WriteLine("Certificate saved.");
} }
else {
Console.WriteLine("Unable to get new cached certificate.");
}
}
}
catch (Exception ex) {
Console.WriteLine(ex.Message.ToString());
}
}
}
catch (Exception ex) {
Console.WriteLine(ex.Message.ToString());
}
}
} }
catch (Exception ex) { catch (Exception ex) {
Console.WriteLine(ex.Message.ToString()); Console.WriteLine(ex.Message.ToString());
} }
Console.Read();
} }
} }
} }

View File

@ -0,0 +1,48 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace LetsEncrypt
{
public class SettingsProvider
{
private readonly string _path;
public Settings settings;
public SettingsProvider(string path) {
_path = path ?? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json");
if(!File.Exists(_path))
throw new FileNotFoundException(string.Format("Settings file \"{0}\" not found."), _path);
settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(_path));
}
}
public class Settings {
public string url { get; set; }
public string www { get; set; }
public string acme { get; set; }
public string ssl { get; set; }
public Customer [] customers { get; set;}
}
public class Customer {
public string id { get; set; }
public string [] contacts { get; set; }
public string name { get; set; }
public string lastname { get; set; }
public Site [] sites { get; set; }
}
public class Site {
public string name { get; set; }
public string [] hosts { get; set; }
public string challenge { get; set; }
}
}

58
LetsEncrypt/settings.json Normal file
View File

@ -0,0 +1,58 @@
{
"_StagingV2": "https://acme-staging-v02.api.letsencrypt.org/directory",
"_ProductionV2": "https://acme-v02.api.letsencrypt.org/directory",
"url": "https://acme-staging-v02.api.letsencrypt.org/directory",
"cache": "/home/maksym/Desktop/LetsEncrypt_Cache/cache",
"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"
],
"challenge": "http-01"
},
{
"name": "maks-it.com",
"hosts": [
"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"
],
"challenge": "http-01"
}
]
}
]
}