CertificateEngine.cs 24 KB

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