private static async Task <IEnumerable <AuthorizationChallengeResponse> > RetrieveAuthz(ACMEClient acmeClient, Uri[] uris) { var challenges = new List <AuthorizationChallengeResponse>(); foreach (Uri uri in uris) { try { AuthorizationChallengeResponse result = await acmeClient.GetAuthorizationChallengeAsync(uri); challenges.Add(result); } catch (Exception ex) { Console.WriteLine(ex.Message); } } return(challenges); }
public async Task <Order> ValidateDomains() { if (acmeDirectory.NewAuthz != null) { Console.WriteLine("Target CA supports NewAuthz"); Task <AuthorizationChallengeResponse>[] preAuthorizationChallenges = options.Domains.Where(x => !x.StartsWith("*")) .Select(x => NewAuthorizationAsync(acmeClient, x)) .ToArray(); AuthorizationChallengeResponse[] challengeResponses = await Task.WhenAll(preAuthorizationChallenges); } IEnumerable <OrderIdentifier> domains = options.Domains.Select(domain => new OrderIdentifier { Type = ChallengeType.DNSChallenge, Value = domain }); Order order = await NewOrderAsync(acmeClient, domains); // todo: save order identifier Program.LogLine($"Order location: {order.Location}"); Uri[] validations = order.Authorizations; var dnsRecords = new List <string>(order.Authorizations.Length); IEnumerable <AuthorizationChallengeResponse> auths = await RetrieveAuthz(acmeClient, validations); foreach (AuthorizationChallengeResponse item in auths) { Program.LogLine($"Processing validations for {item.Identifier.Value}"); if (item.Status == ACMEStatus.Valid) { Program.LogLine("Domain already validated succesfully."); continue; } AuthorizationChallenge validChallenge = item.Challenges.Where(challenge => challenge.Status == ACMEStatus.Valid).FirstOrDefault(); if (validChallenge != null) { Program.LogLine("Found a valid challenge, skipping domain.", true); Program.LogLine(validChallenge.Type, true); continue; } // limit to DNS challenges, as we can handle them with Cloudflare. AuthorizationChallenge dnsChallenge = item.Challenges.FirstOrDefault(x => x.Type == "dns-01"); Program.LogLine($"Got challenge for {item.Identifier.Value}"); var recordId = await AddDNSEntry(item.Identifier.Value, dnsChallenge.AuthorizationToken); dnsRecords.Add(recordId); // validate the DNS record is accessible. await ValidateChallengeCompletion(dnsChallenge, item.Identifier.Value); AuthorizationChallengeResponse c = await CompleteChallenge(acmeClient, dnsChallenge); while (c.Status == ACMEStatus.Pending) { await Task.Delay(5000); c = await acmeClient.GetAuthorizationChallengeAsync(dnsChallenge.Url); } if (c.Status == ACMEStatus.Valid) { // no reason to keep going, we have one succesfull challenge! continue; } } var failedAuthorizations = new List <string>(); foreach (Uri challenge in order.Authorizations) { AuthorizationChallengeResponse c; do { await Task.Delay(5000); c = await acmeClient.GetAuthorizationChallengeAsync(challenge); }while (c == null || c.Status == ACMEStatus.Pending); if (c.Status == ACMEStatus.Invalid) { failedAuthorizations.Add(c.Identifier.Value); } } if (failedAuthorizations.Any()) { throw new Exception($"Failed to authorize the following domains {string.Join(',', failedAuthorizations)}."); } return(order); }
private static async Task OrderDomains(ACMEClient acmeClient, params string[] domainNames) { IEnumerable <OrderIdentifier> domains = domainNames.Select(domain => new OrderIdentifier { Type = ChallengeType.DNSChallenge, Value = domain }); Uri[] validations = null; Order order = null; while (order == null) { order = await NewOrderAsync(acmeClient, domains); if (order == null) { Console.WriteLine("Failed.. retrying"); await Task.Delay(5000); } } // todo: save order identifier Console.WriteLine($"Order location: {order.Location}"); validations = order.Authorizations; IEnumerable <AuthorizationChallengeResponse> auths = await RetrieveAuthz(acmeClient, validations); foreach (AuthorizationChallengeResponse item in auths) { if (item.Status == ACMEStatus.Valid) { Console.WriteLine("Domain already validated succesfully."); continue; } AuthorizationChallenge validChallenge = item.Challenges.Where(challenge => challenge.Status == ACMEStatus.Valid).FirstOrDefault(); if (validChallenge != null) { Console.WriteLine("Found a valid challenge, skipping domain."); Console.WriteLine(validChallenge.Type); continue; } IEnumerable <AuthorizationChallenge> applicableChallenges = item.Wildcard ? item.Challenges.Where(x => x.Type == "dns-01") : item.Challenges; foreach (AuthorizationChallenge challenge in applicableChallenges) { Console.WriteLine($"Status: {challenge.Status}"); Console.WriteLine($"Challenge: {challenge.Url}"); Console.WriteLine($"Type: {challenge.Type}"); Console.WriteLine($"Token: {challenge.Token}"); Console.WriteLine($"Value: {challenge.AuthorizationToken}"); if (challenge.Type == "http-01") { File.WriteAllText(challenge.Token, challenge.AuthorizationToken); Console.WriteLine($"File saved as: {challenge.Token} in working directory."); Console.WriteLine($"Please upload the file to {item.Identifier.Value}/.well-known/acme-challenge/{challenge.Token}"); } else if (challenge.Type == "dns-01") { Console.WriteLine($"Please create a text entry for _acme-challenge.{item.Identifier.Value} with value: {challenge.AuthorizationToken}"); } else { Console.WriteLine($"Unknown challenge type encountered '{challenge.Type}'. Please handle accourdingly."); } var result = HandleConsoleInput("Challenge completed? [y/n]", new[] { "y", "yes", "n", "no" }); if (result == "y" || result == "yes") { Console.WriteLine("Validating challenge"); var validation = await ValidateChallengeCompletion(challenge, item.Identifier.Value); if (validation) { AuthorizationChallengeResponse c = await CompleteChallenge(acmeClient, challenge, challenge.AuthorizationToken); while (c.Status == ACMEStatus.Pending) { await Task.Delay(5000); c = await acmeClient.GetAuthorizationChallengeAsync(challenge.Url); } Console.WriteLine($"Challenge Status: {c.Status}"); if (c.Status == ACMEStatus.Valid) { // no reason to keep going, we have one succesfull challenge! break; } } else { Console.WriteLine($"Validation failed for {item.Identifier.Value}"); } } else { Console.WriteLine("Skipping challenge"); } } } foreach (Uri challenge in order.Authorizations) { AuthorizationChallengeResponse c; do { c = await acmeClient.GetAuthorizationChallengeAsync(challenge); }while (c == null || c.Status == ACMEStatus.Pending); if (c.Status == ACMEStatus.Invalid) { Console.WriteLine($"Failed to validate domain {c.Identifier.Value}. Aborting"); return; } } order = await acmeClient.UpdateOrderAsync(order); Console.WriteLine($"Order status:{order.Status}"); while (order.Status == ACMEStatus.Processing) { Thread.Sleep(500); Console.WriteLine("Order status = processing; updating.."); order = await acmeClient.UpdateOrderAsync(order); } var certKey = new RSACryptoServiceProvider(4096); SaveRSAKeyToFile(certKey, $"{FixFilename(order.Identifiers[0].Value)}.key"); Order certOrder = null; try { certOrder = await acmeClient.RequestCertificateAsync(order, certKey); while (certOrder.Status == ACMEStatus.Processing) { Thread.Sleep(500); Console.WriteLine("Order status = processing; updating.."); certOrder = await acmeClient.UpdateOrderAsync(certOrder); } Console.WriteLine(certOrder.Status); } catch (Exception ex) { Console.WriteLine(ex.Message); return; } X509Certificate2 cert = await acmeClient.GetCertificateAsync(certOrder); var certdata = cert.Export(X509ContentType.Cert); var publicKeyFilename = $"{FixFilename(certOrder.Identifiers[0].Value)}.crt"; File.WriteAllBytes(publicKeyFilename, certdata); Console.WriteLine($"Public certificate written to file {publicKeyFilename}"); // combine the two! X509Certificate2 properCert = cert.CopyWithPrivateKey(certKey); Console.WriteLine("Enter password to secure PFX"); System.Security.SecureString password = PasswordInput.ReadPassword(); var pfxData = properCert.Export(X509ContentType.Pfx, password); var privateKeyFilename = $"{FixFilename(certOrder.Identifiers[0].Value)}.pfx"; File.WriteAllBytes(privateKeyFilename, pfxData); Console.WriteLine($"Private certificate written to file {privateKeyFilename}"); }