Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
AcmeClient.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Net;
5using System.Net.Http;
6using System.Reflection;
7using System.Security.Authentication;
8using System.Security.Cryptography;
9using System.Security.Cryptography.X509Certificates;
10using System.Text;
11using System.Text.RegularExpressions;
12using System.Threading.Tasks;
13using Waher.Content;
17
18namespace Waher.Security.ACME
19{
23 public class AcmeClient : IDisposable
24 {
25 private const int KeySize = 4096;
26
27 private readonly Uri directoryEndpoint;
28 private HttpClient httpClient;
29 private AcmeDirectory directory = null;
30 private RsaSsaPkcsSha256 jws;
31 private string nonce = null;
32 private string jwkThumbprint = null;
33
39 public AcmeClient(Uri DirectoryEndpoint, RSAParameters Parameters)
40 {
41 this.directoryEndpoint = DirectoryEndpoint;
42 this.jws = new RsaSsaPkcsSha256(Parameters);
43
44 try
45 {
46 this.httpClient = new HttpClient(new HttpClientHandler()
47 {
48 AllowAutoRedirect = true,
49 AutomaticDecompression = (DecompressionMethods)(-1), // All
50 CheckCertificateRevocationList = true,
51 SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12
52 }, true);
53 }
54 catch (PlatformNotSupportedException)
55 {
56 this.httpClient = new HttpClient(new HttpClientHandler()
57 {
58 AllowAutoRedirect = true
59 }, true);
60 }
61
62 Type T = typeof(AcmeClient);
63 Version Version = T.GetTypeInfo().Assembly.GetName().Version;
64 StringBuilder UserAgent = new StringBuilder();
65
66 UserAgent.Append(T.Namespace);
67 UserAgent.Append('/');
68 UserAgent.Append(Version.Major.ToString());
69 UserAgent.Append('.');
70 UserAgent.Append(Version.Minor.ToString());
71 UserAgent.Append('.');
72 UserAgent.Append(Version.Build.ToString());
73
74 this.httpClient.DefaultRequestHeaders.Add("User-Agent", UserAgent.ToString());
75 this.httpClient.DefaultRequestHeaders.Add("Accept", JwsAlgorithm.JwsContentType);
76 this.httpClient.DefaultRequestHeaders.Add("Accept-Language", "en");
77 }
78
82 public void Dispose()
83 {
84 if (!(this.httpClient is null))
85 {
86 this.httpClient.Dispose();
87 this.httpClient = null;
88 }
89
90 if (!(this.jws is null))
91 {
92 this.jws.Dispose();
93 this.jws = null;
94 }
95 }
96
101 public async Task<AcmeDirectory> GetDirectory()
102 {
103 if (this.directory is null)
104 this.directory = new AcmeDirectory(this, (await this.GET(this.directoryEndpoint)).Payload);
105
106 return this.directory;
107 }
108
109 internal Task<AcmeResponse> POST_as_GET(Uri URL, Uri AccountLocation)
110 {
111 return this.POST(URL, AccountLocation, null);
112 }
113
114 internal async Task<AcmeResponse> GET(Uri URL)
115 {
116 HttpResponseMessage Response = await this.httpClient.GetAsync(URL);
117
118 byte[] Bin = await Response.Content.ReadAsByteArrayAsync();
119 string CharSet = Response.Content.Headers.ContentType?.CharSet;
120 Encoding Encoding;
121
122 if (string.IsNullOrEmpty(CharSet))
123 Encoding = Encoding.UTF8;
124 else
125 Encoding = InternetContent.GetEncoding(CharSet);
126
127 string JsonResponse = Encoding.GetString(Bin);
128
129 if (!(JSON.Parse(JsonResponse) is IEnumerable<KeyValuePair<string, object>> Obj))
130 throw new Exception("Unexpected response returned.");
131
132 if (Response.Content.Headers.TryGetValues("Retry-After", out IEnumerable<string> _))
133 {
134 // TODO: Rate limit
135 }
136
137 if (Response.IsSuccessStatusCode)
138 {
139 return new AcmeResponse()
140 {
141 Payload = Obj,
142 Location = URL,
143 Json = JsonResponse,
144 ResponseMessage = Response
145 };
146 }
147 else
148 throw CreateException(Obj, Response);
149 }
150
151 internal async Task<string> NextNonce()
152 {
153 if (!string.IsNullOrEmpty(this.nonce))
154 {
155 string s = this.nonce;
156 this.nonce = null;
157 return s;
158 }
159
160 if (this.directory is null)
161 await this.GetDirectory();
162
163 HttpRequestMessage Request = new HttpRequestMessage(HttpMethod.Head, this.directory.NewNonce);
164 HttpResponseMessage Response = await this.httpClient.SendAsync(Request);
165
166 if (!Response.IsSuccessStatusCode)
167 await Content.Getters.WebGetter.ProcessResponse(Response, Request.RequestUri);
168
169 if (Response.Headers.TryGetValues("Replay-Nonce", out IEnumerable<string> Values))
170 {
171 foreach (string s in Values)
172 return s;
173 }
174
175 throw new Exception("No nonce returned from server.");
176 }
177
181 public Task<AcmeDirectory> Directory
182 {
183 get
184 {
185 if (this.directory is null)
186 return this.GetDirectory();
187 else
188 return Task.FromResult<AcmeDirectory>(this.directory);
189 }
190 }
191
192 internal class AcmeResponse
193 {
194 public IEnumerable<KeyValuePair<string, object>> Payload;
195 public HttpResponseMessage ResponseMessage;
196 public Uri Location;
197 public string Json;
198 }
199
200 private async Task<HttpResponseMessage> HttpPost(Uri URL, Uri KeyID, string Accept, params KeyValuePair<string, object>[] Payload)
201 {
202 string HeaderString;
203 string PayloadString;
204 string Signature;
205
206 if (KeyID is null)
207 {
208 this.jws.Sign(new KeyValuePair<string, object>[]
209 {
210 new KeyValuePair<string, object>("nonce", await this.NextNonce()),
211 new KeyValuePair<string, object>("url", URL.ToString())
212 }, Payload, out HeaderString, out PayloadString, out Signature);
213 }
214 else
215 {
216 this.jws.Sign(new KeyValuePair<string, object>[]
217 {
218 new KeyValuePair<string, object>("kid", KeyID.ToString()),
219 new KeyValuePair<string, object>("nonce", await this.NextNonce()),
220 new KeyValuePair<string, object>("url", URL.ToString())
221 }, Payload, out HeaderString, out PayloadString, out Signature);
222 }
223
224 string Json = JSON.Encode(new KeyValuePair<string, object>[]
225 {
226 new KeyValuePair<string, object>("protected", HeaderString),
227 new KeyValuePair<string, object>("payload", PayloadString),
228 new KeyValuePair<string, object>("signature", Signature)
229 }, null);
230
231 HttpContent Content = new ByteArrayContent(Encoding.ASCII.GetBytes(Json));
232 Content.Headers.Add("Content-Type", JwsAlgorithm.JwsContentType);
233
234 if (!string.IsNullOrEmpty(Accept))
235 Content.Headers.TryAddWithoutValidation("Accept", Accept);
236
237 HttpResponseMessage Response = await this.httpClient.PostAsync(URL, Content);
238
239 this.GetNextNonce(Response);
240
241 return Response;
242 }
243
244 internal async Task<AcmeResponse> POST(Uri URL, Uri KeyID, params KeyValuePair<string, object>[] Payload)
245 {
246 HttpResponseMessage Response = await this.HttpPost(URL, KeyID, null, Payload);
247 byte[] Bin = await Response.Content.ReadAsByteArrayAsync();
248 string CharSet = Response.Content.Headers.ContentType?.CharSet;
249 Encoding Encoding;
250
251 if (string.IsNullOrEmpty(CharSet))
252 Encoding = Encoding.UTF8;
253 else
254 Encoding = InternetContent.GetEncoding(CharSet);
255
256 AcmeResponse AcmeResponse = new AcmeResponse()
257 {
258 Json = Encoding.GetString(Bin),
259 Location = URL,
260 ResponseMessage = Response,
261 Payload = null
262 };
263
264 if (Response.Headers.TryGetValues("Location", out IEnumerable<string> Values))
265 {
266 foreach (string s in Values)
267 {
268 AcmeResponse.Location = new Uri(s);
269 break;
270 }
271 }
272
273 if (string.IsNullOrEmpty(AcmeResponse.Json))
274 AcmeResponse.Payload = null;
275 else if ((AcmeResponse.Payload = JSON.Parse(AcmeResponse.Json) as IEnumerable<KeyValuePair<string, object>>) is null)
276 throw new Exception("Unexpected response returned.");
277
278 if (Response.IsSuccessStatusCode)
279 return AcmeResponse;
280 else
281 throw CreateException(AcmeResponse.Payload, Response);
282 }
283
284 internal static AcmeException CreateException(IEnumerable<KeyValuePair<string, object>> Obj, HttpResponseMessage Response)
285 {
286 AcmeException[] Subproblems = null;
287 string Type = null;
288 string Detail = null;
289 string instance = null;
290 int? Status = null;
291
292 foreach (KeyValuePair<string, object> P in Obj)
293 {
294 switch (P.Key)
295 {
296 case "type":
297 Type = P.Value as string;
298 break;
299
300 case "detail":
301 Detail = P.Value as string;
302 break;
303
304 case "instance":
305 instance = P.Value as string;
306 break;
307
308 case "status":
309 if (int.TryParse(P.Value as string, out int i))
310 Status = i;
311 break;
312
313 case "subproblems":
314 if (P.Value is Array A)
315 {
316 List<AcmeException> Subproblems2 = new List<AcmeException>();
317
318 foreach (object Obj2 in A)
319 {
320 if (Obj2 is IEnumerable<KeyValuePair<string, object>> Obj3)
321 Subproblems2.Add(CreateException(Obj3, Response));
322 }
323
324 Subproblems = Subproblems2.ToArray();
325 }
326 break;
327 }
328 }
329
330 if (Type.StartsWith("urn:ietf:params:acme:error:"))
331 {
332 switch (Type.Substring(27))
333 {
334 case "accountDoesNotExist": return new AcmeAccountDoesNotExistException(Type, Detail, Status, Subproblems);
335 case "badCSR": return new AcmeBadCsrException(Type, Detail, Status, Subproblems);
336 case "badNonce": return new AcmeBadNonceException(Type, Detail, Status, Subproblems);
337 case "badRevocationReason": return new AcmeBadRevocationReasonException(Type, Detail, Status, Subproblems);
338 case "badSignatureAlgorithm": return new AcmeBadSignatureAlgorithmException(Type, Detail, Status, Subproblems);
339 case "caa": return new AcmeCaaException(Type, Detail, Status, Subproblems);
340 case "compound": return new AcmeCompoundException(Type, Detail, Status, Subproblems);
341 case "connection": return new AcmeConnectionException(Type, Detail, Status, Subproblems);
342 case "dns": return new AcmeDnsException(Type, Detail, Status, Subproblems);
343 case "externalAccountRequired": return new AcmeExternalAccountRequiredException(Type, Detail, Status, Subproblems);
344 case "incorrectResponse": return new AcmeIncorrectResponseException(Type, Detail, Status, Subproblems);
345 case "invalidContact": return new AcmeInvalidContactException(Type, Detail, Status, Subproblems);
346 case "malformed": return new AcmeMalformedException(Type, Detail, Status, Subproblems);
347 case "rateLimited": return new AcmeRateLimitedException(Type, Detail, Status, Subproblems);
348 case "rejectedIdentifier": return new AcmeRejectedIdentifierException(Type, Detail, Status, Subproblems);
349 case "serverInternal": return new AcmeServerInternalException(Type, Detail, Status, Subproblems);
350 case "tls": return new AcmeTlsException(Type, Detail, Status, Subproblems);
351 case "unauthorized": return new AcmeUnauthorizedException(Type, Detail, Status, Subproblems);
352 case "unsupportedContact": return new AcmeUnsupportedContactException(Type, Detail, Status, Subproblems);
353 case "unsupportedIdentifier": return new AcmeUnsupportedIdentifierException(Type, Detail, Status, Subproblems);
354 case "userActionRequired": return new AcmeUserActionRequiredException(Type, Detail, Status, Subproblems, new Uri(instance), GetLink(Response, "terms-of-service"));
355 default: return new AcmeException(Type, Detail, Status, Subproblems);
356 }
357 }
358 else
359 return new AcmeException(Type, Detail, Status, Subproblems);
360 }
361
362 private static readonly Regex nextUrl = new Regex("^\\s*[<](?'URL'[^>]+)[>]\\s*;\\s*rel\\s*=\\s*['\"](?'Rel'.*)['\"]\\s*$", RegexOptions.Singleline | RegexOptions.Compiled);
363
364 internal static Uri GetLink(HttpResponseMessage Response, string Rel)
365 {
366 if (Response.Headers.TryGetValues("Link", out IEnumerable<string> Values))
367 {
368 foreach (string s in Values)
369 {
370 Match M = nextUrl.Match(s);
371 if (M.Success)
372 {
373 if (M.Groups["Rel"].Value == Rel)
374 return new Uri(M.Groups["URL"].Value);
375 }
376 }
377 }
378
379 return null;
380 }
381
382 private void GetNextNonce(HttpResponseMessage Response)
383 {
384 if (Response.Headers.TryGetValues("Replay-Nonce", out IEnumerable<string> Values))
385 {
386 foreach (string s in Values)
387 {
388 this.nonce = s;
389 return;
390 }
391 }
392 }
393
400 public async Task<AcmeAccount> CreateAccount(string[] ContactURLs, bool TermsOfServiceAgreed)
401 {
402 if (this.directory is null)
403 await this.GetDirectory();
404
405 AcmeResponse Response = await this.POST(this.directory.NewAccount, null,
406 new KeyValuePair<string, object>("termsOfServiceAgreed", TermsOfServiceAgreed),
407 new KeyValuePair<string, object>("contact", ContactURLs));
408
409 AcmeAccount Account;
410
411 if (Response.Payload is null)
412 {
413 Response = await this.POST(Response.Location, Response.Location);
414 Account = new AcmeAccount(this, Response.Location, Response.Payload);
415
416 bool ContactsDifferent = false;
417 int i, c = ContactURLs.Length;
418
419 if (c != Account.Contact.Length)
420 ContactsDifferent = true;
421 else
422 {
423 for (i = 0; i < c; i++)
424 {
425 if (ContactURLs[i] != Account.Contact[i])
426 {
427 ContactsDifferent = true;
428 break;
429 }
430 }
431 }
432
433 if (ContactsDifferent)
434 Account = await this.UpdateAccount(Account.Location, ContactURLs);
435 }
436 else
437 Account = new AcmeAccount(this, Response.Location, Response.Payload);
438
439 return Account;
440 }
441
446 public async Task<AcmeAccount> GetAccount()
447 {
448 if (this.directory is null)
449 await this.GetDirectory();
450
451 AcmeResponse Response = await this.POST(this.directory.NewAccount, null,
452 new KeyValuePair<string, object>("onlyReturnExisting", true));
453
454 if (Response.Payload is null)
455 Response = await this.POST(Response.Location, Response.Location);
456
457 return new AcmeAccount(this, Response.Location, Response.Payload);
458 }
459
466 public async Task<AcmeAccount> UpdateAccount(Uri AccountLocation, string[] Contact)
467 {
468 if (this.directory is null)
469 await this.GetDirectory();
470
471 AcmeResponse Response = await this.POST(AccountLocation, AccountLocation,
472 new KeyValuePair<string, object>("contact", Contact));
473
474 return new AcmeAccount(this, Response.Location, Response.Payload);
475 }
476
482 public async Task<AcmeAccount> DeactivateAccount(Uri AccountLocation)
483 {
484 if (this.directory is null)
485 await this.GetDirectory();
486
487 AcmeResponse Response = await this.POST(AccountLocation, AccountLocation,
488 new KeyValuePair<string, object>("status", "deactivated"));
489
490 return new AcmeAccount(this, Response.Location, Response.Payload);
491 }
492
497 public async Task<AcmeAccount> NewKey(Uri AccountLocation)
498 {
499 if (this.directory is null)
500 await this.GetDirectory();
501 RSA NewKey = RSA.Create();
502 NewKey.KeySize = KeySize;
503
504 if (NewKey.KeySize != KeySize) // Happens when using library from traditioanl .NET FW
505 {
506 Type T = Runtime.Inventory.Types.GetType("System.Security.Cryptography.RSACryptoServiceProvider")
507 ?? throw new Exception("Unable to set RSA key size to anything but default (" + NewKey.KeySize.ToString() + " bits).");
508
509 NewKey = Runtime.Inventory.Types.Instantiate(T, KeySize) as RSA;
510 }
511
513
514 try
515 {
516 Jws2.Sign(new KeyValuePair<string, object>[]
517 {
518 new KeyValuePair<string, object>("url", this.directory.KeyChange.ToString())
519 }, new KeyValuePair<string, object>[]
520 {
521 new KeyValuePair<string, object>("account", AccountLocation.ToString()),
522 new KeyValuePair<string, object>("oldkey", this.jws.PublicWebKey),
523 }, out string Header, out string Payload, out string Signature);
524
525 AcmeResponse Response = await this.POST(this.directory.KeyChange, AccountLocation,
526 new KeyValuePair<string, object>("protected", Header),
527 new KeyValuePair<string, object>("payload", Payload),
528 new KeyValuePair<string, object>("signature", Signature));
529
530 this.jwkThumbprint = null;
531 this.jws.ImportKey(NewKey);
532
533 return new AcmeAccount(this, Response.Location, Response.Payload);
534 }
535 finally
536 {
537 Jws2.Dispose();
538 }
539 }
540
549 public async Task<AcmeOrder> OrderCertificate(Uri AccountLocation, AcmeIdentifier[] Identifiers,
550 DateTime? NotBefore, DateTime? NotAfter)
551 {
552 if (this.directory is null)
553 await this.GetDirectory();
554
555 int i, c = Identifiers.Length;
556 IEnumerable<KeyValuePair<string, object>>[] Identifiers2 = new IEnumerable<KeyValuePair<string, object>>[c];
557
558 for (i = 0; i < c; i++)
559 {
560 Identifiers2[i] = new KeyValuePair<string, object>[]
561 {
562 new KeyValuePair<string, object>("type", Identifiers[i].Type),
563 new KeyValuePair<string, object>("value", Identifiers[i].Value)
564 };
565 }
566
567 List<KeyValuePair<string, object>> Payload = new List<KeyValuePair<string, object>>()
568 {
569 new KeyValuePair<string, object>("identifiers", Identifiers2)
570 };
571
572 if (NotBefore.HasValue)
573 Payload.Add(new KeyValuePair<string, object>("notBefore", NotBefore.Value));
574
575 if (NotAfter.HasValue)
576 Payload.Add(new KeyValuePair<string, object>("notAfter", NotAfter.Value));
577
578 AcmeResponse Response = await this.POST(this.directory.NewOrder, AccountLocation, Payload.ToArray());
579
580 return new AcmeOrder(this, AccountLocation, Response.Location, Response.Payload, Response.ResponseMessage);
581 }
582
589 public async Task<AcmeOrder> GetOrder(Uri AccountLocation, Uri OrderLocation)
590 {
591 AcmeResponse Response = await this.POST_as_GET(OrderLocation, AccountLocation);
592 return new AcmeOrder(this, AccountLocation, OrderLocation, Response.Payload, Response.ResponseMessage);
593 }
594
601 public async Task<AcmeOrder[]> GetOrders(Uri AccountLocation, Uri OrdersLocation)
602 {
603 AcmeResponse _ = await this.GET(OrdersLocation);
604 throw new NotImplementedException("Method not implemented.");
605 }
606
613 public async Task<AcmeAuthorization> GetAuthorization(Uri AccountLocation, Uri AuthorizationLocation)
614 {
615 AcmeResponse Response = await this.POST_as_GET(AuthorizationLocation, AccountLocation);
616 return new AcmeAuthorization(this, AccountLocation, AuthorizationLocation, Response.Payload);
617 }
618
625 public async Task<AcmeAuthorization> DeactivateAuthorization(Uri AccountLocation, Uri AuthorizationLocation)
626 {
627 AcmeResponse Response = await this.POST(AuthorizationLocation, AccountLocation,
628 new KeyValuePair<string, object>("status", "deactivated"));
629
630 return new AcmeAuthorization(this, AccountLocation, Response.Location, Response.Payload);
631 }
632
639 public async Task<AcmeChallenge> AcknowledgeChallenge(Uri AccountLocation, Uri ChallengeLocation)
640 {
641 AcmeResponse Response = await this.POST(ChallengeLocation, AccountLocation);
642 return this.CreateChallenge(AccountLocation, Response.Payload);
643 }
644
645 internal AcmeChallenge CreateChallenge(Uri AccountLocation, IEnumerable<KeyValuePair<string, object>> Obj)
646 {
647 string Type = string.Empty;
648
649 foreach (KeyValuePair<string, object> P2 in Obj)
650 {
651 if (P2.Key == "type" && P2.Value is string s)
652 {
653 Type = s;
654 break;
655 }
656 }
657
658 switch (Type)
659 {
660 case "http-01": return new AcmeHttpChallenge(this, AccountLocation, Obj);
661 case "dns-01": return new AcmeDnsChallenge(this, AccountLocation, Obj);
662 default: return new AcmeChallenge(this, AccountLocation, Obj);
663 }
664 }
665
670 internal string JwkThumbprint
671 {
672 get
673 {
674 if (this.jwkThumbprint is null)
675 {
676 SortedDictionary<string, object> Sorted = new SortedDictionary<string, object>();
677
678 foreach (KeyValuePair<string, object> P in this.jws.PublicWebKey)
679 {
680 switch (P.Key)
681 {
682 case "kty":
683 case "n":
684 case "e":
685 Sorted[P.Key] = P.Value;
686 break;
687 }
688 }
689
690 string Json = JSON.Encode(Sorted, null);
691 byte[] Bin = Encoding.UTF8.GetBytes(Json);
692 byte[] Hash = Hashes.ComputeSHA256Hash(Bin);
693
694 this.jwkThumbprint = Base64Url.Encode(Hash);
695 }
696
697 return this.jwkThumbprint;
698 }
699 }
700
708 public async Task<AcmeOrder> FinalizeOrder(Uri AccountLocation, Uri FinalizeLocation, CertificateRequest CertificateRequest)
709 {
710 byte[] CSR = CertificateRequest.BuildCSR();
711 AcmeResponse Response = await this.POST(FinalizeLocation, AccountLocation,
712 new KeyValuePair<string, object>("csr", Base64Url.Encode(CSR)));
713
714 return new AcmeOrder(this, AccountLocation, Response.Location, Response.Payload, Response.ResponseMessage);
715 }
716
723 public async Task<X509Certificate2[]> DownloadCertificate(Uri AccountLocation, Uri CertificateLocation)
724 {
725 string ContentType = PemDecoder.ContentType;
726 HttpResponseMessage Response = await this.HttpPost(CertificateLocation, AccountLocation, ContentType, null);
727
728 if (!Response.IsSuccessStatusCode)
729 await Content.Getters.WebGetter.ProcessResponse(Response, AccountLocation);
730
731 byte[] Bin = await Response.Content.ReadAsByteArrayAsync();
732
733 if (Response.Headers.TryGetValues("Content-Type", out IEnumerable<string> Values))
734 {
735 foreach (string s in Values)
736 {
737 ContentType = s;
738 break;
739 }
740 }
741
742 object Decoded = await InternetContent.DecodeAsync(ContentType, Bin, CertificateLocation);
743 if (!(Decoded is X509Certificate2[] Certificates))
744 throw new Exception("Unexpected response returned. Content-Type: " + ContentType);
745
746 return Certificates;
747 }
748
754 public RSAParameters ExportAccountKey(bool IncludePrivateParameters)
755 {
756 return this.jws.RSA.ExportParameters(IncludePrivateParameters);
757 }
758
759 }
760}
Static class that does BASE64URL encoding (using URL and filename safe alphabet), as defined in RFC46...
Definition: Base64Url.cs:11
static string Encode(byte[] Data)
Converts a binary block of data to a Base64URL-encoded string.
Definition: Base64Url.cs:48
Static class managing encoding and decoding of internet content.
static Task< object > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
static Encoding GetEncoding(string CharacterSet)
Gets a character encoding from its name.
Helps with common JSON-related tasks.
Definition: JSON.cs:14
static object Parse(string Json)
Parses a JSON string.
Definition: JSON.cs:43
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:507
Represents an ACME account.
Definition: AcmeAccount.cs:34
string[] Contact
Optional array of URLs that the server can use to contact the client for issues related to this accou...
Definition: AcmeAccount.cs:98
Represents an ACME authorization.
Base class of all ACME challenges.
Implements an ACME client for the generation of certificates using ACME-compliant certificate servers...
Definition: AcmeClient.cs:24
async Task< AcmeAuthorization > DeactivateAuthorization(Uri AccountLocation, Uri AuthorizationLocation)
Deactivates an authorization.
Definition: AcmeClient.cs:625
async Task< AcmeAccount > NewKey(Uri AccountLocation)
Generates a new key for the account. (Account keys are managed by the CSP.)
Definition: AcmeClient.cs:497
async Task< AcmeOrder > GetOrder(Uri AccountLocation, Uri OrderLocation)
Gets the state of an order.
Definition: AcmeClient.cs:589
RSAParameters ExportAccountKey(bool IncludePrivateParameters)
Exports the account key.
Definition: AcmeClient.cs:754
async Task< AcmeAccount > GetAccount()
Gets the account object from the ACME server.
Definition: AcmeClient.cs:446
async Task< AcmeAccount > CreateAccount(string[] ContactURLs, bool TermsOfServiceAgreed)
Creates an account on the ACME server.
Definition: AcmeClient.cs:400
async Task< AcmeOrder > OrderCertificate(Uri AccountLocation, AcmeIdentifier[] Identifiers, DateTime? NotBefore, DateTime? NotAfter)
Orders certificate.
Definition: AcmeClient.cs:549
void Dispose()
IDisposable.Dispose
Definition: AcmeClient.cs:82
async Task< AcmeChallenge > AcknowledgeChallenge(Uri AccountLocation, Uri ChallengeLocation)
Acknowledges a challenge from the server.
Definition: AcmeClient.cs:639
async Task< AcmeAccount > UpdateAccount(Uri AccountLocation, string[] Contact)
Updates an account.
Definition: AcmeClient.cs:466
async Task< AcmeOrder[]> GetOrders(Uri AccountLocation, Uri OrdersLocation)
Gets the list of current orders for an account.
Definition: AcmeClient.cs:601
async Task< AcmeDirectory > GetDirectory()
Gets the ACME directory.
Definition: AcmeClient.cs:101
AcmeClient(Uri DirectoryEndpoint, RSAParameters Parameters)
Implements an ACME client for the generation of certificates using ACME-compliant certificate servers...
Definition: AcmeClient.cs:39
async Task< AcmeAuthorization > GetAuthorization(Uri AccountLocation, Uri AuthorizationLocation)
Gets the state of an authorization.
Definition: AcmeClient.cs:613
async Task< AcmeOrder > FinalizeOrder(Uri AccountLocation, Uri FinalizeLocation, CertificateRequest CertificateRequest)
Finalize order.
Definition: AcmeClient.cs:708
Task< AcmeDirectory > Directory
Directory object.
Definition: AcmeClient.cs:182
async Task< X509Certificate2[]> DownloadCertificate(Uri AccountLocation, Uri CertificateLocation)
Downloads a certificate.
Definition: AcmeClient.cs:723
async Task< AcmeAccount > DeactivateAccount(Uri AccountLocation)
Deactivates an account.
Definition: AcmeClient.cs:482
Represents an ACME directory.
Uri KeyChange
URL for keyChange method.
Uri NewAccount
URL for newAccount method.
Uri NewOrder
URL for newOrder method.
Represents an ACME identifier.
Represents an ACME order.
Definition: AcmeOrder.cs:50
Uri Location
Location of resource.
Definition: AcmeResource.cs:37
Contains methods for simple hash calculations.
Definition: Hashes.cs:59
static byte[] ComputeSHA256Hash(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:348
Abstract base class for JWS algorithm.
Definition: JwsAlgorithm.cs:15
const string JwsContentType
application/jose+json
Definition: JwsAlgorithm.cs:19
RSASSA-PKCS1-v1_5 SHA-256 algorithm. https://tools.ietf.org/html/rfc3447#page-32
RSA RSA
RSA Cryptographic service provider.
void ImportKey(RSA RSA)
Imports a new key from an external RSA Cryptographic service provider.
override IEnumerable< KeyValuePair< string, object > > PublicWebKey
The public JSON web key, if supported.
override void Dispose()
IDisposable.Dispose
override string Sign(string HeaderEncoded, string PayloadEncoded)
Signs data.
Contains information about a Certificate Signing Request (CSR).
byte[] BuildCSR()
Building a Certificate Signing Request (CSR) in accordance with RFC 2986
Decodes certificates encoded using the application/pem-certificate-chain content type.
Definition: PemDecoder.cs:15
const string ContentType
application/pem-certificate-chain
Definition: PemDecoder.cs:19