Ejemplo n.º 1
0
        public async Task PostAddsProperHeader()
        {
            HttpRequestMessage httpRequestMessage = null;

            Mock <HttpMessageHandler> messageHandlerMock = new Mock <HttpMessageHandler>(MockBehavior.Strict)
                                                           .AddDirectoryResponse()
                                                           .AddNonce();

            messageHandlerMock.Protected()
            .Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.Is <HttpRequestMessage>(x => x.RequestUri == TestHelpers.AcmeDirectory.NewAccount), ItExpr.IsAny <CancellationToken>())
            .Returns((HttpRequestMessage request, CancellationToken token) =>
            {
                httpRequestMessage = request;
                return(Task.FromResult(new HttpResponseMessage()));
            })
            .Verifiable();

            var httpClient = new HttpClient(messageHandlerMock.Object);

            var rsaKey     = RSA.Create();
            var acmeClient = new ACMEClient(TestHelpers.BaseUri, rsaKey, httpClient);
            await acmeClient.RegisterAsync(new[] { "*****@*****.**" });

            // assert
            httpRequestMessage.Content.Headers.ContentType.MediaType.Should().Be("application/jose+json");
            httpRequestMessage.Content.Headers.ContentType.CharSet.Should().BeNull();
        }
Ejemplo n.º 2
0
        public (ACMEClient, Mock <HttpClient>) Build()
        {
            if (rsaKey == null)
            {
                rsaKey = RSA.Create();
            }

            var acmeClient = new ACMEClient(TestHelpers.BaseUri, rsaKey, restClient.Object);

            return(acmeClient, restClient);
        }
Ejemplo n.º 3
0
        private static async Task <AuthorizationChallengeResponse> CompleteChallenge(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);
        }
Ejemplo n.º 4
0
        public (ACMEClient, Mock <IRestClient>) Build()
        {
            if (rsaKey == null)
            {
                rsaKey = RSA.Create();
            }

            var restClientFactory = new Mock <IRestClientFactory>();

            restClientFactory.Setup(rcf => rcf.CreateRestClient(It.IsAny <Jws>())).Returns(restClient.Object);

            var acmeClient = new ACMEClient(TestHelpers.baseUri.ToString(), rsaKey, accountId, restClientFactory.Object);

            return(acmeClient, restClient);
        }
Ejemplo n.º 5
0
        public async Task GetsANonceIfNoneIsPresent()
        {
            Mock <HttpMessageHandler> messageHandlerMock = new Mock <HttpMessageHandler>(MockBehavior.Strict)
                                                           .AddDirectoryResponse()
                                                           .AddNonce();

            messageHandlerMock.Protected()
            .Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.Is <HttpRequestMessage>(x => x.RequestUri == TestHelpers.AcmeDirectory.NewAccount), ItExpr.IsAny <CancellationToken>())
            .Returns((HttpRequestMessage request, CancellationToken token) => Task.FromResult(new HttpResponseMessage()))
            .Verifiable();

            var httpClient = new HttpClient(messageHandlerMock.Object);

            var rsaKey     = RSA.Create();
            var acmeClient = new ACMEClient(TestHelpers.BaseUri, rsaKey, httpClient);
            await acmeClient.RegisterAsync(new[] { "*****@*****.**" });

            messageHandlerMock.VerifyAll();
        }
Ejemplo n.º 6
0
        private static async Task <Order> NewOrderAsync(ACMEClient acmeClient, IEnumerable <OrderIdentifier> domains)
        {
            try
            {
                Order result = await acmeClient.OrderAsync(domains);

                return(result);
            }
            catch (ACMEException ex)
            {
                Console.WriteLine(ex.Descriptor);
                Console.WriteLine(ex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.GetType());
                Console.WriteLine(ex.Message);
            }

            return(null);
        }
Ejemplo n.º 7
0
        public OrderDomains(Options options)
        {
            this.options = options;
            var serviceCollection = new ServiceCollection();

            serviceCollection.AddHttpClient();
            ServiceProvider    services          = serviceCollection.BuildServiceProvider();
            IHttpClientFactory httpClientFactory = services.GetRequiredService <System.Net.Http.IHttpClientFactory>();

            cloudflareClient = new CloudflareClientFactory(options.Username, options.ApiKey, new CloudflareRestClientFactory(httpClientFactory), CloudflareAPIEndpoint.V4Endpoint)
                               .Create();

            // RSA service provider
            var rsaCryptoServiceProvider = new RSACryptoServiceProvider(2048);

            if (string.IsNullOrEmpty(options.Key))
            {
                Program.LogLine("Generating new key for ACME.");
                var exportKey = rsaCryptoServiceProvider.ExportCspBlob(true);
                var strKey    = Convert.ToBase64String(exportKey);

                File.WriteAllText("acmekey.key", strKey);
            }
            else
            {
                var key = Convert.FromBase64String(File.ReadAllText(options.Key));
                rsaCryptoServiceProvider.ImportCspBlob(key);
            }

            var rsaKey = RSA.Create(rsaCryptoServiceProvider.ExportParameters(true));

            acmeClient = new ACMEClient(
                new Uri(options.Environment == AcmeEnvironment.ProductionV2 ? ACMEEnvironment.ProductionV2 : ACMEEnvironment.StagingV2),
                rsaKey,
                new HttpClient());
        }
Ejemplo n.º 8
0
        static async Task Main(string[] args)
        {
            Console.WriteLine("Kenc.ACMEClient example");
            Console.WriteLine("USE AT OWN RISK");
            Console.WriteLine("This program uses the LetsEncrypt STAGING environment by default");
            Console.WriteLine("WARNING: Encryption key is stored UNPROTECTED in bin folder");
            Console.WriteLine("============================================================\n\n");

            // RSA service provider
            var rsaCryptoServiceProvider = new RSACryptoServiceProvider(2048);

            Console.WriteLine($"Looking for key {keyPath}");
            var existingKey = File.Exists(keyPath);

            if (!existingKey)
            {
                Console.WriteLine("Key not found - generating");
                var exportKey = rsaCryptoServiceProvider.ExportCspBlob(true);
                var strKey    = Convert.ToBase64String(exportKey);

                File.WriteAllText(keyPath, strKey);
            }

            var key = Convert.FromBase64String(File.ReadAllText(keyPath));

            rsaCryptoServiceProvider.ImportCspBlob(key);

            var rsaKey = RSA.Create(rsaCryptoServiceProvider.ExportParameters(true));

            var acmeClient = new ACMEClient(ACMEEnvironment.StagingV2, rsaKey, string.Empty, new RestClientFactory());
            var directory  = await acmeClient.InitializeAsync();

            Account account = null;

            if (existingKey)
            {
                Console.WriteLine("Validating if user exists with existing key");
                try
                {
                    account = await acmeClient.GetAccountAsync();
                }
                catch (AccountDoesNotExistException exception)
                {
                    Console.WriteLine(exception.Message);
                }
                catch (Exception exception)
                {
                    Console.WriteLine($"Exception encounted while looking up client: {exception.Message}");
                }

                if (account != null)
                {
                    Console.WriteLine($"Using previously created account {account.Id}");
                }
                else
                {
                    Console.WriteLine("Couldn't retrieve existing account. Creating new account.");
                }
            }

            if (account == null)
            {
                Console.WriteLine("Creating user..");
                Console.WriteLine("By creating this user, you acknowledge the terms of service: {0}", directory.Meta.TermsOfService);
                Console.Write("Enter email address for user: "******"mailto:" + userContact });
                }
                catch (Exception ex)
                {
                    Console.WriteLine("An error occured while registering user. {0}", ex.Message);
                    throw;
                }
            }

            Console.WriteLine("Enter domain to validate and request certificate for:");
            var domainName = Console.ReadLine();

            if (string.IsNullOrWhiteSpace(domainName))
            {
                Console.WriteLine("Invalid domain aborting.");
                return;
            }

            var domainTask = OrderDomains(acmeClient, domainName);

            domainTask.Wait();
        }
Ejemplo n.º 9
0
        static async Task OrderDomains(ACMEClient acmeClient, params string[] domainNames)
        {
            var 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;
            var auths = await RetrieveAuthz(acmeClient, validations);

            foreach (var item in auths)
            {
                foreach (var challenge in item.Challenges)
                {
                    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 {domainNames.First()}/.well-known/acme-challenge/{challenge.Token}");
                    }
                    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}");
                    }
                    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, domainNames);

                        if (validation.Any(validationItem => !validationItem.Value))
                        {
                            Console.WriteLine($"The following domains failed validation: " +
                                              $"{string.Join(',', validation.Where(vItem => !vItem.Value).Select(vItem => vItem.Key))}");
                        }

                        if (validation.Any(validationItem => validationItem.Value))
                        {
                            var c = await CompleteChallenge(acmeClient, challenge, challenge.AuthorizationToken);

                            if (c != null)
                            {
                                Console.WriteLine(c.Status);
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine("Skipping challenge");
                    }
                }
            }

            // todo: add a proper check to see if all validations passed.

            order = await acmeClient.UpdateOrderAsync(order);

            Console.WriteLine($"Order status:{order.Status}");

            // todo: if(order.Status == failed...)

            while (order.Status == Order.Processing)
            {
                Thread.Sleep(500);
                Console.WriteLine("Order status = processing; updating..");
                order = await acmeClient.UpdateOrderAsync(order);
            }

            var certKey = new RSACryptoServiceProvider(4096);

            SaveRSAKeyToFile(certKey, $"{order.Identifiers[0].Value}.key");

            Order certOrder = null;

            try
            {
                certOrder = await acmeClient.RequestCertificateAsync(order, certKey);

                while (certOrder.Status == Order.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);
            }

            var cert = await acmeClient.GetCertificateAsync(certOrder);

            var certdata          = cert.Export(X509ContentType.Cert);
            var publicKeyFilename = $"{certOrder.Identifiers[0].Value}.crt";

            File.WriteAllBytes(publicKeyFilename, certdata);
            Console.WriteLine($"Public certificate written to file {publicKeyFilename}");

            // combine the two!
            var properCert = cert.CopyWithPrivateKey(certKey);
            var pfxData    = cert.Export(X509ContentType.Pfx);

            var privateKeyFilename = $"{certOrder.Identifiers[0].Value}.pfx";

            File.WriteAllBytes(privateKeyFilename, pfxData);
            Console.WriteLine($"Private certificate written to file {privateKeyFilename}");
        }
Ejemplo n.º 10
0
 private static async Task <AuthorizationChallengeResponse> CompleteChallenge(ACMEClient acmeClient, AuthorizationChallenge challenge)
 {
     return(await acmeClient.CompleteChallengeAsync(challenge.Url, challenge.Token, challenge.AuthorizationToken));
 }
Ejemplo n.º 11
0
        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);
        }
Ejemplo n.º 12
0
 private static async Task <AuthorizationChallengeResponse> NewAuthorizationAsync(ACMEClient acmeClient, string domain)
 {
     return(await acmeClient.NewAuthorizationAsync(domain));
 }
Ejemplo n.º 13
0
        private static async Task <Order> NewOrderAsync(ACMEClient acmeClient, IEnumerable <OrderIdentifier> domains)
        {
            Order result = await acmeClient.OrderAsync(domains);

            return(result);
        }
Ejemplo n.º 14
0
        private static async Task Main()
        {
            Console.WriteLine("Kenc.ACMEClient example");
            Console.WriteLine("USE AT OWN RISK");
            Console.WriteLine("This program uses the LetsEncrypt STAGING environment by default");
            Console.WriteLine("WARNING: Encryption key is stored UNPROTECTED in bin folder");
            Console.WriteLine("============================================================\n\n");

            // RSA service provider
            var rsaCryptoServiceProvider = new RSACryptoServiceProvider(2048);

            Console.WriteLine($"Looking for key {keyPath}");
            var existingKey = File.Exists(keyPath);

            if (!existingKey)
            {
                Console.WriteLine("Key not found - generating");
                var exportKey = rsaCryptoServiceProvider.ExportCspBlob(true);
                var strKey    = Convert.ToBase64String(exportKey);

                File.WriteAllText(keyPath, strKey);
            }

            var key = Convert.FromBase64String(File.ReadAllText(keyPath));

            rsaCryptoServiceProvider.ImportCspBlob(key);

            var rsaKey = RSA.Create(rsaCryptoServiceProvider.ExportParameters(true));

            var           acmeClient = new ACMEClient(new Uri(ACMEEnvironment.StagingV2), rsaKey, new HttpClient());
            ACMEDirectory directory  = await acmeClient.InitializeAsync();

            Account account = null;

            if (existingKey)
            {
                Console.WriteLine("Validating if user exists with existing key");
                try
                {
                    account = await acmeClient.GetAccountAsync();
                }
                catch (AccountDoesNotExistException exception)
                {
                    Console.WriteLine(exception.Message);
                }
                catch (Exception exception)
                {
                    Console.WriteLine($"Exception encounted while looking up client: {exception.Message}");
                }

                if (account != null)
                {
                    Console.WriteLine($"Using previously created account {account.Id}");
                }
                else
                {
                    Console.WriteLine("Couldn't retrieve existing account. Creating new account.");
                }
            }

            if (account == null)
            {
                Console.WriteLine("Creating user..");
                Console.WriteLine("By creating this user, you acknowledge the terms of service: {0}", directory.Meta.TermsOfService);
                Console.Write("Enter email address for user: "******"mailto:" + userContact });
                }
                catch (Exception ex)
                {
                    Console.WriteLine("An error occured while registering user. {0}", ex.Message);
                    throw;
                }
            }

            Console.WriteLine("Enter domain(s) to validate and request certificate(s) for (wildcard? add wildcard and tld comma separated):");
            var domainNames = Console.ReadLine().Split(',').Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();

            if (!domainNames.Any())
            {
                Console.WriteLine("Invalid domain aborting.");
                return;
            }

            Task domainTask = OrderDomains(acmeClient, domainNames);

            domainTask.Wait();

            // enumerate all certs
            var certificateFiles = Directory.EnumerateFiles(Directory.GetCurrentDirectory(), "*.crt").ToList();

            Console.WriteLine($"Revoking certificates: {string.Join(',', certificateFiles)}");

            var foo = HandleConsoleInput("Continue?", new[] { "y", "yes", "n", "no" }, false).ToLower();

            if (foo == "y" || foo == "yes")
            {
                IEnumerable <X509Certificate> certificates = certificateFiles.Select(path => X509Certificate2.CreateFromCertFile(path));
                foreach (X509Certificate certificate in certificates)
                {
                    try
                    {
                        await acmeClient.RevokeCertificateAsync(certificate, RevocationReason.Superseded);

                        Console.WriteLine($"{certificate.Subject} revoked.");
                    }
                    catch (ACMEException exception) when(exception.Descriptor == "urn:ietf:params:acme:error:alreadyRevoked")
                    {
                        Console.WriteLine(exception.Message);
                    }
                    catch (ACMEException exception)
                    {
                        Console.WriteLine(exception.Message);
                        throw;
                    }
                }
                Console.WriteLine("Completed");
            }
        }
Ejemplo n.º 15
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}");
        }