/// <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 <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); }
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); }
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"); } }
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 <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); }
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); }
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); try { client.Directory = await client.GetDirectoryAsync(); } catch (Exception) { // Perhaps the BaseUri *is* the directory, such // as implemented by Digicert (#1434) client.Directory.Directory = ""; client.Directory = await client.GetDirectoryAsync(); } // 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"); }
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"); }); }
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); } }
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); } } }
private async Task EnsureClient() { var caUri = new Uri(config.CertAuthorityUrl); var account = await accountStore.GetAccountAsync(); if (account != null && account.Account.Kid.Contains(caUri.Host) == false) { logger.LogWarning("Fetched account KID doesn't contain CA host, ignoring fetched account"); account = null; } var client = new AcmeProtocolClient(caUri, null, account?.Account, account?.Signer, usePostAsGet: true, logger: logger); client.Directory = await client.GetDirectoryAsync(); // get nonce, used to communicate w/ server await client.GetNonceAsync(); if (account == null) { // make request to create account var contactEmails = new[] { "mailto:" + config.ContactEmail }; var newAccount = await client.CreateAccountAsync(contactEmails, termsOfServiceAgreed : true); var accountKey = new AccountKey { KeyType = client.Signer.JwsAlg, KeyExport = client.Signer.Export() }; await accountStore.StoreAccountAsync(newAccount, accountKey); client.Account = newAccount; } this.client = client; }
private async void agreeTosLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { var url = ResolveCaServerEndpoint(); if (url == null) { return; } var signer = new PkiJwsTool(256); using (var acme = new AcmeProtocolClient(url, signer: signer)) { var dir = await acme.GetDirectoryAsync(); if (string.IsNullOrEmpty(dir.Meta.TermsOfService)) { MessageBox.Show("CA Server directory meta data contains no ToS link.", "Missing ToS", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } System.Diagnostics.Process.Start(dir.Meta.TermsOfService); } }
private async void createOrderButton_Click(object sender, EventArgs e) { var url = ResolveCaServerEndpoint(); if (url == null) { return; } var ids = ResolveDnsIdentifiers(); if (ids == null) { return; } var dateRange = ResolveOrderDateRange(); if (dateRange == 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.CreateOrderAsync(ids, dateRange.Value.notBefore, dateRange.Value.notAfter); var order = new DbOrder { FirstOrderUrl = details.OrderUrl, Details = details, }; Repo.Saveorder(order); _lastOrder = order; RebindOrderControls(); SetStatus("Order created and saved"); await RefreshOrderAuthorizations(acme); } Repo.Saveorder(_lastOrder); RebindOrderControls(); SetStatus("Order created and Authorization resolved and saved"); await DecodeOrderAuthorizationChallenges(signer); Repo.Saveorder(_lastOrder); RebindOrderControls(); SetStatus("Order created, Authorizations resolved and Challenges decoded and saved"); }); }
protected async Task DoTheWorkAsync(object state) { var cts = new CancellationTokenSource(); cts.CancelAfter(15000); try { var suggestion = await _registryClient.Registry.SuggestAsync(_gameConfiguration.PublicURL, cts.Token); if (suggestion != "localhost") { _options.DnsNames = new[] { suggestion } } ; else { if (!WarnedLocalMode) { _logger.LogWarning("registry reports we are not accessible on http TCP/80 on our public IP. If you're in development mode, this is fine."); } WarnedLocalMode = true; return; } } catch (Exception e) { _logger.LogError("** failed to get suggestion from registry: " + e); } if (_options.DnsNames == null) { return; } _logger.LogInformation("** Checking LetsEncrypt status *****************************************"); _logger.LogInformation($"DNS Names: {string.Join(",", _options.DnsNames)}"); if (_state.Certificate != null) { var now = DateTime.Now; if (_state.Certificate.NotAfter > now) { _logger.LogInformation("Existing certificate is Good!"); return; } else { _logger.LogWarning($"Existing Certificate is Expired! {_state.Certificate.NotAfter} > {now}"); } } else { _logger.LogWarning("Missing Certificate"); } try { Directory.Delete(_state.RootDir, true); } catch (Exception) { } Reinitialize(); try { var acmeUrl = new Uri(_options.CaUrl); using (var acme = new AcmeProtocolClient(acmeUrl)) { _state.ServiceDirectory = await acme.GetDirectoryAsync(); Save(_state.ServiceDirectoryFile, _state.ServiceDirectory); acme.Directory = _state.ServiceDirectory; Save(_state.TermsOfServiceFile, await acme.GetTermsOfServiceAsync()); await acme.GetNonceAsync(); if (!await ResolveAccount(acme)) { return; } if (!await ResolveOrder(acme)) { return; } if (!await ResolveChallenges(acme)) { return; } if (!await ResolveAuthorizations(acme)) { return; } if (!await ResolveCertificate(acme)) { return; } } } catch (Exception e) { _logger.LogWarning($"Exception while getting SSL Certificate: {e}"); } }
protected async Task DoTheWorkAsync(object state) { _logger.LogInformation("** DOING WORKING *****************************************"); _logger.LogInformation($"DNS Names: {string.Join(",", _options.DnsNames)}"); if (_state.Certificate != null) { var now = DateTime.Now; if (_state.Certificate.NotBefore > now && _state.Certificate.NotAfter < now) { _logger.LogInformation("Existing certificate is Good!"); return; } { _logger.LogWarning("Existing Certificate is Expired!"); } } else { _logger.LogWarning("Missing Certificate"); } var acmeUrl = new Uri(_options.CaUrl); using (var acme = new AcmeProtocolClient(acmeUrl)) { _state.ServiceDirectory = await acme.GetDirectoryAsync(); Save(_state.ServiceDirectoryFile, _state.ServiceDirectory); acme.Directory = _state.ServiceDirectory; Save(_state.TermsOfServiceFile, await acme.GetTermsOfServiceAsync()); await acme.GetNonceAsync(); if (!await ResolveAccount(acme)) { return; } if (!await ResolveOrder(acme)) { return; } if (!await ResolveChallenges(acme)) { return; } if (!await ResolveAuthorizations(acme)) { return; } if (!await ResolveCertificate(acme)) { return; } } }
async Task CreateAccount() { var acmeUrl = new Uri(Program.LetsEncryptV2StagingEndpoint); _http = new HttpClient(); _http.BaseAddress = acmeUrl; var sample = Encoding.UTF8.GetBytes("abcdefg"); // var ecKeys = PkiKeyPair.GenerateEcdsaKeyPair(256); // var ecPrvKey = ecKeys.PrivateKey.Export(PkiEncodingFormat.Pem); // var ecPubKey = ecKeys.PublicKey.Export(PkiEncodingFormat.Pem); // WriteLine( // "getting from: " + acmeUrl // + "\r\necPrv: " + Convert.ToBase64String(ecPrvKey) // + "\r\necPub: " + Convert.ToBase64String(ecPubKey) // ); // var signer = new ACMESharp.Crypto.JOSE.Impl.ESJwsTool(); // signer.Init(); // WriteLine($"ECKeys: {signer.Export()}"); var signer = new PkiJwsTool(256); signer.Init(); var signerExport = signer.Export(); signer = new PkiJwsTool(256); signer.Import(signerExport); WriteLine($"ECKeys: {signerExport}"); var sig1 = signer.Sign(sample); WriteLine($"Sig: {Convert.ToBase64String(sig1)}"); sig1 = signer.Sign(sample); WriteLine($"Sig: {Convert.ToBase64String(sig1)}"); sig1 = signer.Sign(sample); WriteLine($"Sig: {Convert.ToBase64String(sig1)}"); WriteLine($"Vfy: {signer.Verify(sample, sig1)}"); WriteLine($"JWK: {JsonConvert.SerializeObject(signer.ExportJwk())}"); WriteLine("Sig1Hex: " + BitConverter.ToString(sig1)); // var ecAlt = JsonConvert.SerializeObject(signer.KeyPair.ExportEcParameters()); // WriteLine($"ECAlt: {ecAlt}"); // var signer2 = new ACMESharp.Crypto.JOSE.Impl.ESJwsTool(); // signer2.HashSize = 256; // signer2.Init(); // signer2.Import(ecAlt); // WriteLine($"ECKeys2: {signer2.Export()}"); // var sig2 = signer2.Sign(sample); // WriteLine($"Sig2: {Convert.ToBase64String(sig2)}"); // sig2 = signer2.Sign(sample); // WriteLine($"Sig2: {Convert.ToBase64String(sig2)}"); // sig2 = signer2.Sign(sample); // WriteLine($"Sig2: {Convert.ToBase64String(sig2)}"); // WriteLine($"Vfy2: {signer2.Verify(sample, sig2)}"); // WriteLine($"JWK2: {JsonConvert.SerializeObject(signer2.ExportJwk())}"); // WriteLine("Sig2Hex: " + BitConverter.ToString(sig2)); var lineSeps = "\r\n".ToCharArray(); using (var acme = new AcmeProtocolClient(_http, signer: signer)) { acme.BeforeAcmeSign = (s, o) => WriteLine($"BEFORE_SIGN({s}, {JsonConvert.SerializeObject(o)})"); acme.BeforeHttpSend = (s, m) => WriteLine($"BEFORE_SEND({s}, {m.Content?.ReadAsStringAsync().Result}"); var dir = await acme.GetDirectoryAsync(); acme.Directory = dir; await acme.GetNonceAsync(); var c = _contacts.Split(lineSeps, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); var acct = await acme.CreateAccountAsync(c, _agreeTos); var acctStr = JsonConvert.SerializeObject(acct, Formatting.Indented); Console.WriteLine("Got Account: " + acctStr); } signer = new PkiJwsTool(256); signer.Import(signerExport); using (var acme = new AcmeProtocolClient(_http, signer: signer)) { acme.BeforeAcmeSign = (s, o) => WriteLine($"BEFORE_SIGN({s}, {JsonConvert.SerializeObject(o)})"); acme.BeforeHttpSend = (s, m) => WriteLine($"BEFORE_SEND({s}, {m.Content?.ReadAsStringAsync().Result}"); var dir = await acme.GetDirectoryAsync(); acme.Directory = dir; await acme.GetNonceAsync(); var d = _dnsNames.Split(lineSeps, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); var order = await acme.CreateOrderAsync(d); var orderStr = JsonConvert.SerializeObject(order, Formatting.Indented); Console.WriteLine("Got Order: " + orderStr); } }
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..."); try { _client = new AcmeProtocolClient(httpClient, signer: signer); } 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 = new Rsa(_log, new RsaOptions()).GetRsaKeyBits() }; signer.Init(); _client = new AcmeProtocolClient(httpClient, signer: signer); } 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"); } return(true); }
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 })); }
public async Task <Uri> GetAcmeTermsOfService() { var tos = await _client.GetDirectoryAsync(); return(new Uri(tos.Meta.TermsOfService)); }