CertificateEngine.cs 28 KB

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