internal async Task UpdateAccount(AcmeProtocolClient client) { var account = await Retry(client, () => client.CheckAccountAsync()); _accountManager.CurrentAccount = account; client.Account = account; }
public async Task <AcmeProtocolClient> CreateClientAsync() { var account = LoadState <AccountDetails>("account.json"); var accountKey = LoadState <AccountKey>("account_key.json"); var directory = LoadState <ServiceDirectory>("directory.json"); var acmeProtocolClient = new AcmeProtocolClient(_acmeEndpoint, directory, account, accountKey?.GenerateSigner()); if (directory == null) { directory = await acmeProtocolClient.GetDirectoryAsync(); acmeProtocolClient.Directory = directory; } await acmeProtocolClient.GetNonceAsync(); if (acmeProtocolClient.Account == null) { account = await acmeProtocolClient.CreateAccountAsync(new[] { "mailto:" + Settings.Default.Contacts }, true); accountKey = new AccountKey { KeyType = acmeProtocolClient.Signer.JwsAlg, KeyExport = acmeProtocolClient.Signer.Export() }; SaveState(account, "account.json"); SaveState(accountKey, "account_key.json"); acmeProtocolClient.Account = account; } return(acmeProtocolClient); }
/// <summary> /// Load the account, signer and directory /// </summary> /// <returns></returns> internal async Task ConfigureAcmeClient() { var httpClient = _proxyService.GetHttpClient(); httpClient.BaseAddress = _settings.BaseUri; _log.Verbose("Constructing ACME protocol client..."); var client = new AcmeProtocolClient(httpClient, usePostAsGet: _settings.Acme.PostAsGet); client.Directory = await EnsureServiceDirectory(client); // Try to load prexisting account if (_accountManager.CurrentAccount != null && _accountManager.CurrentSigner != null) { _log.Verbose("Using existing ACME account"); await client.ChangeAccountKeyAsync(_accountManager.CurrentSigner.JwsTool()); client.Account = _accountManager.CurrentAccount; } else { _log.Verbose("No account found, creating new one"); await SetupAccount(client); } if (client.Account == null) { throw new Exception("AcmeClient was unable to find or create an account"); } _client = client; _log.Verbose("ACME client initialized"); }
protected async Task <bool> ResolveAccount(AcmeProtocolClient acme) { // TODO: All this ASSUMES a fixed key type/size for now if (_state.Account == null || _state.AccountKey == null) { var contacts = _options.AccountContactEmails.Select(x => $"mailto:{x}"); _logger.LogInformation("Creating ACME Account"); _state.Account = await acme.CreateAccountAsync( contacts : contacts, termsOfServiceAgreed : _options.AcceptTermsOfService); _state.AccountKey = new ExamplesAccountKey { KeyType = acme.Signer.JwsAlg, KeyExport = acme.Signer.Export(), }; Save(_state.AccountFile, _state.Account); Save(_state.AccountKeyFile, _state.AccountKey); acme.Account = _state.Account; } else { acme.Account = _state.Account; acme.Signer.Import(_state.AccountKey.KeyExport); } return(true); }
/// <summary> /// Make sure that we find a service directory /// </summary> /// <param name="client"></param> /// <returns></returns> internal async Task <acme.ServiceDirectory> EnsureServiceDirectory(AcmeProtocolClient client) { acme.ServiceDirectory?directory; try { _log.Verbose("Getting service directory..."); directory = await Backoff(async() => await client.GetDirectoryAsync("directory")); if (directory != null) { return(directory); } } catch { } // Perhaps the BaseUri *is* the directory, such // as implemented by Digicert (#1434) directory = await Backoff(async() => await client.GetDirectoryAsync("")); if (directory != null) { return(directory); } throw new Exception("Unable to get service directory"); }
public async Task <bool> InitProvider(ILog log = null, Certify.Models.AccountDetails account = null) { if (log != null) { _log = log; } _loggingHandler = new LoggingHandler(new HttpClientHandler(), _log); var customHttpClient = new System.Net.Http.HttpClient(_loggingHandler); customHttpClient.DefaultRequestHeaders.Add("User-Agent", _userAgentName); _httpClient = new HttpClient(); await LoadSettings(); _client = new AcmeProtocolClient(_httpClient); _client.Directory = await _client.GetDirectoryAsync(); if (_client.Account == null) { throw new Exception("AcmeClient was unable to find or create an account"); } return(true); }
internal async Task ConfigureAcmeClient() { var httpClient = _proxyService.GetHttpClient(); httpClient.BaseAddress = _settings.BaseUri; _log.Verbose("Loading ACME account signer..."); IJwsTool signer = null; var accountSigner = AccountSigner; if (accountSigner != null) { signer = accountSigner.JwsTool(); } _log.Verbose("Constructing ACME protocol client..."); try { _client = new AcmeProtocolClient( httpClient, signer: signer, usePostAsGet: _settings.Acme.PostAsGet); } catch (CryptographicException) { if (signer == null) { // There has been a problem generate a signer for the // new account, possibly because some EC curve is not // on available on the system? So we give it another // shot with a less fancy RSA signer _log.Verbose("First chance error generating new signer, retrying with RSA instead of ECC"); signer = new RSJwsTool { KeySize = _settings.Security.RSAKeyBits }; signer.Init(); _client = new AcmeProtocolClient( httpClient, signer: signer, usePostAsGet: _settings.Acme.PostAsGet); } else { throw; } } _client.BeforeHttpSend = (x, r) => _log.Debug("Send {method} request to {uri}", r.Method, r.RequestUri); _client.AfterHttpSend = (x, r) => _log.Verbose("Request completed with status {s}", r.StatusCode); _client.Directory = await _client.GetDirectoryAsync(); await _client.GetNonceAsync(); _client.Account = await LoadAccount(signer); if (_client.Account == null) { throw new Exception("AcmeClient was unable to find or create an account"); } }
/// <summary> /// Attempt to create an account using specific parameters /// </summary> /// <param name="client"></param> /// <param name="signer"></param> /// <param name="contacts"></param> /// <param name="eabAlg"></param> /// <param name="eabKid"></param> /// <param name="eabKey"></param> /// <returns></returns> private async Task CreateAccount( AcmeProtocolClient client, AccountSigner signer, string[]?contacts, string eabAlg, string?eabKid, string?eabKey) { if (client.Account != null) { throw new Exception("Client already has an account!"); } ExternalAccountBinding?externalAccount = null; if (!string.IsNullOrWhiteSpace(eabKey) && !string.IsNullOrWhiteSpace(eabKid)) { externalAccount = new ExternalAccountBinding( eabAlg, JsonConvert.SerializeObject( signer.JwsTool().ExportJwk(), Formatting.None), eabKid, eabKey, client.Directory?.NewAccount ?? ""); } await client.ChangeAccountKeyAsync(signer.JwsTool()); client.Account = await Retry(client, () => client.CreateAccountAsync( contacts, termsOfServiceAgreed: true, externalAccountBinding: externalAccount?.Payload() ?? null)); _accountManager.CurrentSigner = signer; _accountManager.CurrentAccount = client.Account; }
public async Task GetAuthzWildcard() { 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[] { "*.mock.acme2.zyborg.io" }; var order = await acme.CreateOrderAsync(dnsIds); Assert.IsNotNull(order?.OrderUrl); Assert.AreEqual(dnsIds.Length, order.Payload.Authorizations?.Length); Assert.AreEqual(dnsIds.Length, order.Payload.Identifiers?.Length); var authz = await acme.GetAuthorizationDetailsAsync( order.Payload.Authorizations[0]); Assert.IsNotNull(authz); Assert.IsTrue(authz.Wildcard ?? false); Assert.AreEqual(dnsIds[0], authz.Identifier.Value); } } }
public async Task NewNonce() { using (var http = _server.CreateClient()) { var dir = await GetDir(); var requ = new HttpRequestMessage( HttpMethod.Head, dir.NewNonce); var resp = await http.SendAsync(requ); resp.EnsureSuccessStatusCode(); Assert.AreEqual(HttpStatusCode.NoContent, resp.StatusCode); Assert.IsTrue(resp.Headers.Contains(Constants.ReplayNonceHeaderName), "contains nonce response header"); using (var acme = new AcmeProtocolClient(http, dir)) { Assert.IsNull(acme.NextNonce); await acme.GetNonceAsync(); Assert.IsNotNull(acme.NextNonce); } } }
private async void refreshAccountButton_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 details = await acme.UpdateAccountAsync(); _account.Details = details; Repo.SaveAccount(_account); } RebindAccountControls(); SetStatus("Account refreshed and saved"); }); }
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); } } }
public async Task <AcmeProtocolClient> CreateClientAsync() { var account = await LoadStateAsync <AccountDetails>(ACME_ACCOUNT_NAME); var accountKey = await LoadStateAsync <AccountKey>(ACME_ACCOUNT_KEY_NAME); var acmeProtocolClient = new AcmeProtocolClient(_acmeEndpoint, null, account, accountKey?.GenerateSigner()); var directory = await acmeProtocolClient.GetDirectoryAsync(); acmeProtocolClient.Directory = directory; await acmeProtocolClient.GetNonceAsync(); if (acmeProtocolClient.Account == null) { account = await acmeProtocolClient.CreateAccountAsync(new[] { "mailto:" + _configuration.AcmeAccountEmail }, true); acmeProtocolClient.Account = account; accountKey = new AccountKey { KeyType = acmeProtocolClient.Signer.JwsAlg, KeyExport = acmeProtocolClient.Signer.Export() }; await SaveStateAsync(ACME_ACCOUNT_NAME, account); await SaveStateAsync(ACME_ACCOUNT_KEY_NAME, accountKey); } return(acmeProtocolClient); }
public async Task <AcmeProtocolClient> CreateClientAsync() { var account = LoadState <AccountDetails>("account.json"); var accountKey = LoadState <AccountKey>("account_key.json"); var directory = LoadState <ServiceDirectory>("directory.json"); var acmeProtocolClient = new AcmeProtocolClient(_baseUri, directory, account, accountKey?.GenerateSigner(), usePostAsGet: true); if (directory == null) { try { directory = await acmeProtocolClient.GetDirectoryAsync(); } catch (AcmeProtocolException) { acmeProtocolClient.Directory.Directory = ""; directory = await acmeProtocolClient.GetDirectoryAsync(); } SaveState(directory, "directory.json"); acmeProtocolClient.Directory = directory; } await acmeProtocolClient.GetNonceAsync(); if (acmeProtocolClient.Account == null) { var externalAccountBinding = directory.Meta.ExternalAccountRequired ?? false?CreateExternalAccountBinding(acmeProtocolClient) : null; account = await acmeProtocolClient.CreateAccountAsync(new[] { $"mailto:{_options.Contacts}" }, true, externalAccountBinding); accountKey = new AccountKey { KeyType = acmeProtocolClient.Signer.JwsAlg, KeyExport = acmeProtocolClient.Signer.Export() }; SaveState(account, "account.json"); SaveState(accountKey, "account_key.json"); acmeProtocolClient.Account = account; } if (acmeProtocolClient.Account.Payload.Contact[0] != $"mailto:{_options.Contacts}") { account = await acmeProtocolClient.UpdateAccountAsync(new[] { $"mailto:{_options.Contacts}" }); SaveState(account, "account.json"); acmeProtocolClient.Account = account; } return(acmeProtocolClient); }
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 AddCertificateFunctions(IHttpClientFactory httpClientFactory, CertbotConfiguration configuration, LookupClient lookupClient, KeyVaultClient keyVaultClient, IAzure azure, BlobContainerClient blobContainerClient, IAcmeProtocolClientFactory acmeProtocolClientFactory) { _httpClientFactory = httpClientFactory; _configuration = configuration; _lookupClient = lookupClient; _keyVaultClient = keyVaultClient; _azure = azure; _blobContainerClient = blobContainerClient; _acmeProtocolClient = acmeProtocolClientFactory.CreateClientAsync().Result; }
private async void createAccountButton_Click(object sender, EventArgs e) { if (!agreeTosCheckbox.Checked) { MessageBox.Show("You must agree to the Terms of Service.", "Terms of Service", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } var url = ResolveCaServerEndpoint(); if (url == null) { return; } var contacts = ResolveEmailContacts(); if (contacts == null) { return; } await InvokeWithWaitCursor(async() => { var signer = new PkiJwsTool(256); signer.Init(); using (var acme = new AcmeProtocolClient(url, signer: signer)) { var dir = await acme.GetDirectoryAsync(); acme.Directory = dir; await acme.GetNonceAsync(); var details = await acme.CreateAccountAsync( contacts, agreeTosCheckbox.Checked, throwOnExistingAccount: true); var acct = new DbAccount { AcmeServerEndpoint = url.ToString(), JwsSigner = signer.Export(), Details = details, }; Repo.SaveAccount(acct); _account = acct; } RebindAccountControls(); SetStatus("Account created and saved"); }); }
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"); }); }
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); }
private object CreateExternalAccountBinding(AcmeProtocolClient acmeProtocolClient) { byte[] HmacSignature(byte[] x) { var hmacKeyBytes = CryptoHelper.Base64.UrlDecode(_options.ExternalAccountBinding.HmacKey); var hmac = (HMAC)(_options.ExternalAccountBinding.Algorithm switch { "HS256" => new HMACSHA256(hmacKeyBytes), "HS384" => new HMACSHA384(hmacKeyBytes), "HS512" => new HMACSHA512(hmacKeyBytes), _ => throw new NotSupportedException($"The signature algorithm {_options.ExternalAccountBinding.Algorithm} is not supported.") }); return(hmac.ComputeHash(x)); }
private async Task <AccountDetails?> LoadAccount(AcmeProtocolClient client, IJwsTool?signer) { AccountDetails?account = null; if (File.Exists(AccountPath)) { if (signer != null) { _log.Debug("Loading account information from {registrationPath}", AccountPath); account = JsonConvert.DeserializeObject <AccountDetails>(File.ReadAllText(AccountPath)); client.Account = account; // Maybe we should update the account details // on every start of the program to figure out // if it hasn't been suspended or cancelled? // UpdateAccount(); } else { _log.Error("Account found but no valid signer could be loaded"); } } else { var contacts = await GetContacts(); var(_, filename, content) = await client.GetTermsOfServiceAsync(); if (!string.IsNullOrEmpty(filename)) { if (!await AcceptTos(filename, content)) { return(null); } } account = await client.CreateAccountAsync(contacts, termsOfServiceAgreed : true); _log.Debug("Saving registration"); var accountKey = new AccountSigner { KeyType = client.Signer.JwsAlg, KeyExport = client.Signer.Export(), }; AccountSigner = accountKey; File.WriteAllText(AccountPath, JsonConvert.SerializeObject(account)); } return(account); }
public async Task NewAccount() { using (var http = _server.CreateClient()) { var dir = await GetDir(); using (var acme = new AcmeProtocolClient(http, dir)) { Assert.IsNull(acme.NextNonce); await acme.GetNonceAsync(); Assert.IsNotNull(acme.NextNonce); await acme.CreateAccountAsync(new[] { "*****@*****.**" }); } } }
private async Task RefreshOrderAuthorizations(AcmeProtocolClient acme) { var details = _lastOrder.Details; var authzs = new List <DbAuthz>(); foreach (var authzUrl in details.Payload.Authorizations) { var authzDetails = await acme.GetAuthorizationDetailsAsync(authzUrl); authzs.Add(new DbAuthz { Url = authzUrl, Details = authzDetails, }); } _lastOrder.Authorizations = authzs.ToArray(); }
public async Task GetAuthzMulti() { 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[] { "foo1.mock.acme2.zyborg.io", "foo2.mock.acme2.zyborg.io", "foo3.mock.acme2.zyborg.io", }; var order = await acme.CreateOrderAsync(dnsIds); Assert.IsNotNull(order?.OrderUrl); Assert.AreEqual(dnsIds.Length, order.Payload.Authorizations?.Length); Assert.AreEqual(dnsIds.Length, order.Payload.Identifiers?.Length); var dnsIdsList = new List <string>(dnsIds); foreach (var authzUrl in order.Payload.Authorizations) { var authz = await acme.GetAuthorizationDetailsAsync(authzUrl); Assert.IsNotNull(authz); Assert.IsFalse(authz.Wildcard ?? false); Assert.IsTrue(dnsIdsList.Remove(authz.Identifier.Value), "DNS Identifiers contains authz DNS Identifier"); } Assert.AreEqual(0, dnsIdsList.Count); } } }
public async Task AnswerChallenge() { using (var http = _server.CreateClient()) { var dir = await GetDir(); var dnsIds = new[] { "foo.mock.acme2.zyborg.io" }; 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 order = await acme.CreateOrderAsync(dnsIds); Assert.IsNotNull(order?.OrderUrl); Assert.AreEqual(dnsIds.Length, order.Payload.Authorizations?.Length); Assert.AreEqual(dnsIds.Length, order.Payload.Identifiers?.Length); var authzUrl = order.Payload.Authorizations[0]; var authz = await acme.GetAuthorizationDetailsAsync(authzUrl); Assert.IsNotNull(authz); Assert.IsFalse(authz.Wildcard ?? false); Assert.AreEqual(dnsIds[0], authz.Identifier.Value); foreach (var chlng in authz.Challenges) { var chlng2 = await acme.AnswerChallengeAsync(chlng.Url); Assert.IsNotNull(chlng2); Assert.AreEqual("valid", chlng2.Status); } } } }
async Task GetTermsOfService() { var signer = new ACMESharp.Crypto.JOSE.Impl.RSJwsTool(); var acmeUrl = new Uri(Program.LetsEncryptV2StagingEndpoint); _http = new HttpClient(); _http.BaseAddress = acmeUrl; WriteLine("getting from: " + acmeUrl); using (var acme = new AcmeProtocolClient(_http, signer: signer)) { acme.BeforeAcmeSign = (s, o) => WriteLine($"BEFORE({s}, {JsonConvert.SerializeObject(o)})"); var dir = await acme.GetDirectoryAsync(); WriteLine("Got Directory: " + dir); WriteLine("TOS: " + dir.Meta?.TermsOfService); } }
/// <summary> /// According to the ACME standard, we SHOULD retry calls /// if there is an invalid nonce. TODO: check for the proper /// exception feedback, now *any* failed request is retried /// </summary> /// <typeparam name="T"></typeparam> /// <param name="executor"></param> /// <returns></returns> private async Task <T> Retry <T>(AcmeProtocolClient client, Func <Task <T> > executor, int attempt = 0) { if (attempt == 0) { await _requestLock.WaitAsync(); } try { if (string.IsNullOrEmpty(client.NextNonce)) { await client.GetNonceAsync(); } return(await executor()); } catch (AcmeProtocolException apex) { if (attempt < 3 && apex.ProblemType == ProblemType.BadNonce) { _log.Warning("First chance error calling into ACME server, retrying with new nonce..."); await client.GetNonceAsync(); return(await Retry(client, executor, attempt + 1)); } else if (apex.ProblemType == ProblemType.UserActionRequired) { _log.Error("{detail}: {instance}", apex.ProblemDetail, apex.ProblemInstance); throw; } else { throw; } } finally { if (attempt == 0) { _requestLock.Release(); } } }
private async Task <bool> ConfigureAcmeClient() { var httpClientHandler = new HttpClientHandler() { Proxy = _proxyService.GetWebProxy(), }; var httpClient = new HttpClient(httpClientHandler) { BaseAddress = new Uri(_arguments.MainArguments.GetBaseUri()) }; _log.Verbose("Loading ACME account signer..."); IJwsTool signer = null; var accountSigner = AccountSigner; if (accountSigner != null) { signer = accountSigner.JwsTool(); } _log.Verbose("Constructing ACME protocol client..."); _client = new AcmeProtocolClient(httpClient, signer: signer) { BeforeHttpSend = (x, r) => { _log.Debug("Send {method} request to {uri}", r.Method, r.RequestUri); }, }; _client.Directory = await _client.GetDirectoryAsync(); await _client.GetNonceAsync(); _client.Account = await LoadAccount(signer); if (_client.Account == null) { throw new Exception("AcmeClient was unable to find or create an account"); } return(true); }
private static async Task <AcmeProtocolClient> CreateAcmeClientAsync() { var account = default(AccountDetails); var accountKey = default(AccountKey); var acmeDir = default(ServiceDirectory); LoadState(ref account, "account.json"); LoadState(ref accountKey, "account_key.json"); LoadState(ref acmeDir, "directory.json"); var acme = new AcmeProtocolClient(_acmeHttpClient, acmeDir, account, accountKey?.GenerateSigner()); if (acmeDir == null) { acmeDir = await acme.GetDirectoryAsync(); SaveState(acmeDir, "directory.json"); acme.Directory = acmeDir; } await acme.GetNonceAsync(); if (account == null || accountKey == null) { account = await acme.CreateAccountAsync(new[] { "mailto:" + Settings.Default.Contacts }, true); accountKey = new AccountKey { KeyType = acme.Signer.JwsAlg, KeyExport = acme.Signer.Export() }; SaveState(account, "account.json"); SaveState(accountKey, "account_key.json"); acme.Account = account; } return(acme); }
private async Task <ServiceDirectory> GetDir() { // var dir = new ServiceDirectory // { // Directory = $"{DefaultServerUrl}acme/directory", // NewNonce = $"{DefaultServerUrl}acme/new-nonce", // NewAccount = $"{DefaultServerUrl}acme/new-acct", // NewOrder = $"{DefaultServerUrl}acme/new-order", // }; // return Task.FromResult(dir); using (var http = _server.CreateClient()) { using (var acme = new AcmeProtocolClient(http)) { var dir = await acme.GetDirectoryAsync(); return(dir); } } }