3using System.Diagnostics;
8using System.Security.Cryptography.X509Certificates;
10using System.Threading.Tasks;
44 private string[] alternativeDomains =
null;
45 private byte[] certificate =
null;
46 private byte[] privateKey =
null;
47 private byte[] pfx =
null;
48 private string humanReadableName =
string.Empty;
49 private string humanReadableNameLanguage =
string.Empty;
50 private string humanReadableDescription =
string.Empty;
51 private string humanReadableDescriptionLanguage =
string.Empty;
52 private string domain =
string.Empty;
53 private string acmeDirectory =
string.Empty;
54 private string contactEMail =
string.Empty;
55 private string urlToS =
string.Empty;
56 private string password =
string.Empty;
57 private string openSslPath =
string.Empty;
58 private string dynDnsTemplate =
string.Empty;
59 private string checkIpScript =
string.Empty;
60 private string updateIpScript =
string.Empty;
61 private string dynDnsAccount =
string.Empty;
62 private string dynDnsPassword =
string.Empty;
63 private int dynDnsInterval = 300;
64 private bool useDomainName =
false;
65 private bool dynamicDns =
false;
66 private bool useEncryption =
true;
67 private bool customCA =
false;
68 private bool acceptToS =
false;
69 private bool deferredTest =
false;
71 private string challenge =
string.Empty;
72 private string token =
string.Empty;
82 [DefaultValueStringEmpty]
86 set => this.domain = value;
95 get => this.alternativeDomains;
96 set => this.alternativeDomains = value;
102 [DefaultValue(
false)]
105 get => this.useDomainName;
108 this.useDomainName = value;
109 if (!this.useDomainName)
111 this.domain =
string.Empty;
112 this.alternativeDomains =
null;
113 this.dynamicDns =
false;
114 this.useEncryption =
false;
115 this.customCA =
false;
123 [DefaultValue(
false)]
126 get => this.dynamicDns;
127 set => this.dynamicDns = value;
136 get => this.useEncryption;
137 set => this.useEncryption = value;
143 [DefaultValue(
false)]
146 get => this.customCA;
147 set => this.customCA = value;
153 [DefaultValueStringEmpty]
156 get => this.acmeDirectory;
157 set => this.acmeDirectory = value;
163 [DefaultValueStringEmpty]
166 get => this.contactEMail;
167 set => this.contactEMail = value;
173 [DefaultValueStringEmpty]
177 set => this.urlToS = value;
183 [DefaultValue(
false)]
186 get => this.acceptToS;
187 set => this.acceptToS = value;
196 get => this.certificate;
197 set => this.certificate = value;
206 get => this.privateKey;
207 set => this.privateKey = value;
217 set => this.pfx = value;
226 get => this.password;
227 set => this.password = value;
238 [DefaultValueStringEmpty]
241 get => this.openSslPath;
242 set => this.openSslPath = value;
248 [DefaultValueStringEmpty]
251 get => this.dynDnsTemplate;
252 set => this.dynDnsTemplate = value;
258 [DefaultValueStringEmpty]
261 get => this.checkIpScript;
262 set => this.checkIpScript = value;
268 [DefaultValueStringEmpty]
271 get => this.updateIpScript;
272 set => this.updateIpScript = value;
278 [DefaultValueStringEmpty]
281 get => this.dynDnsAccount;
282 set => this.dynDnsAccount = value;
291 get => this.dynDnsInterval;
292 set => this.dynDnsInterval = value;
298 [DefaultValueStringEmpty]
301 get => this.dynDnsPassword;
302 set => this.dynDnsPassword = value;
308 [DefaultValueStringEmpty]
311 get => this.humanReadableName;
312 set => this.humanReadableName = value;
318 [DefaultValueStringEmpty]
321 get => this.humanReadableNameLanguage;
322 set => this.humanReadableNameLanguage = value;
328 [DefaultValueStringEmpty]
331 get => this.humanReadableDescription;
332 set => this.humanReadableDescription = value;
338 [DefaultValueStringEmpty]
341 get => this.humanReadableDescriptionLanguage;
342 set => this.humanReadableDescriptionLanguage = value;
351 get => this.localizedNames;
352 set => this.localizedNames = value;
361 get => this.localizedDescriptions;
362 set => this.localizedDescriptions = value;
368 public bool HasToS => !
string.IsNullOrEmpty(this.urlToS);
374 (!(this.certificate is
null) && !(this.privateKey is
null)) ||
375 (!(this.pfx is
null) && !(this.password is
null));
380 public override string Resource =>
"/Settings/Domain.md";
402 return Gateway.ConfigureDomain(
this);
420 this.testDomainNames = WebServer.
Register(
"/Settings/TestDomainNames",
null, this.TestDomainNames,
true,
false,
true);
421 this.testDomainName = WebServer.
Register(
"/Settings/TestDomainName", this.TestDomainName,
true,
false,
true);
422 this.testCA = WebServer.
Register(
"/Settings/TestCA",
null, this.TestCA,
true,
false,
true);
423 this.acmeChallenge = WebServer.
Register(
"/.well-known/acme-challenge", this.
AcmeChallenge,
true,
true,
true);
424 this.saveNames = WebServer.
Register(
"/Settings/SaveNames",
null, this.SaveNames,
true,
false,
true);
425 this.saveDescriptions = WebServer.
Register(
"/Settings/SaveDescriptions",
null, this.SaveDescriptions,
true,
false,
true);
427 return base.InitSetup(WebServer);
443 return base.UnregisterSetup(WebServer);
462 if (Content.
HasError || !(Content.
Decoded is Dictionary<string, object> Parameters))
468 if (!Parameters.TryGetValue(
"domainName", out
object Obj) || !(Obj is
string DomainName) ||
469 !Parameters.TryGetValue(
"dynamicDns", out Obj) || !(Obj is
bool DynamicDns) ||
470 !Parameters.TryGetValue(
"dynDnsTemplate", out Obj) || !(Obj is
string DynDnsTemplate) ||
471 !Parameters.TryGetValue(
"checkIpScript", out Obj) || !(Obj is
string CheckIpScript) ||
472 !Parameters.TryGetValue(
"updateIpScript", out Obj) || !(Obj is
string UpdateIpScript) ||
473 !Parameters.TryGetValue(
"dynDnsAccount", out Obj) || !(Obj is
string DynDnsAccount) ||
474 !Parameters.TryGetValue(
"dynDnsPassword", out Obj) || !(Obj is
string DynDnsPassword) ||
475 !Parameters.TryGetValue(
"dynDnsInterval", out Obj) || !(Obj is
int DynDnsInterval))
481 if (
string.Compare(DomainName,
"localhost",
true) == 0)
487 List<string> AlternativeNames =
new List<string>();
490 while (Parameters.TryGetValue(
"altDomainName" + Index.ToString(), out Obj) && Obj is
string AltDomainName && !
string.IsNullOrEmpty(AltDomainName))
492 if (
string.Compare(AltDomainName,
"localhost",
true) == 0)
498 AlternativeNames.Add(AltDomainName);
502 if (Parameters.TryGetValue(
"altDomainName", out Obj) && Obj is
string AltDomainName2 && !
string.IsNullOrEmpty(AltDomainName2))
504 if (
string.Compare(AltDomainName2,
"localhost",
true) == 0)
510 AlternativeNames.Add(AltDomainName2);
513 string TabID = Request.
Header[
"X-TabID"];
514 if (
string.IsNullOrEmpty(TabID))
527 this.domain = DomainName;
528 this.alternativeDomains = AlternativeNames.Count == 0 ? null : AlternativeNames.ToArray();
529 this.useDomainName =
true;
531 Response.StatusCode = 200;
532 Response.StatusMessage =
"OK";
534 Task
_ = Task.Run(async () => await this.Test(TabID,
false));
539 Response.StatusCode = 200;
540 Response.StatusMessage =
"OK";
542 return Response.
Write(this.token);
545 private async Task<bool> Test(
string TabID,
bool EnvironmentSetup)
549 if (!
string.IsNullOrEmpty(this.domain))
551 if (!await this.Test(TabID, EnvironmentSetup, this.domain))
553 if (
string.IsNullOrEmpty(TabID))
561 if (!(this.alternativeDomains is
null))
563 foreach (
string AltDomainName
in this.alternativeDomains)
565 if (!await this.Test(TabID, EnvironmentSetup, AltDomainName))
567 if (
string.IsNullOrEmpty(TabID))
570 await ClientEvents.PushEvent(
new string[] { TabID },
"NameNotValid", AltDomainName,
false,
"User");
579 this.Updated = DateTime.Now;
582 if (!
string.IsNullOrEmpty(TabID))
583 await ClientEvents.PushEvent(
new string[] { TabID },
"NamesOK",
string.Empty,
false,
"User");
589 if (
string.IsNullOrEmpty(TabID))
592 await ClientEvents.PushEvent(
new string[] { TabID },
"ShowStatus", ex.Message,
false,
"User");
598 internal Task<bool> CheckDynamicIp()
600 return this.CheckDynamicIp(
null,
false);
603 internal Task<bool> CheckDynamicIp(
bool EnvironmentSetup)
605 return this.CheckDynamicIp(
null, EnvironmentSetup);
608 private async Task<bool> CheckDynamicIp(
string TabID,
bool EnvironmentSetup)
612 if (!this.useDomainName || !this.dynamicDns)
617 if (!await this.CheckDynamicIp(TabID, this.domain, EnvironmentSetup))
620 foreach (
string AlternativeDomain
in this.alternativeDomains)
622 if (!await this.CheckDynamicIp(TabID, AlternativeDomain, EnvironmentSetup))
635 internal async Task<bool> CheckDynamicIp(
string TabID,
string DomainName,
bool EnvironmentSetup)
637 string Msg = await this.CheckDynamicIp(DomainName, EnvironmentSetup, async (
_, Status) =>
639 if (!
string.IsNullOrEmpty(TabID))
640 await ClientEvents.PushEvent(
new string[] { TabID },
"ShowStatus", Status,
false,
"User");
643 if (!
string.IsNullOrEmpty(Msg))
645 if (!
string.IsNullOrEmpty(TabID))
646 await ClientEvents.PushEvent(
new string[] { TabID },
"CertificateError", Msg,
false,
"User");
661 public async Task<string>
CheckDynamicIp(
string DomainName,
bool EnvironmentSetup, EventHandlerAsync<string> Status)
663 if (!this.dynamicDns)
675 string Msg =
"Unable to parse script checking current IP Address: " + ex.Message;
677 if (EnvironmentSetup)
689 string Msg =
"Unable to parse script updating the dynamic DNS server: " + ex.Message;
691 if (EnvironmentSetup)
697 await Status.Raise(
this,
"Checking current IP Address.");
708 string Msg =
"Unable to get current IP Address: " + ex.Message;
710 if (EnvironmentSetup)
716 if (!(Result is
string CurrentIP) || !IPAddress.TryParse(CurrentIP, out IPAddress
_))
718 string Msg =
"Unable to get current IP Address. Unexpected response.";
720 if (EnvironmentSetup)
726 await Status.Raise(
this,
"Current IP Address: " + CurrentIP);
730 if (LastIP == CurrentIP)
731 await Status.Raise(
this,
"IP Address has not changed for " + DomainName +
".");
736 await Status.Raise(
this,
"Updating IP address for " + DomainName +
" to " + CurrentIP +
".");
738 Variables[
"Account"] = this.dynDnsAccount;
739 Variables[
"Password"] = this.dynDnsPassword;
749 string Msg =
"Unable to register new dynamic IP Address: " + ex.Message;
751 if (EnvironmentSetup)
761 private async Task<bool> Test(
string TabID,
bool EnvironmentSetup,
string DomainName)
763 string Msg = await this.Test(DomainName, EnvironmentSetup, async (
_, Status) =>
765 if (!
string.IsNullOrEmpty(TabID))
769 if (!
string.IsNullOrEmpty(Msg))
771 if (!
string.IsNullOrEmpty(TabID))
787 public async Task<string>
Test(
string DomainName,
bool EnvironmentSetup, EventHandlerAsync<string> Status)
789 await Status.Raise(
this,
"Testing " + DomainName +
"...");
791 string Msg = await this.CheckDynamicIp(DomainName, EnvironmentSetup, Status);
792 if (!
string.IsNullOrEmpty(Msg))
797 using (HttpClient HttpClient =
new HttpClient()
799 Timeout = TimeSpan.FromMilliseconds(10000)
804 StringBuilder Url =
new StringBuilder();
807 Url.Append(
"http://");
808 Url.Append(DomainName);
810 if (Array.IndexOf(HttpPorts, 80) < 0 && HttpPorts.Length > 0)
813 Url.Append(HttpPorts[0].
ToString());
816 Url.Append(
"/Settings/TestDomainName");
818 HttpResponseMessage Response = await HttpClient.GetAsync(
"http://" + DomainName +
"/Settings/TestDomainName");
819 if (!Response.IsSuccessStatusCode)
820 return "Domain name does not point to this machine.";
822 byte[] Bin = await Response.Content.ReadAsByteArrayAsync();
823 string Token =
System.Text.Encoding.ASCII.GetString(Bin);
825 if (Token != this.token)
826 return "Unexpected response returned. Domain name does not point to this machine.";
828 catch (TimeoutException)
830 return "Time-out. Check that the domain name points to this machine.";
834 return "Unable to validate domain name: " + ex.Message;
838 await Status.Raise(
this,
"Domain name valid.");
854 if (Content.
HasError || !(Content.
Decoded is Dictionary<string, object> Parameters))
860 if (!Parameters.TryGetValue(
"useEncryption", out
object Obj) || !(Obj is
bool UseEncryption))
866 if (!Parameters.TryGetValue(
"customCA", out Obj) || !(Obj is
bool CustomCA))
872 if (!Parameters.TryGetValue(
"acmeDirectory", out Obj) || !(Obj is
string AcmeDirectory))
878 if (!Parameters.TryGetValue(
"contactEMail", out Obj) || !(Obj is
string ContactEMail))
884 if (!Parameters.TryGetValue(
"acceptToS", out Obj) || !(Obj is
bool AcceptToS))
890 if (!Parameters.TryGetValue(
"domainName", out Obj) || !(Obj is
string DomainName) ||
891 !Parameters.TryGetValue(
"dynamicDns", out Obj) || !(Obj is
bool DynamicDns) ||
892 !Parameters.TryGetValue(
"dynDnsTemplate", out Obj) || !(Obj is
string DynDnsTemplate) ||
893 !Parameters.TryGetValue(
"checkIpScript", out Obj) || !(Obj is
string CheckIpScript) ||
894 !Parameters.TryGetValue(
"updateIpScript", out Obj) || !(Obj is
string UpdateIpScript) ||
895 !Parameters.TryGetValue(
"dynDnsAccount", out Obj) || !(Obj is
string DynDnsAccount) ||
896 !Parameters.TryGetValue(
"dynDnsPassword", out Obj) || !(Obj is
string DynDnsPassword) ||
897 !Parameters.TryGetValue(
"dynDnsInterval", out Obj) || !(Obj is
int DynDnsInterval))
903 List<string> AlternativeNames =
new List<string>();
906 while (Parameters.TryGetValue(
"altDomainName" + Index.ToString(), out Obj) && Obj is
string AltDomainName && !
string.IsNullOrEmpty(AltDomainName))
908 AlternativeNames.Add(AltDomainName);
912 if (Parameters.TryGetValue(
"altDomainName", out Obj) && Obj is
string AltDomainName2 && !
string.IsNullOrEmpty(AltDomainName2))
913 AlternativeNames.Add(AltDomainName2);
915 string TabID = Request.
Header[
"X-TabID"];
916 if (
string.IsNullOrEmpty(TabID))
929 this.domain = DomainName;
930 this.alternativeDomains = AlternativeNames.Count == 0 ? null : AlternativeNames.ToArray();
931 this.useDomainName =
true;
938 Response.StatusCode = 200;
939 Response.StatusMessage =
"OK";
941 if (!this.inProgress)
943 this.inProgress =
true;
944 Task
_ = Task.Run(async () =>
948 await this.CreateCertificate(TabID,
false);
958 private bool inProgress =
false;
966 public async Task<string>
AddDomain(
string Name, EventHandlerAsync<string> Status)
969 return "Domain already registered.";
971 string Msg = await this.Test(Name,
false, Status);
972 if (!
string.IsNullOrEmpty(Msg))
975 string DomainBak = this.domain;
976 bool UseDomainBak = this.useDomainName;
977 string[] AlternativeBak = this.alternativeDomains;
979 if (
string.IsNullOrEmpty(this.domain))
982 this.useDomainName =
true;
984 else if (this.alternativeDomains is
null)
985 this.alternativeDomains =
new string[] { Name };
988 int c = this.alternativeDomains.Length;
989 string[] NewArray =
new string[c + 1];
990 this.alternativeDomains.CopyTo(NewArray, 0);
992 this.alternativeDomains = NewArray;
995 Msg = await this.CreateCertificate(
false, Status, (
_, __) => Task.CompletedTask);
996 if (!
string.IsNullOrEmpty(Msg))
998 this.domain = DomainBak;
999 this.useDomainName = UseDomainBak;
1000 this.alternativeDomains = AlternativeBak;
1005 this.Updated = DateTime.Now;
1017 public async Task<string>
RemoveDomain(
string Name, EventHandlerAsync<string> Status)
1020 return "Domain not registered.";
1022 string DomainBak = this.domain;
1023 bool UseDomainBak = this.useDomainName;
1024 string[] AlternativeBak = this.alternativeDomains;
1027 if (
string.Compare(this.domain, Name,
true) == 0)
1029 if ((this.alternativeDomains?.Length ?? 0) == 0)
1031 this.domain =
string.Empty;
1032 this.useDomainName =
false;
1036 this.domain = this.alternativeDomains[0];
1038 int c = this.alternativeDomains.Length;
1041 this.alternativeDomains =
null;
1044 NewArray =
new string[c - 1];
1045 Array.Copy(this.alternativeDomains, 1, NewArray, 0, c - 1);
1046 this.alternativeDomains = NewArray;
1050 else if (!(this.alternativeDomains is
null))
1052 int c = this.alternativeDomains.Length;
1054 this.alternativeDomains =
null;
1057 int i = Array.IndexOf(this.alternativeDomains, Name);
1059 NewArray =
new string[c - 1];
1062 Array.Copy(this.alternativeDomains, 0, NewArray, 0, i);
1065 Array.Copy(this.alternativeDomains, i + 1, NewArray, i, c - i - 1);
1067 this.alternativeDomains = NewArray;
1071 if (this.useDomainName)
1073 string Msg = await this.CreateCertificate(
false, Status, (
_, __) => Task.CompletedTask);
1074 if (!
string.IsNullOrEmpty(Msg))
1076 this.domain = DomainBak;
1077 this.useDomainName = UseDomainBak;
1078 this.alternativeDomains = AlternativeBak;
1084 this.Updated = DateTime.Now;
1097 if (
string.Compare(Name, this.domain,
true) == 0)
1100 if (this.alternativeDomains is
null)
1103 foreach (
string Alternative
in this.alternativeDomains)
1105 if (
string.Compare(Name, Alternative,
true) == 0)
1112 internal Task<bool> CreateCertificate()
1114 return this.CreateCertificate(
null,
false);
1117 private async Task<bool> CreateCertificate(
string TabID,
bool EnvironmentSetup)
1121 string Msg = await this.CreateCertificate(EnvironmentSetup,
1122 async (
_, Status) =>
1124 if (!
string.IsNullOrEmpty(TabID))
1129 if (!
string.IsNullOrEmpty(TabID))
1130 await ClientEvents.PushEvent(
new string[] { TabID },
"TermsOfService", URL,
false,
"User");
1133 if (
string.IsNullOrEmpty(Msg))
1135 if (!
string.IsNullOrEmpty(TabID))
1136 await ClientEvents.PushEvent(
new string[] { TabID },
"CertificateOk",
string.Empty,
false,
"User");
1142 if (!
string.IsNullOrEmpty(TabID))
1143 await ClientEvents.PushEvent(
new string[] { TabID },
"CertificateError", Msg,
false,
"User");
1148 catch (Exception ex)
1150 if (!
string.IsNullOrEmpty(TabID))
1151 await ClientEvents.PushEvent(
new string[] { TabID },
"CertificateError", ex.Message,
false,
"User");
1153 if (EnvironmentSetup)
1154 this.LogEnvironmentError(ex.Message, GATEWAY_ENCRYPTION,
this.useEncryption);
1160 internal async Task<string> CreateCertificate(
bool EnvironmentSetup, EventHandlerAsync<string> Status,
1161 EventHandlerAsync<string> TermsOfService)
1165 string URL = this.customCA ? this.acmeDirectory :
"https://acme-v02.api.letsencrypt.org/directory";
1166 RSAParameters Parameters;
1167 CspParameters CspParams =
new CspParameters()
1169 Flags = CspProviderFlags.UseMachineKeyStore,
1170 KeyContainerName =
"IoTGateway:" + URL
1177 using (RSACryptoServiceProvider RSA =
new RSACryptoServiceProvider(4096, CspParams))
1179 Parameters = RSA.ExportParameters(
true);
1181 if (RSA.KeySize < 4096)
1183 RSA.PersistKeyInCsp =
false;
1193 using RSACryptoServiceProvider RSA =
new RSACryptoServiceProvider(4096, CspParams);
1194 Parameters = RSA.ExportParameters(
true);
1197 catch (CryptographicException ex)
1199 throw new CryptographicException(
"Unable to get access to cryptographic key for \"IoTGateway:" + URL +
1200 "\". Was the database created using another user?", ex);
1204 await Status.Raise(
this,
"Connecting to directory.");
1209 await Status.Raise(
this,
"An external account is required.");
1214 await Status.Raise(
this,
"Terms of service available on: " + URL);
1215 await TermsOfService.Raise(
this, URL);
1219 if (!this.acceptToS)
1221 string Msg =
"You need to accept the terms of service.";
1223 if (EnvironmentSetup)
1224 this.LogEnvironmentError(Msg, GATEWAY_ACME_ACCEPT_TOS, this.acceptToS);
1233 await Status.Raise(
this,
"Getting account.");
1235 List<string>
Names =
new List<string>();
1237 if (!
string.IsNullOrEmpty(this.domain))
1238 Names.Add(this.domain);
1240 if (!(this.alternativeDomains is
null))
1242 foreach (
string Name
in this.alternativeDomains)
1244 if (!
Names.Contains(Name))
1248 string[] DomainNames =
Names.ToArray();
1254 Account = await Client.GetAccount();
1256 await Status.Raise(
this,
"Account found.");
1257 await Status.Raise(
this,
"Created: " + Account.
CreatedAt.ToString());
1258 await Status.Raise(
this,
"Initial IP: " + Account.
InitialIp);
1259 await Status.Raise(
this,
"Status: " + Account.
Status.ToString());
1261 if (
string.IsNullOrEmpty(this.contactEMail))
1265 await Status.Raise(
this,
"Updating contact URIs in account.");
1266 Account = await Account.
Update(Array.Empty<
string>());
1267 await Status.Raise(
this,
"Account updated.");
1272 if (Account.
Contact is
null || Account.
Contact.Length != 1 || Account.
Contact[0] !=
"mailto:" +
this.contactEMail)
1274 await Status.Raise(
this,
"Updating contact URIs in account.");
1275 Account = await Account.
Update(
new string[] {
"mailto:" + this.contactEMail });
1276 await Status.Raise(
this,
"Account updated.");
1282 await Status.Raise(
this,
"Account not found.");
1283 await Status.Raise(
this,
"Creating account.");
1285 Account = await Client.CreateAccount(
string.IsNullOrEmpty(this.contactEMail) ? Array.Empty<
string>() :
new string[] {
"mailto:" + this.contactEMail },
1288 await Status.Raise(
this,
"Account created.");
1289 await Status.Raise(
this,
"Status: " + Account.
Status.ToString());
1292 await Status.Raise(
this,
"Generating new key.");
1295 using (RSACryptoServiceProvider RSA =
new RSACryptoServiceProvider(4096, CspParams))
1297 RSA.ImportParameters(Client.ExportAccountKey(
true));
1300 await Status.Raise(
this,
"New key generated.");
1302 await Status.Raise(
this,
"Creating order.");
1311 await Task.Delay(5000);
1312 await Status.Raise(
this,
"Retrying.");
1316 await Status.Raise(
this,
"Order created.");
1320 await Status.Raise(
this,
"Getting authorizations.");
1327 await Task.Delay(5000);
1328 await Status.Raise(
this,
"Retrying.");
1334 await Status.Raise(
this,
"Processing authorization for " + Authorization.
Value);
1337 bool Acknowledged =
false;
1339 int NrChallenges = Authorization.
Challenges.Length;
1341 for (Index = 1; Index <= NrChallenges; Index++)
1343 Challenge = Authorization.
Challenges[Index - 1];
1347 this.challenge =
"/" + HttpChallenge.
Token;
1348 this.token = HttpChallenge.KeyAuthorization;
1350 await Status.Raise(
this,
"Acknowleding challenge.");
1352 string Msg = await this.CheckDynamicIp(Authorization.
Value, EnvironmentSetup, Status);
1353 if (
string.IsNullOrEmpty(Msg))
1356 await Status.Raise(
this,
"Challenge acknowledged: " + Challenge.
Status.ToString());
1358 Acknowledged =
true;
1367 string Msg =
"No automated method found to respond to any of the authorization challenges.";
1369 if (EnvironmentSetup)
1370 this.LogEnvironmentError(Msg, GATEWAY_ENCRYPTION, this.useEncryption);
1379 await Status.Raise(
this,
"Waiting to poll authorization status.");
1380 await Task.Delay(5000);
1382 await Status.Raise(
this,
"Polling authorization.");
1383 Authorization2 = await Authorization2.
Poll();
1385 await Status.Raise(
this,
"Authorization polled: " + Authorization2.
Status.ToString());
1391 throw Authorization2.Status
switch
1393 AcmeAuthorizationStatus.deactivated =>
new Exception(
"Authorization deactivated."),
1394 AcmeAuthorizationStatus.expired =>
new Exception(
"Authorization expired."),
1395 AcmeAuthorizationStatus.invalid =>
new Exception(
"Authorization invalid."),
1396 AcmeAuthorizationStatus.revoked =>
new Exception(
"Authorization revoked."),
1397 _ =>
new Exception(
"Authorization not validated."),
1402 using (RSACryptoServiceProvider RSA =
new RSACryptoServiceProvider(4096))
1404 await Status.Raise(
this,
"Finalizing order.");
1408 Order = await Order.
FinalizeOrder(
new Security.PKCS.CertificateRequest(SignAlg)
1410 CommonName = this.domain,
1411 SubjectAlternativeNames = DomainNames,
1412 EMailAddress = this.contactEMail
1415 await Status.Raise(
this,
"Order finalized: " + Order.
Status.ToString());
1419 throw Order.Status
switch
1421 AcmeOrderStatus.invalid =>
new Exception(
"Order invalid."),
1422 _ =>
new Exception(
"Unable to validate order."),
1427 throw new Exception(
"No certificate URI provided.");
1429 await Status.Raise(
this,
"Downloading certificate.");
1432 X509Certificate2 Certificate = Certificates[0];
1434 await Status.Raise(
this,
"Exporting certificate.");
1436 this.certificate = Certificate.Export(X509ContentType.Cert);
1437 this.privateKey = RSA.ExportCspBlob(
true);
1439 this.password =
string.Empty;
1441 await Status.Raise(
this,
"Adding private key.");
1445 Certificate.PrivateKey = RSA;
1447 catch (PlatformNotSupportedException)
1449 await Status.Raise(
this,
"Platform does not support adding of private key.");
1450 await Status.Raise(
this,
"Searching for OpenSSL on machine.");
1454 string CertFileName =
null;
1455 string CertFileName2 =
null;
1456 string KeyFileName =
null;
1458 if (
string.IsNullOrEmpty(this.openSslPath) || !File.Exists(
this.openSslPath))
1460 string[] Folders = Gateway.GetFolders(
new Environment.SpecialFolder[]
1462 Environment.SpecialFolder.ProgramFiles,
1463 Environment.SpecialFolder.ProgramFilesX86
1465 Path.DirectorySeparatorChar +
"OpenSSL-Win32",
1466 Path.DirectorySeparatorChar +
"OpenSSL-Win64");
1468 Files = Gateway.FindFiles(Folders,
"openssl.exe", 2,
int.MaxValue);
1471 Files =
new string[] { this.openSslPath };
1475 if (Files.Length == 0)
1477 string Msg =
"Unable to join certificate with private key. Try installing <a target=\"_blank\" href=\"https://wiki.openssl.org/index.php/Binaries\">OpenSSL</a> and try again.";
1479 if (EnvironmentSetup)
1480 this.LogEnvironmentError(Msg, GATEWAY_ENCRYPTION, this.useEncryption);
1486 foreach (
string OpenSslFile
in Files)
1488 if (CertFileName is
null)
1490 await Status.Raise(
this,
"Generating temporary certificate file.");
1492 StringBuilder PemOutput =
new StringBuilder();
1493 byte[] Bin = Certificate.Export(X509ContentType.Cert);
1495 PemOutput.AppendLine(
"-----BEGIN CERTIFICATE-----");
1496 PemOutput.AppendLine(Convert.ToBase64String(Bin, Base64FormattingOptions.InsertLineBreaks));
1497 PemOutput.AppendLine(
"-----END CERTIFICATE-----");
1499 CertFileName = Path.Combine(Gateway.AppDataFolder,
"Certificate.pem");
1500 await Runtime.IO.Files.WriteAllTextAsync(CertFileName, PemOutput.ToString(),
System.Text.Encoding.ASCII);
1502 await Status.Raise(
this,
"Generating temporary key file.");
1508 PemOutput.AppendLine(
"-----BEGIN RSA PRIVATE KEY-----");
1509 PemOutput.AppendLine(Convert.ToBase64String(KeyOutput.
ToArray(), Base64FormattingOptions.InsertLineBreaks));
1510 PemOutput.AppendLine(
"-----END RSA PRIVATE KEY-----");
1512 KeyFileName = Path.Combine(Gateway.AppDataFolder,
"Certificate.key");
1514 await Runtime.IO.Files.WriteAllTextAsync(KeyFileName, PemOutput.ToString(),
System.Text.Encoding.ASCII);
1517 await Status.Raise(
this,
"Converting to PFX using " + OpenSslFile);
1519 Process P =
new Process()
1521 StartInfo =
new ProcessStartInfo()
1523 FileName = OpenSslFile,
1524 Arguments =
"pkcs12 -nodes -export -out Certificate.pfx -inkey Certificate.key -in Certificate.pem -password pass:" + Password,
1525 UseShellExecute =
false,
1526 RedirectStandardError =
true,
1527 RedirectStandardOutput =
true,
1528 WorkingDirectory = Gateway.AppDataFolder,
1529 CreateNoWindow =
true,
1530 WindowStyle = ProcessWindowStyle.Hidden
1536 if (!P.WaitForExit(60000) || P.ExitCode != 0)
1538 if (!P.StandardOutput.EndOfStream)
1539 await Status.Raise(
this,
"Output: " + P.StandardOutput.ReadToEnd());
1541 if (!P.StandardError.EndOfStream)
1542 await Status.Raise(
this,
"Error: " + P.StandardError.ReadToEnd());
1547 await Status.Raise(
this,
"Loading PFX.");
1549 CertFileName2 = Path.Combine(Gateway.AppDataFolder,
"Certificate.pfx");
1550 this.pfx = await Runtime.IO.Files.ReadAllBytesAsync(CertFileName2);
1551 this.password = Password;
1552 this.openSslPath = OpenSslFile;
1554 await Status.Raise(
this,
"PFX successfully generated using OpenSSL.");
1558 if (this.pfx is
null)
1560 this.openSslPath =
string.Empty;
1562 string Msg =
"Unable to convert to PFX using OpenSSL.";
1564 if (EnvironmentSetup)
1565 this.LogEnvironmentError(Msg, GATEWAY_ENCRYPTION, this.useEncryption);
1573 if (!(CertFileName is
null) && File.Exists(CertFileName))
1575 await Status.Raise(
this,
"Deleting temporary certificate file.");
1576 File.Delete(CertFileName);
1579 if (!(KeyFileName is
null) && File.Exists(KeyFileName))
1581 await Status.Raise(
this,
"Deleting temporary key file.");
1582 File.Delete(KeyFileName);
1585 if (!(CertFileName2 is
null) && File.Exists(CertFileName2))
1587 await Status.Raise(
this,
"Deleting temporary pfx file.");
1588 File.Delete(CertFileName2);
1597 this.Updated = DateTime.Now;
1600 await Gateway.UpdateCertificate(
this);
1605 catch (Exception ex)
1611 if (EnvironmentSetup)
1612 this.LogEnvironmentError(Msg, GATEWAY_ENCRYPTION, this.useEncryption);
1618 this.inProgress =
false;
1624 if (Request.
SubPath !=
this.challenge)
1630 Response.StatusCode = 200;
1631 Response.StatusMessage =
"OK";
1634 await Response.
Write(
true,
System.Text.Encoding.ASCII.GetBytes(
this.token));
1639 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
1648 if (!(Content.
Decoded is Dictionary<string, object> Parameters))
1654 if (!Parameters.TryGetValue(
"humanReadableName", out
object Obj) ||
1655 !(Obj is
string HumanReadableName) ||
1656 !Parameters.TryGetValue(
"humanReadableNameLanguage", out Obj) ||
1657 !(Obj is
string HumanReadableNameLanguage))
1663 List<AlternativeField> LocalizedNames =
new List<AlternativeField>();
1666 while (Parameters.TryGetValue(
"nameLanguage" + Index.ToString(), out Obj) &&
1667 Obj is
string NameLanguage &&
1668 Parameters.TryGetValue(
"nameLocalized" + Index.ToString(), out Obj) &&
1669 Obj is
string NameLocalized)
1671 if (
string.IsNullOrEmpty(NameLanguage))
1677 if (
string.IsNullOrEmpty(NameLocalized))
1683 LocalizedNames.Add(
new AlternativeField(NameLanguage, NameLocalized));
1687 this.HumanReadableName = HumanReadableName;
1688 this.HumanReadableNameLanguage = HumanReadableNameLanguage;
1689 this.LocalizedNames = LocalizedNames.ToArray();
1691 this.Updated = DateTime.Now;
1697 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
1706 if (Content.
HasError || !(Content.
Decoded is Dictionary<string, object> Parameters))
1712 if (!Parameters.TryGetValue(
"humanReadableDescription", out
object Obj) ||
1713 !(Obj is
string HumanReadableDescription) ||
1714 !Parameters.TryGetValue(
"humanReadableDescriptionLanguage", out Obj) ||
1715 !(Obj is
string HumanReadableDescriptionLanguage))
1721 List<AlternativeField> LocalizedDescriptions =
new List<AlternativeField>();
1724 while (Parameters.TryGetValue(
"descriptionLanguage" + Index.ToString(), out Obj) &&
1725 Obj is
string DescriptionLanguage &&
1726 Parameters.TryGetValue(
"descriptionLocalized" + Index.ToString(), out Obj) &&
1727 Obj is
string DescriptionLocalized)
1729 if (
string.IsNullOrEmpty(DescriptionLanguage))
1735 if (
string.IsNullOrEmpty(DescriptionLocalized))
1741 LocalizedDescriptions.Add(
new AlternativeField(DescriptionLanguage, DescriptionLocalized));
1745 this.HumanReadableDescription = HumanReadableDescription;
1746 this.HumanReadableDescriptionLanguage = HumanReadableDescriptionLanguage;
1747 this.LocalizedDescriptions = LocalizedDescriptions.ToArray();
1749 this.Updated = DateTime.Now;
1759 return Task.FromResult(
true);
1765 public const string GATEWAY_DOMAIN_USE = nameof(GATEWAY_DOMAIN_USE);
1770 public const string GATEWAY_DOMAIN_NAME = nameof(GATEWAY_DOMAIN_NAME);
1777 public const string GATEWAY_DOMAIN_ALT = nameof(GATEWAY_DOMAIN_ALT);
1782 public const string GATEWAY_DYNDNS = nameof(GATEWAY_DYNDNS);
1787 public const string GATEWAY_ENCRYPTION = nameof(GATEWAY_ENCRYPTION);
1792 public const string GATEWAY_CA_CUSTOM = nameof(GATEWAY_CA_CUSTOM);
1797 public const string GATEWAY_ACME_EMAIL = nameof(GATEWAY_ACME_EMAIL);
1802 public const string GATEWAY_ACME_ACCEPT_TOS = nameof(GATEWAY_ACME_ACCEPT_TOS);
1807 public const string GATEWAY_HR_NAME = nameof(GATEWAY_HR_NAME);
1812 public const string GATEWAY_HR_NAME_LANG = nameof(GATEWAY_HR_NAME_LANG);
1817 public const string GATEWAY_HR_DESC = nameof(GATEWAY_HR_DESC);
1822 public const string GATEWAY_HR_DESC_LANG = nameof(GATEWAY_HR_DESC_LANG);
1827 public const string GATEWAY_HR_NAME_LOC = nameof(GATEWAY_HR_NAME_LOC);
1832 public const string GATEWAY_HR_NAME_ = nameof(GATEWAY_HR_NAME_);
1837 public const string GATEWAY_HR_DESC_LOC = nameof(GATEWAY_HR_DESC_LOC);
1842 public const string GATEWAY_HR_DESC_ = nameof(GATEWAY_HR_DESC_);
1849 public const string GATEWAY_DYNDNS_TEMPLATE = nameof(GATEWAY_DYNDNS_TEMPLATE);
1854 public const string GATEWAY_DYNDNS_CHECK = nameof(GATEWAY_DYNDNS_CHECK);
1859 public const string GATEWAY_DYNDNS_UPDATE = nameof(GATEWAY_DYNDNS_UPDATE);
1864 public const string GATEWAY_DYNDNS_ACCOUNT = nameof(GATEWAY_DYNDNS_ACCOUNT);
1869 public const string GATEWAY_DYNDNS_PASSWORD = nameof(GATEWAY_DYNDNS_PASSWORD);
1874 public const string GATEWAY_DYNDNS_INTERVAL = nameof(GATEWAY_DYNDNS_INTERVAL);
1881 public const string GATEWAY_ACME_DIRECTORY = nameof(GATEWAY_ACME_DIRECTORY);
1889 if (!this.TryGetEnvironmentVariable(GATEWAY_DOMAIN_USE,
false, out this.useDomainName))
1892 if (!this.useDomainName)
1894 this.Domain =
string.Empty;
1895 this.DynamicDns =
false;
1896 this.UseEncryption =
false;
1897 this.CustomCA =
false;
1898 this.ContactEMail =
string.Empty;
1899 this.AcceptToS =
false;
1903 if (!this.TryGetEnvironmentVariable(GATEWAY_DOMAIN_NAME,
true, out
string Value) ||
1904 string.IsNullOrEmpty(Value))
1906 return !this.useDomainName;
1909 if (
string.Compare(Value,
"localhost",
true) == 0)
1911 this.LogEnvironmentError(
"localhost is not a valid domain name.", GATEWAY_DOMAIN_NAME, Value);
1915 this.Domain = Value;
1917 Value = Environment.GetEnvironmentVariable(GATEWAY_DOMAIN_ALT);
1918 if (
string.IsNullOrEmpty(Value))
1919 this.AlternativeDomains =
null;
1922 string[] Parts = Value.Split(
',');
1924 foreach (
string Part
in Parts)
1926 if (
string.Compare(Part,
"localhost",
true) == 0)
1928 this.LogEnvironmentError(
"localhost is not a valid alternative domain name.", GATEWAY_DOMAIN_ALT, Value);
1933 this.AlternativeDomains = Parts;
1936 if (!this.TryGetEnvironmentVariable(GATEWAY_DYNDNS, out this.dynamicDns,
false))
1939 if (this.dynamicDns)
1941 if (!this.TryGetEnvironmentVariable(GATEWAY_DYNDNS_TEMPLATE,
true, out Value))
1944 this.DynDnsTemplate = Value;
1946 if (!this.TryGetEnvironmentVariable(GATEWAY_DYNDNS_CHECK,
true, out Value))
1949 this.CheckIpScript = Value;
1951 if (!this.TryGetEnvironmentVariable(GATEWAY_DYNDNS_UPDATE,
true, out Value))
1954 this.UpdateIpScript = Value;
1956 this.TryGetEnvironmentVariable(GATEWAY_DYNDNS_ACCOUNT,
false, out this.dynDnsAccount);
1957 this.TryGetEnvironmentVariable(GATEWAY_DYNDNS_PASSWORD,
false, out this.dynDnsPassword);
1959 if (!this.TryGetEnvironmentVariable(GATEWAY_DYNDNS_UPDATE, 60, 86400,
true, ref this.dynDnsInterval))
1963 if (!this.TryGetEnvironmentVariable(GATEWAY_ENCRYPTION,
true, out this.useEncryption))
1966 if (this.useEncryption)
1968 if (!this.TryGetEnvironmentVariable(GATEWAY_CA_CUSTOM, out this.customCA,
false))
1973 if (!this.TryGetEnvironmentVariable(GATEWAY_ACME_DIRECTORY,
true, out this.acmeDirectory))
1977 if (!this.TryGetEnvironmentVariable(GATEWAY_ACME_EMAIL,
true, out this.contactEMail))
1980 if (!this.TryGetEnvironmentVariable(GATEWAY_ACME_ACCEPT_TOS,
true, out this.acceptToS))
1985 AlternativeField LocalizedString = this.GetLocalizedEnvironmentVariable(GATEWAY_HR_NAME, GATEWAY_HR_NAME_LANG);
1986 if (LocalizedString is
null)
1989 this.HumanReadableName = LocalizedString.
Value;
1990 this.HumanReadableNameLanguage = LocalizedString.
Key;
1992 LocalizedString = this.GetLocalizedEnvironmentVariable(GATEWAY_HR_DESC, GATEWAY_HR_DESC_LANG);
1993 if (LocalizedString is
null)
1996 this.HumanReadableDescription = LocalizedString.
Value;
1997 this.HumanReadableDescriptionLanguage = LocalizedString.
Key;
1999 this.localizedNames = this.GetLocalizedEnvironmentVariables(GATEWAY_HR_NAME_LOC, GATEWAY_HR_NAME_);
2000 if (this.localizedNames is
null)
2003 this.localizedDescriptions = this.GetLocalizedEnvironmentVariables(GATEWAY_HR_DESC_LOC, GATEWAY_HR_DESC_);
2004 if (this.localizedDescriptions is
null)
2008 this.deferredTest =
true;
2011 if (!await this.Test(
null,
true))
2015 if (this.dynamicDns && !await this.CheckDynamicIp(
true))
2018 if (this.useEncryption && this.useDomainName && !await this.CreateCertificate(
null,
true))
2024 private AlternativeField GetLocalizedEnvironmentVariable(
string VariableName,
string LanguageVariableName)
2026 if (!this.TryGetEnvironmentVariable(VariableName,
true, out
string Value) ||
2027 !this.TryGetEnvironmentVariable(LanguageVariableName,
true, out
string Language))
2032 return new AlternativeField(Value,
Language);
2035 private AlternativeField[] GetLocalizedEnvironmentVariables(
string CollectionVariableName,
string VariableName)
2037 List<AlternativeField> Result =
new List<AlternativeField>();
2039 string Value = Environment.GetEnvironmentVariable(CollectionVariableName);
2040 if (!
string.IsNullOrEmpty(Value))
2042 string[] Languages = Value.Split(
',');
2045 foreach (
string Language in Languages)
2049 if (!this.TryGetEnvironmentVariable(Name,
true, out Value))
2052 Result.Add(
new AlternativeField(
Language, Value));
2056 return Result.ToArray();
2068 if (this.deferredTest)
2070 this.deferredTest =
false;
2071 await this.Test(
null,
true);
const string DefaultContentType
text/plain
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Plain text encoder/decoder.
const string DefaultContentType
text/plain
Helps with common XML-related tasks.
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
Static class managing the application event log. Applications and services log events on this static ...
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
The ClientEvents class allows applications to push information asynchronously to web clients connecte...
static Task< int > PushEvent(string[] TabIDs, string Type, object Data)
Puses an event to a set of Tabs, given their Tab IDs.
Static class managing the runtime environment of the IoT Gateway.
static HttpServer HttpServer
HTTP Server
static IUser AssertUserAuthenticated(HttpRequest Request, string Privilege)
Makes sure a request is being made from a session with a successful user login.
static byte[] NextBytes(int NrBytes)
Generates an array of random bytes.
static int[] GetConfigPorts(string Protocol)
Gets the port numbers defined for a given protocol in the configuration file.
Represents an alternative field in a legal identity.
string Value
Alternative field Value.
string Key
Alternative field name.
string UpdateIpScript
Script to use to update the current IP Address.
string DynDnsTemplate
Dynamic DNS Template
AlternativeField[] LocalizedDescriptions
Localized descriptions of domain
static DomainConfiguration Instance
Current instance of configuration.
string Password
Password for PFX file, if any.
const string GATEWAY_DOMAIN_NAME
Main Domain Name of the gateway, if defined. If not provided, the gateway will not use a domain name.
string HumanReadableDescription
Human-readable description of domain
async Task< string > Test(string DomainName, bool EnvironmentSetup, EventHandlerAsync< string > Status)
Checks if the domain points to the server.
string[] EncryptedProperties
Array of properties that are encrypted.
string AcmeDirectory
If a custom Certificate Authority is to be used, this property holds the URL to their ACME directory.
override Task ConfigureSystem()
Is called during startup to configure the system.
async Task< string > AddDomain(string Name, EventHandlerAsync< string > Status)
Adds a domain to the list of domains supported by the gateway.
override void SetStaticInstance(ISystemConfiguration Configuration)
Sets the static instance of the configuration.
string HumanReadableDescriptionLanguage
Language of HumanReadableDescription.
override string ConfigPrivilege
Minimum required privilege for a user to be allowed to change the configuration defined by the class.
string HumanReadableName
Human-readable name of domain
bool CustomCA
If a custom Certificate Authority is to be used
override Task UnregisterSetup(HttpServer WebServer)
Unregisters the setup object.
string OpenSslPath
Path to OpenSSL
AlternativeField[] LocalizedNames
Localized names of domain
byte[] PrivateKey
Private Key
int DynDnsInterval
Interval (in seconds) for checking if the IP address has changed.
string[] AlternativeDomains
Alternative domain names
override async Task DeferredConfiguration(HttpServer WebServer)
Performs a deferred configuration. Deferred configurations are such that could not be performed durin...
bool IsDomainRegistered(string Name)
Checks if a domain name is registered.
string CheckIpScript
Script to use to evaluate the current IP Address.
async Task< string > CheckDynamicIp(string DomainName, bool EnvironmentSetup, EventHandlerAsync< string > Status)
Checks if Dynamic IP configuration is correct
byte[] PFX
PFX container for certificate and private key, if available.
string ContactEMail
Contact e-mail address
override Task< bool > SimplifiedConfiguration()
Simplified configuration by configuring simple default values.
byte[] Certificate
Certificate
override async Task< bool > EnvironmentConfiguration()
Environment configuration by configuring values available in environment variables.
string UrlToS
CA Terms of Service
bool AcceptToS
If the CA Terms of Service has been accepted.
override int Priority
Priority of the setting. Configurations are sorted in ascending order.
async Task< string > RemoveDomain(string Name, EventHandlerAsync< string > Status)
Removes a domain from the list of domains supported by the gateway.
bool UseDomainName
If the server uses a domain name.
const string GATEWAY_DYNDNS_UPDATE
Script to use to update the current public IP address of the gateway in the Dynamic DNS service.
string DynDnsAccount
Account Name for the Dynamic DNS service
override Task InitSetup(HttpServer WebServer)
Initializes the setup object.
bool DynamicDns
If the server uses a dynamic DNS service.
bool UseEncryption
If the server uses server-side encryption.
override Task< string > Title(Language Language)
Gets a title for the system configuration.
string DynDnsPassword
Password for the Dynamic DNS service
const string GATEWAY_DOMAIN_ALT
Comma-separated list of alternative domain names for the gateway, if defined.
bool HasToS
If the CA has a Terms of Service.
string Domain
Principal domain name
string HumanReadableNameLanguage
Language of HumanReadableName.
const string GATEWAY_DYNDNS_CHECK
Script to use to check the current public IP address of the gateway.
bool HasCertificate
If the configuration has a certificate.
void LogEnvironmentError(string EnvironmentVariable, object Value)
Logs an error to the event log, telling the operator an environment variable value contains an error.
Abstract base class for multi-step system configurations.
int Step
Configuration step.
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Represents an HTTP request.
HttpRequestHeader Header
Request header.
bool HasData
If the request has data.
string SubPath
Sub-path. If a resource is found handling the request, this property contains the trailing sub-path o...
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Base class for all HTTP resources.
Represets a response of an HTTP client request.
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
Task Write(byte[] Data)
Returns binary data in the response.
Implements an HTTP server.
static SessionVariables CreateSessionVariables()
Creates a new collection of variables, that contains access to the global set of variables.
HttpResource Register(HttpResource Resource)
Registers a resource with the server.
bool Unregister(HttpResource Resource)
Unregisters a resource from the server.
The server has not found anything matching the Request-URI. No indication is given of whether the con...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
static async Task Update(object Object)
Updates an object in the database.
Contains information about a language.
Task< string > GetStringAsync(Type Type, int Id, string Default)
Gets the string value of a string ID. If no such string exists, a string is created with the default ...
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
static async Task< bool > SetAsync(string Key, string Value)
Sets a string-valued setting.
Class managing a script expression.
The request specified an account that does not exist
Represents an ACME account.
Task< AcmeAccount > NewKey()
Creates a new key for the account.
Task< AcmeOrder > OrderCertificate(string[] Domains, DateTime? NotBefore, DateTime? NotAfter)
Orders certificate.
string[] Contact
Optional array of URLs that the server can use to contact the client for issues related to this accou...
DateTime? CreatedAt
Date and time of creation, if available.
Task< AcmeAccount > Update(string[] Contact)
Updates the account.
string InitialIp
Initial IP address.
AcmeAccountStatus Status
The status of this account.
Represents an ACME authorization.
Task< AcmeAuthorization > Poll()
Gets the current state of the order.
string Value
The identifier itself.
AcmeChallenge[] Challenges
For pending authorizations, the challenges that the client can fulfill in order to prove possession o...
AcmeAuthorizationStatus Status
The status of this authorization.
Base class of all ACME challenges.
AcmeChallengeStatus Status
The status of this challenge.
Task< AcmeChallenge > AcknowledgeChallenge()
Acknowledges the challenge.
Implements an ACME client for the generation of certificates using ACME-compliant certificate servers...
Represents an ACME directory.
Uri TermsOfService
URL to terms of service.
Uri Website
URL to website.
bool ExternalAccountRequired
If an external account is required.
Represents an ACME HTTP challenge.
Represents an ACME order.
AcmeOrderStatus Status
The status of this order.
Uri Certificate
A URL for the certificate that has been issued in response to this order.
async Task< AcmeAuthorization[]> GetAuthorizations()
Gets current authorization objects.
Task< X509Certificate2[]> DownloadCertificate()
Downloads the certificate.
Task< AcmeOrder > FinalizeOrder(CertificateRequest CertificateRequest)
Finalize order.
Contains methods for simple hash calculations.
static string BinaryToString(byte[] Data)
Converts an array of bytes to a string with their hexadecimal representations (in lower case).
Encodes data using the Distinguished Encoding Rules (DER), as defined in X.690
byte[] ToArray()
Converts the generated output to a byte arary.
RSA with SHA-256 signatures
Abstract base class for signature algorithms
abstract void ExportPrivateKey(DerEncoder Output)
Exports the private key using DER.
Interface for system configurations. The gateway will scan all module for system configuration classe...
Interface for objects containing encrypted properties. Mark the properties that are encrypted with th...
class Names(Vector NamesVector)
Contains a collection of distinguished names.
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.
AcmeOrderStatus
ACME Order status enumeration
AcmeAuthorizationStatus
ACME Authorization status enumeration