Beispiel #1
0
        private static async Task ValidateChallengeCompletion(AuthorizationChallenge challenge, string domainName)
        {
            var maxRetries = 50;
            var delay      = TimeSpan.FromSeconds(5);

            if (challenge.Type != "dns-01")
            {
                throw new Exception("Invalid challenge type.");
            }

            Program.LogLine($"Trying to validate an entry exists for _acme-challenge.{domainName} every {delay} for a maximum of {maxRetries * delay}", true);
            for (var i = 0; i < maxRetries; i++)
            {
                var lookup = new LookupClient(IPAddress.Parse("1.1.1.1"));
                IDnsQueryResponse result = await lookup.QueryAsync($"_acme-challenge.{domainName}", QueryType.TXT);

                TxtRecord record = result.Answers.TxtRecords().Where(txt => txt.Text.Contains(challenge.AuthorizationToken)).FirstOrDefault();
                if (record != null)
                {
                    Program.LogLine($"Succesfully validated a DNS entry exists for {domainName}.", true);
                    return;
                }

                await Task.Delay(delay);
            }

            throw new Exception($"Failed to validate {domainName}");
        }
Beispiel #2
0
 static async Task <Dictionary <string, bool> > ValidateChallengeCompletion(AuthorizationChallenge challenge, IEnumerable <string> domainNames)
 {
     if (challenge.Type == "http-01")
     {
         return(await ValidateHttpChallenge(challenge.Token, challenge.AuthorizationToken, domainNames));
     }
     else if (challenge.Type == "dns-01")
     {
         Console.WriteLine($"Please create a text entry in the DNS records for each domain in {string.Join(',', domainNames)} using {challenge.Token}");
         return(domainNames.ToDictionary(domain => domain, domain => false));
     }
     else
     {
         Console.WriteLine($"Unknown challenge type encountered '{challenge.Type}'. Please validate yourself.");
         // can't validate, return success.
         return(domainNames.ToDictionary(domain => domain, domain => true));
     }
 }
Beispiel #3
0
 private static async Task <bool> ValidateChallengeCompletion(AuthorizationChallenge challenge, string domainName)
 {
     if (challenge.Type == "http-01")
     {
         return(await ValidateHttpChallenge(challenge.Token, challenge.AuthorizationToken, domainName));
     }
     else if (challenge.Type == "dns-01")
     {
         // can't validate, return success.
         Console.WriteLine($"Please validate a text entry exists for _acme-challenge.{domainName} with value {challenge.AuthorizationToken}");
         return(true);
     }
     else
     {
         // can't validate, return success.
         Console.WriteLine($"Unknown challenge type encountered '{challenge.Type}'. Please validate yourself.");
         return(true);
     }
 }
Beispiel #4
0
        private static async Task <AuthorizationChallengeResponse> CompleteChallenge(Kenc.ACMELib.ACMEClient acmeClient, AuthorizationChallenge challenge, string value)
        {
            try
            {
                return(await acmeClient.CompleteChallengeAsync(challenge.Url, challenge.Token, value));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            return(null);
        }
Beispiel #5
0
 private static async Task <AuthorizationChallengeResponse> CompleteChallenge(ACMEClient acmeClient, AuthorizationChallenge challenge)
 {
     return(await acmeClient.CompleteChallengeAsync(challenge.Url, challenge.Token, challenge.AuthorizationToken));
 }
Beispiel #6
0
        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);
        }
Beispiel #7
0
        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}");
        }