private async Task PrepareHttpChallengeResponseAsync( IAuthorizationContext authorizationContext, string domainName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (_client == null) { throw new InvalidOperationException(); } var httpChallenge = await _client.CreateChallengeAsync(authorizationContext, ChallengeTypes.Http01); if (httpChallenge == null) { throw new InvalidOperationException( $"Did not receive challenge information for challenge type {ChallengeTypes.Http01}"); } var keyAuth = httpChallenge.KeyAuthz; _challengeStore.AddChallengeResponse(httpChallenge.Token, keyAuth); _logger.LogTrace("Waiting for server to start accepting HTTP requests"); await _appStarted.Task; _logger.LogTrace("Requesting server to validate HTTP challenge"); await _client.ValidateChallengeAsync(httpChallenge); }
private async void RefreshCertificates(object state) { _httpChallengeResponseStore.ClearPendingOrders(); var hostNames = _certificateSelector.GetCertificatesAboutToExpire(); if (hostNames.Length > 0) { var account = await _accountManager.GetAccountKey(); if (string.IsNullOrEmpty(account)) { _logger.LogError("Canot get Let´s Encrpyt account"); } else { try { foreach (var host in hostNames) { var acme = new AcmeContext(_options.AcmeServer, KeyFactory.FromPem(account)); _logger.LogInformation("LetsEncrypt: Creating order"); var order = await acme.NewOrder(new[] { host }); var authz = (await order.Authorizations()).First(); var httpChallenge = await authz.Http(); var orderInfo = new OrderInfo { Order = order, Challenge = httpChallenge, HostName = host }; _httpChallengeResponseStore.AddChallengeResponse(httpChallenge.Token, orderInfo); await httpChallenge.Validate(); } } catch (Exception ex) { _logger.LogError(ex.ToString()); throw; } } } }
protected override async Task ExecuteAsync(CancellationToken cancellationToken) { try { var directoryUri = new Uri(_options.Authority.DirectoryUri); var(acme, account) = await InitializeAccount(directoryUri, _options.Email, _options.AccountKey); // Todo: Save the account key for later use _logger?.LogWarning("Account key is not yet persisted for later use."); _options.AccountKey = acme.AccountKey.ToPem(); _logger?.LogDebug("Create new ACME order with http challenge for hostname {hostname}", _options.Hostname); var order = await acme.NewOrder(new[] { _options.Hostname }); var authorization = (await order.Authorizations()).First(); var httpChallenge = await authorization.Http(); // Save the expected response _responseStore.AddChallengeResponse(httpChallenge.Token, httpChallenge.KeyAuthz); _logger?.LogInformation("ACME http challenge initialized and tokens cached. About to execute http challenge."); // Execute http challenge await httpChallenge.Validate(); await httpChallenge.WaitForCompletion(TimeSpan.FromSeconds(1), _logger); // Download certificate, generate pfx and write to file var certificate = await order.GetFinalCertificate( _options.CsrInfo, _options.Hostname, _options.FriendlyName); _logger?.LogInformation("Certificate successfully downloaded and is about to get persisted."); _certificateSaver.Save(certificate, _options.Hostname); } catch (Exception ex) { _logger?.LogError(ex, "Error while running the ACME http challenge protocol procedure."); _errorReporter.ReportException(ex); } finally { // Stop intermediary application _logger?.LogInformation("Intermediary server for ACME challenge is shutting down..."); _application.StopApplication(); } }
private async Task ValidateDomainOwnershipAsync(IAuthorizationContext authorizationContext, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var authorization = await authorizationContext.Resource(); var domainName = authorization.Identifier.Value; if (authorization.Status == AuthorizationStatus.Valid) { // Short circuit if authorization is already complete return; } _logger.LogDebug("Requesting authorization to create certificate for {domainName}", domainName); cancellationToken.ThrowIfCancellationRequested(); var httpChallenge = await authorizationContext.Http(); cancellationToken.ThrowIfCancellationRequested(); if (httpChallenge == null) { throw new InvalidOperationException($"Did not receive challenge information for challenge type {ChallengeTypes.Http01}"); } var keyAuth = httpChallenge.KeyAuthz; _challengeStore.AddChallengeResponse(httpChallenge.Token, keyAuth); cancellationToken.ThrowIfCancellationRequested(); _logger.LogDebug("Requesting completion of challenge to prove ownership of domain {domainName}", domainName); var challenge = await httpChallenge.Validate(); var retries = 60; var delay = TimeSpan.FromSeconds(2); while (retries > 0) { retries--; cancellationToken.ThrowIfCancellationRequested(); authorization = await authorizationContext.Resource(); _logger.LogAcmeAction("GetAuthorization"); switch (authorization.Status) { case AuthorizationStatus.Valid: return; case AuthorizationStatus.Pending: await Task.Delay(delay); continue; case AuthorizationStatus.Invalid: throw InvalidAuthorizationError(authorization); case AuthorizationStatus.Revoked: throw new InvalidOperationException($"The authorization to verify domainName '{domainName}' has been revoked."); case AuthorizationStatus.Expired: throw new InvalidOperationException($"The authorization to verify domainName '{domainName}' has expired."); default: throw new ArgumentOutOfRangeException("Unexpected response from server while validating domain ownership."); } } throw new TimeoutException("Timed out waiting for domain ownership validation."); }
private async Task ValidateDomainOwnershipAsync(string hostName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); _logger.LogDebug("Requesting authorization to create certificates for {hostname}", hostName); var auth = await _client.NewAuthorization(new AuthorizationIdentifier { Type = AuthorizationIdentifierTypes.Dns, Value = hostName, }); _logger.LogResponse("NewAuthorization", auth); cancellationToken.ThrowIfCancellationRequested(); var httpChallenge = auth.Data.Challenges.FirstOrDefault(c => c.Type == ChallengeTypes.Http01); if (httpChallenge == null) { throw new InvalidOperationException($"Did not receive challenge information for challenge type {ChallengeTypes.Http01}"); } var keyAuth = _client.ComputeKeyAuthorization(httpChallenge); _challengeStore.AddChallengeResponse(httpChallenge.Token, keyAuth); cancellationToken.ThrowIfCancellationRequested(); _logger.LogDebug("Requesting completion of challenge to prove ownership of {hostname}", hostName); var challengeCompletion = await _client.CompleteChallenge(httpChallenge); _logger.LogResponse("CompleteChallenge", challengeCompletion); var retries = 60; var delay = TimeSpan.FromSeconds(2); AcmeResult <AuthorizationEntity> authorization; while (retries > 0) { retries--; cancellationToken.ThrowIfCancellationRequested(); authorization = await _client.GetAuthorization(challengeCompletion.Location); _logger.LogResponse("GetAuthorization", authorization); switch (authorization.Data.Status) { case EntityStatus.Valid: return; case EntityStatus.Pending: case EntityStatus.Processing: await Task.Delay(delay); continue; case EntityStatus.Invalid: throw InvalidAuthorizationError(hostName, authorization); case EntityStatus.Revoked: throw new InvalidOperationException($"The authorization to verify hostname '{hostName}' has been revoked."); case EntityStatus.Unknown: default: throw new ArgumentOutOfRangeException("Unexpected response from server while validating domain ownership."); } } throw new TimeoutException("Timed out waiting for domain ownership validation."); }