mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
code review, settings implementation
This commit is contained in:
parent
9047a482ba
commit
febf5f35b7
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace ACMEv2
|
|
||||||
{
|
|
||||||
public class DirectoryMeta
|
|
||||||
{
|
|
||||||
[JsonProperty("termsOfService")]
|
|
||||||
public string TermsOfService { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,132 +1,179 @@
|
|||||||
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>
|
|
||||||
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");
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LetsEncryptClient client = new LetsEncryptClient(LetsEncryptClient.ProductionV2, AppDomain.CurrentDomain.BaseDirectory);
|
Console.WriteLine("Let's Encrypt C# .Net Core Client");
|
||||||
Console.WriteLine("1. Client Initialization...");
|
|
||||||
|
|
||||||
// 1
|
Settings settings = (new SettingsProvider(null)).settings;
|
||||||
client.Init(contacts.ToArray()).Wait();
|
|
||||||
Console.WriteLine(string.Format("Terms of service: {0}",client.GetTermsOfServiceUri()));
|
//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...");
|
||||||
|
client.Init(customer.contacts).Wait();
|
||||||
|
Console.WriteLine(string.Format("Terms of service: {0}", client.GetTermsOfServiceUri()));
|
||||||
|
|
||||||
|
//create folder for ssl
|
||||||
|
string ssl = Path.Combine(settings.ssl, site.name);
|
||||||
|
if(!Directory.Exists(ssl)) {
|
||||||
|
Directory.CreateDirectory(ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get cached certificate and check if it's valid
|
||||||
|
// if valid check if cert and key exists otherwise recreate
|
||||||
|
// else continue with new certificate request
|
||||||
|
CachedCertificateResult certRes = new CachedCertificateResult();
|
||||||
|
if (client.TryGetCachedCertificate(site.hosts, out certRes))
|
||||||
|
{
|
||||||
|
string cert = Path.Combine(ssl, site.name + ".crt");
|
||||||
|
if(!File.Exists(cert))
|
||||||
|
File.WriteAllText(cert, certRes.Certificate);
|
||||||
|
|
||||||
|
string key = Path.Combine(ssl, site.name + ".key");
|
||||||
|
if(!File.Exists(key)) {
|
||||||
|
using (StreamWriter writer = File.CreateText(key))
|
||||||
|
Library.ExportPrivateKey(certRes.PrivateKey, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
//try to make new order
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//create new orders
|
||||||
|
Console.WriteLine("2. Client New Order...");
|
||||||
|
Task<Dictionary<string, string>> orders = client.NewOrder(site.hosts, site.challenge);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Key: " + result.Key + Environment.NewLine + "Value: " + result.Value);
|
||||||
|
string[] splitToken = result.Value.Split('~');
|
||||||
|
|
||||||
|
string token = Path.Combine(acme, splitToken[0]);
|
||||||
|
File.WriteAllText(token, splitToken[1]);
|
||||||
|
|
||||||
|
//for Selinux on centos7
|
||||||
|
Console.WriteLine(Library.RestoreCon(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "dns-01": {
|
||||||
|
//Manage DNS server MX record, depends from provider
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//complete challanges
|
||||||
|
Console.WriteLine("3. Client Complete Challange...");
|
||||||
|
client.CompleteChallenges().Wait();
|
||||||
|
Console.WriteLine("Challanges comleted.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex.Message.ToString());
|
||||||
|
client.GetOrder(site.hosts).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// get cached certificate and chech if it's valid
|
// Download new certificate
|
||||||
CachedCertificateResult certRes = new CachedCertificateResult();
|
Console.WriteLine("4. Download certificate...");
|
||||||
if (client.TryGetCachedCertificate(hosts, out certRes))
|
client.GetCertificate().Wait();
|
||||||
{
|
|
||||||
File.WriteAllText(Path.Combine(certsPath, "maks-it.com.crt"), certRes.Certificate);
|
|
||||||
|
|
||||||
using (StreamWriter writer = File.CreateText(Path.Combine(certsPath, "maks-it.com.key")))
|
// Write to filesystem
|
||||||
Library.ExportPrivateKey(certRes.PrivateKey, writer);
|
certRes = new CachedCertificateResult();
|
||||||
}
|
if (client.TryGetCachedCertificate(site.hosts, out certRes)) {
|
||||||
else {
|
string cert = Path.Combine(ssl, site.name + ".crt");
|
||||||
|
File.WriteAllText(cert, certRes.Certificate);
|
||||||
|
|
||||||
|
string key = Path.Combine(ssl, site.name + ".key");
|
||||||
|
using (StreamWriter writer = File.CreateText(key))
|
||||||
|
Library.ExportPrivateKey(certRes.PrivateKey, writer);
|
||||||
|
|
||||||
client.NewNonce().Wait();
|
Console.WriteLine("Certificate saved.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Console.WriteLine("Unable to get new cached certificate.");
|
||||||
|
}
|
||||||
|
|
||||||
// 2
|
|
||||||
try
|
}
|
||||||
{
|
}
|
||||||
Console.WriteLine("2. Client New Order...");
|
catch (Exception ex) {
|
||||||
Task<Dictionary<string, string>> orders = client.NewOrder(hosts.ToArray(), "http-01");
|
Console.WriteLine(ex.Message.ToString());
|
||||||
orders.Wait();
|
}
|
||||||
|
|
||||||
foreach (var result in orders.Result)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Key: " + result.Key + Environment.NewLine + "Value: " + result.Value);
|
|
||||||
string[] splitToken = result.Value.Split('~');
|
|
||||||
File.WriteAllText(FS.Path.Combine(tokensPath, splitToken[0]), splitToken[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3
|
|
||||||
Console.WriteLine("3. Client Complete Challange...");
|
|
||||||
client.CompleteChallenges().Wait();
|
|
||||||
Console.WriteLine("Challanges comleted.");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex) {
|
||||||
{
|
|
||||||
Console.WriteLine(ex.Message.ToString());
|
Console.WriteLine(ex.Message.ToString());
|
||||||
client.GetOrder(hosts.ToArray()).Wait();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 4 Download certificate
|
|
||||||
Console.WriteLine("4. Download certificate...");
|
|
||||||
client.GetCertificate().Wait();
|
|
||||||
|
|
||||||
|
|
||||||
// 5 Write to filesystem
|
|
||||||
//CachedCertificateResult certRes = new CachedCertificateResult();
|
|
||||||
//if (client.TryGetCachedCertificate(hosts, out certRes)) {
|
|
||||||
// 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.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
Console.WriteLine(ex.Message.ToString());
|
Console.WriteLine(ex.Message.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Console.Read();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
48
LetsEncrypt/SettingsProvider.cs
Normal file
48
LetsEncrypt/SettingsProvider.cs
Normal 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
58
LetsEncrypt/settings.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user