CertificateEngine.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Formats.Asn1;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Security.Cryptography.X509Certificates;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using System.Timers;
  11. using ACMESharp.Authorizations;
  12. using ACMESharp.Protocol;
  13. using ACMESharp.Protocol.Resources;
  14. using GenHTTP.Api.Content;
  15. using GenHTTP.Api.Infrastructure;
  16. using GenHTTP.Api.Protocol;
  17. using GenHTTP.Engine;
  18. using GenHTTP.Modules.IO;
  19. using GenHTTP.Modules.Practices;
  20. using InABox.Core;
  21. using Microsoft.VisualBasic;
  22. using Newtonsoft.Json;
  23. using PRSServices;
  24. using Timer = System.Timers.Timer;
  25. namespace PRSServer;
  26. public class CertificateHandler : IHandler
  27. {
  28. private static readonly string AcmeHttp01PathPrefix =
  29. Http01ChallengeValidationDetails.HttpPathPrefix.Trim('/');
  30. public CertificateHandler(IHandler parent)
  31. {
  32. Parent = parent;
  33. }
  34. public IDictionary<string, Http01ChallengeValidationDetails> Http01Responses { get; set; }
  35. = new Dictionary<string, Http01ChallengeValidationDetails>();
  36. public IHandler Parent { get; init; }
  37. public IAsyncEnumerable<ContentElement> GetContentAsync(IRequest request)
  38. {
  39. throw new NotImplementedException();
  40. }
  41. public ValueTask<IResponse?> HandleAsync(IRequest request)
  42. {
  43. var fullPath = request.Target.Path.ToString().Trim('/');
  44. Logger.Send(LogType.Information, "", "Running ACME Challenge Request Handler");
  45. IResponseBuilder response;
  46. if (Http01Responses.TryGetValue(fullPath, out var httpDetails))
  47. {
  48. Logger.Send(LogType.Information, "",
  49. string.Format("Found match on [{0}] with response [{1}]", fullPath, httpDetails.HttpResourceValue));
  50. response = request.Respond()
  51. .Type(new FlexibleContentType(httpDetails.HttpResourceContentType))
  52. .Content(Resource.FromString(httpDetails.HttpResourceValue).Build());
  53. }
  54. else
  55. {
  56. Logger.Send(LogType.Information, "", string.Format("NO MATCH FOUND ON [{0}]", fullPath));
  57. response = request.Respond()
  58. .Status(ResponseStatus.NotFound)
  59. .Content(Resource.FromString("No matching ACME response path").Build());
  60. }
  61. return new ValueTask<IResponse?>(response.Build());
  62. }
  63. public ValueTask PrepareAsync()
  64. {
  65. return new ValueTask();
  66. }
  67. public IEnumerable<ContentElement> GetContent(IRequest request)
  68. {
  69. return Enumerable.Empty<ContentElement>();
  70. }
  71. public bool AddChallengeHandling(IChallengeValidationDetails chlngDetails)
  72. {
  73. if (chlngDetails is not Http01ChallengeValidationDetails httpDetails)
  74. {
  75. Logger.Send(LogType.Information, "", "Unable to handle non-Http01 Challenge details");
  76. return false;
  77. }
  78. var fullPath = httpDetails.HttpResourcePath.Trim('/');
  79. Logger.Send(LogType.Information, "", string.Format("Handling Challenges with HTTP full path of [{0}]", fullPath));
  80. Http01Responses[fullPath] = httpDetails;
  81. return true;
  82. }
  83. }
  84. public class CertificateHandlerBuilder : IHandlerBuilder<CertificateHandlerBuilder>
  85. {
  86. private readonly List<IConcernBuilder> _Concerns = new();
  87. public CertificateHandlerBuilder(CertificateEngine engine)
  88. {
  89. Engine = engine;
  90. }
  91. private CertificateEngine Engine { get; }
  92. public CertificateHandlerBuilder Add(IConcernBuilder concern)
  93. {
  94. _Concerns.Add(concern);
  95. return this;
  96. }
  97. public IHandler Build(IHandler parent)
  98. {
  99. return Concerns.Chain(parent, _Concerns, p =>
  100. {
  101. Engine.Handler = new CertificateHandler(p);
  102. return Engine.Handler;
  103. });
  104. }
  105. }
  106. public class CertificateEngine : Engine<CertificateEngineProperties>
  107. {
  108. private readonly DateTime _scheduleTime;
  109. public CertificateHandler? Handler;
  110. private IServerHost? host;
  111. private static string ChallengeType = AcmeState.Http01ChallengeType;
  112. static CertificateEngine()
  113. {
  114. CertificateFolder = Path.Combine(
  115. Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location ?? "") ?? "",
  116. "certs"
  117. );
  118. }
  119. private static T? Load<T>(string path)
  120. {
  121. if (!File.Exists(path))
  122. return default;
  123. if (typeof(T) == typeof(Stream)) return (T)(object)new FileStream(path, FileMode.Open);
  124. if (typeof(T) == typeof(byte[])) return (T)(object)File.ReadAllBytes(path);
  125. return JsonConvert.DeserializeObject<T>(File.ReadAllText(path)) ?? default;
  126. }
  127. protected static void Save<T>(string path, T value)
  128. {
  129. if (typeof(Stream).IsAssignableFrom(typeof(T)))
  130. {
  131. var rs = (Stream)(object)value;
  132. using (var ws = new FileStream(path, FileMode.Create))
  133. {
  134. rs.CopyTo(ws);
  135. }
  136. }
  137. else if (typeof(T) == typeof(byte[]))
  138. {
  139. var ba = (byte[])(object)value;
  140. File.WriteAllBytes(path, ba);
  141. }
  142. else
  143. {
  144. File.WriteAllText(path,
  145. JsonConvert.SerializeObject(value, Formatting.Indented));
  146. }
  147. }
  148. public void DeleteFile(string filename)
  149. {
  150. try
  151. {
  152. File.Delete(filename);
  153. }
  154. catch (Exception) { }
  155. }
  156. public void RefreshCertificateFile(CancellationToken cancellationToken)
  157. {
  158. Logger.Send(LogType.Information, "", "Updating HTTPS certificate...");
  159. if (!Directory.Exists(CertificateFolder))
  160. {
  161. Logger.Send(LogType.Information, "", "Creating certificate folder...");
  162. Directory.CreateDirectory(CertificateFolder);
  163. }
  164. DeleteFile(OrderFile);
  165. DeleteFile(CertificateFile);
  166. //DeleteFile(AuthorizationsFile);
  167. ServiceDirectory = Load<ServiceDirectory>(ServiceDirectoryFile);
  168. Account = Load<AccountDetails>(AccountFile);
  169. AccountKey = Load<ExamplesAccountKey>(AccountKeyFile);
  170. Order = Load<OrderDetails>(OrderFile);
  171. Authorizations = Load<Dictionary<string, Authorization>>(AuthorizationsFile);
  172. var certRaw = Load<byte[]>(CertificateFile);
  173. if (certRaw?.Length > 0)
  174. Certificate = new X509Certificate2(certRaw);
  175. DnsNames = Properties.ParseDomainNames();
  176. AccountContactEmails = Properties.AccountContactEmails.Split(',');
  177. Logger.Send(LogType.Information, "", "Initializing HTTP Handler");
  178. host = Host.Create()
  179. .Handler(new CertificateHandlerBuilder(this))
  180. .Defaults()
  181. .Port(80)
  182. .Start();
  183. // We delay for 5 seconds just to give other parts of
  184. // the service (like request handling) to get in place
  185. Task.Delay(5 * 1000, cancellationToken);
  186. if (DoTheWork())
  187. {
  188. Logger.Send(LogType.Information, "", "HTTPS Refresh Complete!");
  189. }
  190. else
  191. {
  192. Logger.Send(LogType.Information, "", "HTTPS Refresh Failed");
  193. }
  194. host.Stop();
  195. }
  196. private bool DoTheWork()
  197. {
  198. try
  199. {
  200. Logger.Send(LogType.Information, "", "** DOING WORKING *****************************************");
  201. Logger.Send(LogType.Information, "", $"DNS Names: {Properties.DomainNames}");
  202. var acmeUrl = new Uri(Properties.CaUrl);
  203. using var acme = new AcmeProtocolClient(acmeUrl, usePostAsGet: true);
  204. var task = DoTheWorkAsync(acme);
  205. task.Wait();
  206. return task.Result;
  207. }
  208. catch(Exception e)
  209. {
  210. Logger.Send(LogType.Error, "", CoreUtils.FormatException(e));
  211. return false;
  212. }
  213. }
  214. private async Task ClearAuthorizations(AcmeProtocolClient acme)
  215. {
  216. if(Authorizations != null)
  217. {
  218. foreach (var (url, authorization) in Authorizations)
  219. {
  220. if (authorization.Status != AcmeState.ValidStatus)
  221. {
  222. try
  223. {
  224. await acme.DeactivateAuthorizationAsync(url);
  225. }
  226. catch(Exception e)
  227. {
  228. Logger.Send(LogType.Error, "", $"Could not deactivate authorization: {CoreUtils.FormatException(e)}");
  229. }
  230. }
  231. }
  232. Authorizations.Clear();
  233. Save(AuthorizationsFile, Authorizations);
  234. }
  235. else
  236. {
  237. Authorizations = new Dictionary<string, Authorization>();
  238. }
  239. }
  240. private async Task<bool> DoTheWorkAsync(AcmeProtocolClient acme)
  241. {
  242. ServiceDirectory = await acme.GetDirectoryAsync();
  243. Save(ServiceDirectoryFile, ServiceDirectory);
  244. acme.Directory = ServiceDirectory;
  245. Save(TermsOfServiceFile, await acme.GetTermsOfServiceAsync());
  246. // This line basically has to be called before all ACME things.
  247. await acme.GetNonceAsync();
  248. if (!await ResolveAccount(acme))
  249. return false;
  250. ClearAuthorizations(acme).Wait();
  251. if (!await ResolveOrder(acme))
  252. return false;
  253. if (!await ResolveChallenges(acme))
  254. return false;
  255. if (!await ResolveAuthorizations(acme))
  256. return false;
  257. if (!await ResolveCertificate(acme))
  258. return false;
  259. return true;
  260. }
  261. private async Task<bool> ResolveAccount(AcmeProtocolClient acme)
  262. {
  263. // TODO: All this ASSUMES a fixed key type/size for now
  264. if (Account == null || AccountKey == null)
  265. {
  266. var contacts = AccountContactEmails.Where(x => !string.IsNullOrEmpty(x)).Select(x => $"mailto:{x}");
  267. Logger.Send(LogType.Information, "", "Creating ACME Account");
  268. Account = await acme.CreateAccountAsync(
  269. contacts,
  270. Properties.AcceptTermsOfService);
  271. AccountKey = new ExamplesAccountKey
  272. {
  273. KeyType = acme.Signer.JwsAlg,
  274. KeyExport = acme.Signer.Export()
  275. };
  276. Save(AccountFile, Account);
  277. Save(AccountKeyFile, AccountKey);
  278. acme.Account = Account;
  279. }
  280. else
  281. {
  282. acme.Account = Account;
  283. acme.Signer.Import(AccountKey.KeyExport);
  284. }
  285. return true;
  286. }
  287. private async Task<bool> ResolveOrder(AcmeProtocolClient acme)
  288. {
  289. var now = DateTime.Now;
  290. if (!string.IsNullOrEmpty(Order?.OrderUrl))
  291. {
  292. Logger.Send(LogType.Information, "", "Existing Order found; refreshing");
  293. Order = await acme.GetOrderDetailsAsync(Order.OrderUrl, Order);
  294. }
  295. if (Order?.Payload?.Error != null)
  296. {
  297. Logger.Send(LogType.Information, "", "Existing Order reported an Error:");
  298. Logger.Send(LogType.Information, "", JsonConvert.SerializeObject(Order.Payload.Error));
  299. Logger.Send(LogType.Information, "", "Resetting existing order");
  300. Order = null;
  301. }
  302. if (AcmeState.InvalidStatus == Order?.Payload.Status)
  303. {
  304. Logger.Send(LogType.Information, "", "Existing Order is INVALID; resetting");
  305. Order = null;
  306. }
  307. if (!DateTime.TryParse(Order?.Payload?.Expires, out var orderExpires)
  308. || orderExpires < now)
  309. {
  310. Logger.Send(LogType.Information, "", "Existing Order is EXPIRED; resetting");
  311. Order = null;
  312. }
  313. if (DateTime.TryParse(Order?.Payload?.NotAfter, out var orderNotAfter)
  314. && orderNotAfter < now)
  315. {
  316. Logger.Send(LogType.Information, "", "Existing Order is OUT-OF-DATE; resetting");
  317. Order = null;
  318. }
  319. if (Order?.Payload == null)
  320. {
  321. Logger.Send(LogType.Information, "", "Creating NEW Order");
  322. Order = await acme.CreateOrderAsync(DnsNames);
  323. }
  324. Save(OrderFile, Order);
  325. return true;
  326. }
  327. private async Task<bool> ResolveChallenges(AcmeProtocolClient acme)
  328. {
  329. if (AcmeState.PendingStatus == Order?.Payload?.Status)
  330. {
  331. Logger.Send(LogType.Information, "", "Order is pending, resolving Authorizations");
  332. if (Authorizations == null)
  333. Authorizations = new Dictionary<string, Authorization>();
  334. foreach (var authzUrl in Order.Payload.Authorizations)
  335. {
  336. var authz = await acme.GetAuthorizationDetailsAsync(authzUrl);
  337. Authorizations[authzUrl] = authz;
  338. if (AcmeState.PendingStatus == authz.Status)
  339. foreach (var chlng in authz.Challenges)
  340. if (string.IsNullOrEmpty(ChallengeType) || ChallengeType == chlng.Type)
  341. {
  342. var chlngValidation = AuthorizationDecoder.DecodeChallengeValidation(
  343. authz, chlng.Type, acme.Signer);
  344. if (Handler.AddChallengeHandling(chlngValidation))
  345. {
  346. Logger.Send(LogType.Information, "", "Challenge Handler has handled challenge:");
  347. Logger.Send(LogType.Information, "", JsonConvert.SerializeObject(chlngValidation, Formatting.Indented));
  348. var chlngUpdated = await acme.AnswerChallengeAsync(chlng.Url);
  349. if (chlngUpdated.Error != null)
  350. {
  351. Logger.Send(LogType.Error, "", "Submitting Challenge Answer reported an error:");
  352. Logger.Send(LogType.Error, "", JsonConvert.SerializeObject(chlngUpdated.Error));
  353. }
  354. }
  355. Logger.Send(LogType.Information, "", "Refreshing Authorization status");
  356. authz = await acme.GetAuthorizationDetailsAsync(authzUrl);
  357. Authorizations[authzUrl] = authz;
  358. if (AcmeState.PendingStatus != authz.Status)
  359. break;
  360. }
  361. }
  362. Save(AuthorizationsFile, Authorizations);
  363. Logger.Send(LogType.Information, "", "Refreshing Order status");
  364. Order = await acme.GetOrderDetailsAsync(Order.OrderUrl, Order);
  365. Save(OrderFile, Order);
  366. }
  367. return true;
  368. }
  369. private async Task<bool> ResolveAuthorizations(AcmeProtocolClient acme)
  370. {
  371. if (AcmeState.InvalidStatus == Order?.Payload?.Status)
  372. {
  373. Logger.Send(LogType.Information, "", "Current Order is INVALID; aborting");
  374. return false;
  375. }
  376. if (AcmeState.ValidStatus == Order?.Payload?.Status)
  377. {
  378. Logger.Send(LogType.Information, "", "Current Order is already VALID; skipping");
  379. return true;
  380. }
  381. var now = DateTime.Now;
  382. do
  383. {
  384. if (Authorizations == null)
  385. Authorizations = new Dictionary<string, Authorization>();
  386. // Wait for all Authorizations to be valid or any one to go invalid
  387. var validCount = 0;
  388. var invalidCount = 0;
  389. foreach (var authz in Authorizations)
  390. switch (authz.Value.Status)
  391. {
  392. case AcmeState.ValidStatus:
  393. ++validCount;
  394. break;
  395. case AcmeState.InvalidStatus:
  396. ++invalidCount;
  397. break;
  398. }
  399. if (validCount == Authorizations.Count)
  400. {
  401. Logger.Send(LogType.Information, "", string.Format("All Authorizations ({0}) are valid", validCount));
  402. break;
  403. }
  404. if (invalidCount > 0)
  405. {
  406. Logger.Send(LogType.Error, "", $"Found {invalidCount} invalid Authorization(s): ({
  407. string.Join(',', Authorizations.Where(x => x.Value.Status == AcmeState.InvalidStatus).Select(x => x.Key))
  408. }); ABORTING");
  409. return false;
  410. }
  411. Logger.Send(LogType.Information, "", string.Format("Found {0} Authorization(s) NOT YET valid", Authorizations.Count - validCount));
  412. if (now.AddSeconds(Properties.WaitForAuthorizations) < DateTime.Now)
  413. {
  414. Logger.Send(LogType.Error, "", "Timed out waiting for Authorizations; ABORTING");
  415. Logger.Send(LogType.Error, "", $"The following authorisations are not valid: ({
  416. string.Join(',', Authorizations.Where(x => x.Value.Status != AcmeState.ValidStatus).Select(x => x.Key))
  417. })");
  418. return false;
  419. }
  420. // We wait in 5s increments
  421. await Task.Delay(5000);
  422. foreach (var authzUrl in Order.Payload.Authorizations)
  423. // Update all the Authorizations still pending
  424. if (AcmeState.PendingStatus == Authorizations[authzUrl].Status)
  425. Authorizations[authzUrl] = await acme.GetAuthorizationDetailsAsync(authzUrl);
  426. } while (true);
  427. Save(AuthorizationsFile, Authorizations);
  428. return true;
  429. }
  430. private async Task<bool> ResolveCertificate(AcmeProtocolClient acme)
  431. {
  432. if (Certificate != null)
  433. {
  434. Logger.Send(LogType.Information, "", "Certificate is already resolved");
  435. return true;
  436. }
  437. CertPrivateKey? key = null;
  438. Logger.Send(LogType.Information, "", "Refreshing Order status");
  439. Order = await acme.GetOrderDetailsAsync(Order.OrderUrl, Order);
  440. Save(OrderFile, Order);
  441. if (AcmeState.PendingStatus == Order.Payload.Status || AcmeState.ReadyStatus == Order.Payload.Status)
  442. {
  443. Logger.Send(LogType.Information, "", "Generating CSR");
  444. byte[] csr;
  445. switch (Properties.CertificateKeyAlgor)
  446. {
  447. case "rsa":
  448. key = CertHelper.GenerateRsaPrivateKey(
  449. Properties.CertificateKeySize ?? CertificateEngineProperties.DefaultRsaKeySize);
  450. csr = CertHelper.GenerateRsaCsr(DnsNames, key);
  451. break;
  452. case "ec":
  453. key = CertHelper.GenerateEcPrivateKey(
  454. Properties.CertificateKeySize ?? CertificateEngineProperties.DefaultEcKeySize);
  455. csr = CertHelper.GenerateEcCsr(DnsNames, key);
  456. break;
  457. default:
  458. throw new Exception("Unknown Certificate Key Algorithm: "
  459. + Properties.CertificateKeyAlgor);
  460. }
  461. using (var keyPem = new MemoryStream())
  462. {
  463. CertHelper.ExportPrivateKey(key, EncodingFormat.PEM, keyPem);
  464. keyPem.Position = 0L;
  465. Save(CertificateKeysFile, keyPem);
  466. }
  467. Save(CertificateRequestFile, csr);
  468. Logger.Send(LogType.Information, "", "Finalizing Order");
  469. Order = await acme.FinalizeOrderAsync(Order.Payload.Finalize, csr);
  470. Save(OrderFile, Order);
  471. }
  472. if (string.IsNullOrEmpty(Order.Payload.Certificate))
  473. {
  474. Logger.Send(LogType.Information, "", "Order Certificate is NOT READY YET");
  475. var now = DateTime.Now;
  476. do
  477. {
  478. Logger.Send(LogType.Information, "", "Waiting...");
  479. // We wait in 5s increments
  480. await Task.Delay(5000);
  481. Order = await acme.GetOrderDetailsAsync(Order.OrderUrl, Order);
  482. Save(OrderFile, Order);
  483. if (!string.IsNullOrEmpty(Order.Payload.Certificate))
  484. break;
  485. if (DateTime.Now < now.AddSeconds(Properties.WaitForCertificate))
  486. {
  487. Logger.Send(LogType.Information, "", "Timed Out!");
  488. return false;
  489. }
  490. } while (true);
  491. }
  492. if (AcmeState.ValidStatus != Order.Payload.Status)
  493. {
  494. Logger.Send(LogType.Information, "", "Order is NOT VALID");
  495. return false;
  496. }
  497. Logger.Send(LogType.Information, "", "Retreiving Certificate");
  498. var certBytes = await acme.GetOrderCertificateAsync(Order);
  499. Save(CertificateChainFile, certBytes);
  500. if (key == null)
  501. {
  502. Logger.Send(LogType.Information, "", "Loading private key");
  503. key = CertHelper.ImportPrivateKey(EncodingFormat.PEM, Load<Stream>(CertificateKeysFile));
  504. }
  505. using (var crtStream = new MemoryStream(certBytes))
  506. using (var pfxStream = new MemoryStream())
  507. {
  508. Logger.Send(LogType.Information, "", "Reading in Certificate chain (PEM)");
  509. var cert = CertHelper.ImportCertificate(EncodingFormat.PEM, crtStream);
  510. Logger.Send(LogType.Information, "", "Writing out Certificate archive (PKCS12)");
  511. CertHelper.ExportArchive(key, new[] { cert }, ArchiveFormat.PKCS12, pfxStream);
  512. pfxStream.Position = 0L;
  513. Save(CertificateFile, pfxStream);
  514. }
  515. Logger.Send(LogType.Information, "", "Loading PKCS12 archive as active certificate");
  516. Certificate = new X509Certificate2(Load<byte[]>(CertificateFile));
  517. return true;
  518. }
  519. private void CheckForRefresh()
  520. {
  521. Logger.Send(LogType.Information, "", "Checking for refresh...");
  522. if (!Directory.Exists(CertificateFolder))
  523. {
  524. Logger.Send(LogType.Information, "", "Creating certificate folder...");
  525. Directory.CreateDirectory(CertificateFolder);
  526. }
  527. var certRaw = Load<byte[]>(CertificateFile);
  528. if (certRaw?.Length > 0)
  529. {
  530. Certificate = new X509Certificate2(certRaw);
  531. }
  532. if(Certificate == null)
  533. {
  534. RefreshCertificateFile(CancellationToken.None);
  535. return;
  536. }
  537. DateTime now = DateTime.Now;
  538. TimeSpan startDiff = now - Certificate.NotBefore;
  539. TimeSpan endDiff = Certificate.NotAfter - now;
  540. // If the certificate will expire in less than 30 days or if the certificate is not yet valid
  541. if (startDiff < TimeSpan.Zero || endDiff < TimeSpan.FromDays(30))
  542. {
  543. RefreshCertificateFile(CancellationToken.None);
  544. return;
  545. }
  546. var names = GetDnsNames(Certificate).ToHashSet();
  547. var requiredNames = Properties.ParseDomainNames();
  548. if(requiredNames.Any(x => !names.Contains(x)))
  549. {
  550. RefreshCertificateFile(CancellationToken.None);
  551. return;
  552. }
  553. Logger.Send(LogType.Information, "", "Refresh not required!");
  554. }
  555. public override void Run()
  556. {
  557. System.Threading.Timer timer = new System.Threading.Timer(
  558. (o) => { CheckForRefresh(); },
  559. null,
  560. 3000,
  561. 1000 * 60 * 60 * 24
  562. );
  563. Thread.Sleep(Timeout.Infinite);
  564. }
  565. public override void Stop()
  566. {
  567. host?.Stop();
  568. }
  569. #region File Names
  570. public static string CertificateFolder { get; }
  571. private string ServiceDirectoryFile => Path.Combine(CertificateFolder, "00-ServiceDirectory.json");
  572. private string TermsOfServiceFile => Path.Combine(CertificateFolder, "05-TermsOfService");
  573. private string AccountFile => Path.Combine(CertificateFolder, "10-Account.json");
  574. private string AccountKeyFile => Path.Combine(CertificateFolder, "15-AccountKey.json");
  575. private string OrderFile => Path.Combine(CertificateFolder, "50-Order.json");
  576. private string AuthorizationsFile => Path.Combine(CertificateFolder, "52-Authorizations.json");
  577. private string CertificateKeysFile => Path.Combine(CertificateFolder, "70-CertificateKeys.pem");
  578. private string CertificateRequestFile => Path.Combine(CertificateFolder, "72-CertificateRequest.der");
  579. private string CertificateChainFile => Path.Combine(CertificateFolder, "74-CertificateChain.pem");
  580. public static string CertificateFile => Path.Combine(CertificateFolder, "80-Certificate.pfx");
  581. #endregion
  582. #region Extra State Properties
  583. private ServiceDirectory? ServiceDirectory;
  584. private AccountDetails? Account;
  585. private ExamplesAccountKey? AccountKey;
  586. private OrderDetails? Order;
  587. private Dictionary<string, Authorization>? Authorizations;
  588. private X509Certificate2? Certificate;
  589. private IEnumerable<string> DnsNames;
  590. private IEnumerable<string> AccountContactEmails;
  591. #endregion
  592. }