public async Task NewOrder()
        {
            using (var http = _server.CreateClient())
            {
                var dir = await GetDir();

                var signer = new Crypto.JOSE.Impl.RSJwsTool();
                signer.Init();
                using (var acme = new AcmeProtocolClient(http, dir,
                                                         signer: signer))
                {
                    await acme.GetNonceAsync();

                    var acct = await acme.CreateAccountAsync(new[] { "mailto:[email protected]" });

                    acme.Account = acct;

                    var dnsIds = new[] { "foo.mock.acme2.zyborg.io" };
                    var order  = await acme.CreateOrderAsync(dnsIds);

                    Assert.IsNotNull(order?.OrderUrl);

                    var order2 = await acme.GetOrderDetailsAsync(order.OrderUrl);

                    Assert.AreEqual(order?.Payload?.Finalize, order2?.Payload?.Finalize);
                }
            }
        }
示例#2
0
        protected async Task <bool> ResolveChallenges(AcmeProtocolClient acme)
        {
            if (AcmeState.PendingStatus == _state.Order?.Payload?.Status)
            {
                _logger.LogInformation("Order is pending, resolving Authorizations");
                if (_state.Authorizations == null)
                {
                    _state.Authorizations = new Dictionary <string, Authorization>();
                }
                foreach (var authzUrl in _state.Order.Payload.Authorizations)
                {
                    var authz = await acme.GetAuthorizationDetailsAsync(authzUrl);

                    _state.Authorizations[authzUrl] = authz;

                    if (AcmeState.PendingStatus == authz.Status)
                    {
                        foreach (var chlng in authz.Challenges)
                        {
                            if (string.IsNullOrEmpty(_options.ChallengeType) ||
                                _options.ChallengeType == chlng.Type)
                            {
                                var chlngValidation = AuthorizationDecoder.DecodeChallengeValidation(
                                    authz, chlng.Type, acme.Signer);
                                if (_options.ChallengeHandler(_services, chlngValidation))
                                {
                                    _logger.LogInformation("Challenge Handler has handled challenge:");
                                    _logger.LogInformation(JsonConvert.SerializeObject(chlngValidation, Formatting.Indented));
                                    var chlngUpdated = await acme.AnswerChallengeAsync(chlng.Url);

                                    if (chlngUpdated.Error != null)
                                    {
                                        _logger.LogError("Submitting Challenge Answer reported an error:");
                                        _logger.LogError(JsonConvert.SerializeObject(chlngUpdated.Error));
                                    }
                                }

                                _logger.LogInformation("Refreshing Authorization status");
                                authz = await acme.GetAuthorizationDetailsAsync(authzUrl);

                                if (AcmeState.PendingStatus != authz.Status)
                                {
                                    break;
                                }
                            }
                        }
                    }
                }
                Save(_state.AuthorizationsFile, _state.Authorizations);

                _logger.LogInformation("Refreshing Order status");
                _state.Order = await acme.GetOrderDetailsAsync(_state.Order.OrderUrl, _state.Order);

                Save(_state.OrderFile, _state.Order);
            }
            return(true);
        }
        public async Task <ProcessStepResult> CompleteCertificateRequest(ILog log, CertRequestConfig config, string orderId, string pwd)
        {
            var order = await _client.GetOrderDetailsAsync("");

            _ = await _client.GetOrderCertificateAsync(order);

            return(new ProcessStepResult {
                IsSuccess = true
            });
        }
示例#4
0
        protected async Task <bool> ResolveOrder(AcmeProtocolClient acme)
        {
            var now = DateTime.Now;

            if (!string.IsNullOrEmpty(_state.Order?.OrderUrl))
            {
                _logger.LogInformation("Existing Order found; refreshing");
                _state.Order = await acme.GetOrderDetailsAsync(
                    _state.Order.OrderUrl, _state.Order);
            }

            if (_state.Order?.Payload?.Error != null)
            {
                _logger.LogWarning("Existing Order reported an Error:");
                _logger.LogWarning(JsonConvert.SerializeObject(_state.Order.Payload.Error));
                _logger.LogWarning("Resetting existing order");
                _state.Order = null;
            }

            if (AcmeState.InvalidStatus == _state.Order?.Payload.Status)
            {
                _logger.LogInformation("Existing Order is INVALID; resetting");
                _state.Order = null;
            }

            if (!DateTime.TryParse(_state.Order?.Payload?.Expires, out var orderExpires) ||
                orderExpires < now)
            {
                _logger.LogInformation("Existing Order is EXPIRED; resetting");
                _state.Order = null;
            }

            if (DateTime.TryParse(_state.Order?.Payload?.NotAfter, out var orderNotAfter) &&
                orderNotAfter < now)
            {
                _logger.LogInformation("Existing Order is OUT-OF-DATE; resetting");
                _state.Order = null;
            }

            if (_state.Order?.Payload == null)
            {
                _logger.LogInformation("Creating NEW Order");
                _state.Order = await acme.CreateOrderAsync(_options.DnsNames);
            }

            Save(_state.OrderFile, _state.Order);
            return(true);
        }
示例#5
0
        private async void submitDnsAnswerButton_Click(object sender, EventArgs e)
        {
            var url = ResolveCaServerEndpoint();

            if (url == null)
            {
                return;
            }

            await InvokeWithWaitCursor(async() =>
            {
                var signer = new PkiJwsTool(256);
                signer.Import(_account.JwsSigner);

                using (var acme = new AcmeProtocolClient(url, signer: signer,
                                                         acct: _account.Details))
                {
                    var dir        = await acme.GetDirectoryAsync();
                    acme.Directory = dir;

                    await acme.GetNonceAsync();

                    var dnsChallenge = SelectedAuthorization.Details.Challenges.First(
                        x => x.Type == Dns01ChallengeValidationDetails.Dns01ChallengeType);

                    var updatedDnsChallenge = await acme.AnswerChallengeAsync(dnsChallenge.Url);
                    var details             = await acme.GetOrderDetailsAsync(
                        _lastOrder.Details.OrderUrl ?? _lastOrder.FirstOrderUrl);
                    _lastOrder.Details = details;
                    Repo.Saveorder(_lastOrder);

                    RebindOrderControls();
                    SetStatus("DNS Challenge Answered; Order refreshed and saved");

                    await RefreshOrderAuthorizations(acme);
                }

                Repo.Saveorder(_lastOrder);
                RebindOrderControls();
                SetStatus("DNS Challenge Answered; Order details and Authorizations refreshed and saved");

                await DecodeOrderAuthorizationChallenges(signer);

                Repo.Saveorder(_lastOrder);
                RebindOrderControls();
                SetStatus("DNS Challenge Answered; Order details and Authorizations refreshed, Challenges decoded and saved");
            });
        }
示例#6
0
        internal async Task <OrderDetails> SubmitCsr(OrderDetails details, byte[] csr)
        {
            details = await Retry(() => _client.FinalizeOrderAsync(details.Payload.Finalize, csr));

            var tries = 1;

            while (
                details.Payload.Status == OrderPending ||
                details.Payload.Status == OrderProcessing)
            {
                await Task.Delay(_settings.Acme.RetryInterval * 1000);

                _log.Debug("Refreshing order status ({tries}/{count})", tries, _settings.Acme.RetryCount);
                var update = await Retry(() => _client.GetOrderDetailsAsync(details.OrderUrl));

                details.Payload = update.Payload;
                tries          += 1;
                if (tries > _settings.Acme.RetryCount)
                {
                    break;
                }
            }
            return(details);
        }
示例#7
0
 internal OrderDetails UpdateOrder(string orderUrl)
 {
     return(Retry(() => _client.GetOrderDetailsAsync(orderUrl).Result));
 }
示例#8
0
        protected async Task <bool> ResolveCertificate(AcmeProtocolClient acme)
        {
            if (_state.Certificate != null)
            {
                _logger.LogInformation("Certificate is already resolved");
                return(true);
            }

            CertPrivateKey key = null;

            _logger.LogInformation("Refreshing Order status");
            _state.Order = await acme.GetOrderDetailsAsync(_state.Order.OrderUrl, _state.Order);

            Save(_state.OrderFile, _state.Order);

            if (AcmeState.PendingStatus == _state.Order.Payload.Status)
            {
                _logger.LogInformation("Generating CSR");
                byte[] csr;
                switch (_options.CertificateKeyAlgor)
                {
                case "rsa":
                    key = CertHelper.GenerateRsaPrivateKey(
                        _options.CertificateKeySize ?? AcmeOptions.DefaultRsaKeySize);
                    csr = CertHelper.GenerateRsaCsr(_options.DnsNames, key);
                    break;

                case "ec":
                    key = CertHelper.GenerateEcPrivateKey(
                        _options.CertificateKeySize ?? AcmeOptions.DefaultEcKeySize);
                    csr = CertHelper.GenerateEcCsr(_options.DnsNames, key);
                    break;

                default:
                    throw new Exception("Unknown Certificate Key Algorithm: "
                                        + _options.CertificateKeyAlgor);
                }

                using (var keyPem = new MemoryStream())
                {
                    CertHelper.ExportPrivateKey(key, EncodingFormat.PEM, keyPem);
                    keyPem.Position = 0L;
                    Save(_state.CertificateKeysFile, keyPem);
                }
                Save(_state.CertificateRequestFile, csr);

                _logger.LogInformation("Finalizing Order");
                _state.Order = await acme.FinalizeOrderAsync(_state.Order.Payload.Finalize, csr);

                Save(_state.OrderFile, _state.Order);
            }

            if (AcmeState.ValidStatus != _state.Order.Payload.Status)
            {
                _logger.LogWarning("Order is NOT VALID");
                return(false);
            }

            if (string.IsNullOrEmpty(_state.Order.Payload.Certificate))
            {
                _logger.LogWarning("Order Certificate is NOT READY YET");
                var now = DateTime.Now;
                do
                {
                    _logger.LogInformation("Waiting...");
                    // We wait in 5s increments
                    await Task.Delay(5000);

                    _state.Order = await acme.GetOrderDetailsAsync(_state.Order.OrderUrl, _state.Order);

                    Save(_state.OrderFile, _state.Order);

                    if (!string.IsNullOrEmpty(_state.Order.Payload.Certificate))
                    {
                        break;
                    }

                    if (DateTime.Now < now.AddSeconds(_options.WaitForCertificate))
                    {
                        _logger.LogWarning("Timed Out!");
                        return(false);
                    }
                } while (true);
            }

            _logger.LogInformation("Retreiving Certificate");
            var certBytes = await acme.GetOrderCertificateAsync(_state.Order);

            Save(_state.CertificateChainFile, certBytes);

            if (key == null)
            {
                _logger.LogInformation("Loading private key");
                key = CertHelper.ImportPrivateKey(EncodingFormat.PEM,
                                                  Load <Stream>(_state.CertificateKeysFile).value);
            }

            using (var crtStream = new MemoryStream(certBytes))
                using (var pfxStream = new MemoryStream())
                {
                    _logger.LogInformation("Reading in Certificate chain (PEM)");
                    var cert = CertHelper.ImportCertificate(EncodingFormat.PEM, crtStream);
                    _logger.LogInformation("Writing out Certificate archive (PKCS12)");
                    CertHelper.ExportArchive(key, new[] { cert }, ArchiveFormat.PKCS12, pfxStream);
                    pfxStream.Position = 0L;
                    Save(_state.CertificateFile, pfxStream);
                }

            _logger.LogInformation("Loading PKCS12 archive as active certificate");
            _state.Certificate = new X509Certificate2(Load <byte[]>(_state.CertificateFile).value);

            return(true);
        }
示例#9
0
        public async Task <IActionResult> Post([FromBody] DomainRequest request)
        {
            if (!IsValidDomain(request.Domain))
            {
                return(BadRequest(new DomainResponse {
                    Error = "Invalid domain"
                }));
            }

            var domain    = string.Join(".", request.Domain.Split(".").TakeLast(2));
            var subDomain = string.Join(".", request.Domain.Split(".").SkipLast(2));

            var credentials = new AzureCredentialsFactory()
                              .FromServicePrincipal(
                _configuration["Azure:ClientId"],
                _configuration["Azure:ClientSecret"],
                _configuration["Azure:TenantId"],
                AzureEnvironment.AzureGlobalCloud
                );

            var azure = Azure
                        .Configure()
                        .WithRetryPolicy(new RetryPolicy(new TransientErrorIgnoreStrategy(), 0))
                        .Authenticate(credentials)
                        .WithSubscription(_configuration["Azure:SubscriptionId"]);

            var webApp = await azure.AppServices.WebApps.GetByIdAsync(
                _configuration["Azure:AppId"]);

            try
            {
                webApp.Update()
                .DefineHostnameBinding()
                .WithThirdPartyDomain(domain)
                .WithSubDomain(subDomain)
                .WithDnsRecordType(CustomHostNameDnsRecordType.CName)
                .Attach()
                .Apply();
            }
            catch (Exception e)
            {
                return(BadRequest(new DomainResponse {
                    Error = "Unable to validate domain ownership"
                }));
            }

            _ = Task.Run(async() =>
            {
                using var airtableBase = new AirtableBase(_configuration["Airtable:Key"], _configuration["Airtable:Base"]);
                try
                {
                    HttpClient httpClient = new HttpClient {
                        BaseAddress = new Uri(_configuration["Acme:Endpoint"])
                    };
                    AcmeProtocolClient acme = new AcmeProtocolClient(httpClient, usePostAsGet: true);

                    var acmeDir    = await acme.GetDirectoryAsync();
                    acme.Directory = acmeDir;

                    await acme.GetNonceAsync();

                    var account  = await acme.CreateAccountAsync(new[] { "mailto:" + _configuration["Acme:Email"] }, true);
                    acme.Account = account;

                    var order = await acme.CreateOrderAsync(new[] { request.Domain });
                    if (order.Payload.Status == "invalid")
                    {
                        return;
                    }

                    var authorizationUrl = order.Payload.Authorizations.FirstOrDefault();
                    if (string.IsNullOrEmpty(authorizationUrl))
                    {
                        return;
                    }
                    var authorization = await acme.GetAuthorizationDetailsAsync(authorizationUrl);

                    foreach (var challenge in authorization.Challenges.Where(x => x.Type == "http-01").ToList())
                    {
                        var challengeValidationDetails = (Http01ChallengeValidationDetails)
                                                         AuthorizationDecoder.DecodeChallengeValidation(authorization, challenge.Type, acme.Signer);

                        var path        = challengeValidationDetails.HttpResourcePath;
                        var token       = path.Split("/", StringSplitOptions.RemoveEmptyEntries).Last();
                        var value       = challengeValidationDetails.HttpResourceValue;
                        var contentType = challengeValidationDetails.HttpResourceContentType;

                        await airtableBase.CreateRecord("Acme", new Fields
                        {
                            FieldsCollection = new Dictionary <string, object>
                            {
                                ["Token"]       = token,
                                ["Value"]       = value,
                                ["ContentType"] = contentType
                            }
                        });

                        await Task.Delay(10 * 1000);
                        var challengeUpdated = await acme.AnswerChallengeAsync(challenge.Url);
                    }

                    //Wait for challenge to be resolved
                    var waitUntil = DateTime.Now.AddSeconds(300);
                    Authorization authorizationUpdated;
                    do
                    {
                        await Task.Delay(10 * 1000);
                        authorizationUpdated = await acme.GetAuthorizationDetailsAsync(authorizationUrl);
                    } while (authorizationUpdated.Status != "valid" && DateTime.Now < waitUntil);

                    if (authorizationUpdated.Status != "valid")
                    {
                        return;
                    }

                    //Generate certificate private key and CSR (Certificate signing request)
                    var keyPair = PkiKeyPair.GenerateEcdsaKeyPair(256);
                    var csr     = new PkiCertificateSigningRequest($"CN={request.Domain}", keyPair, PkiHashAlgorithm.Sha256);
                    var certCsr = csr.ExportSigningRequest(PkiEncodingFormat.Der);

                    order = await acme.FinalizeOrderAsync(order.Payload.Finalize, certCsr);
                    if (order.Payload.Status != "valid")
                    {
                        return;
                    }

                    if (string.IsNullOrEmpty(order.Payload.Certificate))
                    {
                        //Wait for certificate
                        var waitUntil2 = DateTime.Now.AddSeconds(300);
                        while (DateTime.Now < waitUntil2)
                        {
                            await Task.Delay(10 * 1000);
                            order = await acme.GetOrderDetailsAsync(order.OrderUrl, existing: order);

                            if (!string.IsNullOrEmpty(order.Payload.Certificate))
                            {
                                break;
                            }
                        }
                    }
                    if (string.IsNullOrEmpty(order.Payload.Certificate))
                    {
                        return;
                    }

                    var certResp = await acme.GetAsync(order.Payload.Certificate);
                    if (!certResp.IsSuccessStatusCode)
                    {
                        return;
                    }

                    var certByteArray = await certResp.Content.ReadAsByteArrayAsync();

                    //Export PFX file
                    var pfxPassword = _configuration["Acme:PfxPassword"];
                    var privateKey  = keyPair.PrivateKey;

                    using var cert = new X509Certificate2(certByteArray);

                    X509Chain chain = new X509Chain();
                    chain.Build(cert);
                    List <PkiCertificate> chainList = new List <PkiCertificate>();
                    foreach (var e in chain.ChainElements)
                    {
                        chainList.Add(PkiCertificate.From(e.Certificate));
                    }

                    var pfx = chainList[0].Export(PkiArchiveFormat.Pkcs12,
                                                  chain: chainList.Skip(1),
                                                  privateKey: privateKey,
                                                  password: pfxPassword?.ToCharArray());

                    webApp.Update()
                    .DefineSslBinding()
                    .ForHostname(request.Domain)
                    .WithPfxByteArrayToUpload(pfx, pfxPassword)
                    .WithSniBasedSsl()
                    .Attach()
                    .Apply();
                }
                catch (Exception e)
                {
                    await airtableBase.CreateRecord("Logs", new Fields
                    {
                        FieldsCollection = new Dictionary <string, object>
                        {
                            ["Hostname"] = request.Domain,
                            ["Event"]    = "exception-thrown",
                            ["Data"]     = JsonConvert.SerializeObject(e)
                        }
                    });
                }
            });



            return(Ok(new DomainResponse {
                IsSuccessful = true
            }));
        }