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); }
public async Task <ActionResult <Certify.Models.AccountDetails> > AddNewAccountAndAcceptTOS(ILog log, string email) { _log.Debug("Adding new account"); try { var account = await _client.CreateAccountAsync(new string[] { email.Trim() }, termsOfServiceAgreed : true); return(new ActionResult <Certify.Models.AccountDetails> { IsSuccess = true, Result = new Certify.Models.AccountDetails { ID = account.Payload.Id, AccountKey = account.Payload.Key.ToString() } }); } catch (Exception exp) { return(new ActionResult <Certify.Models.AccountDetails> { IsSuccess = false, Message = "Failed to add account. Check email address is valid and system can contact the ACME API. " + exp.ToString() }); } }
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); }
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); } } }
/// <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 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 <bool> AddNewAccountAndAcceptTOS(ILog log, string email) { _log.Debug("Adding new account"); var account = await _client.CreateAccountAsync(new string[] { email.Trim() }, termsOfServiceAgreed : true); return(true); }
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); }
private async Task <AccountDetails> LoadAccount(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 (!_arguments.MainArguments.AcceptTos) { var tosPath = Path.Combine(_settings.Client.ConfigurationPath, filename); File.WriteAllBytes(tosPath, content); _input.Show($"Terms of service", tosPath); if (await _input.PromptYesNo($"Open in default application?", false)) { Process.Start(tosPath); } if (!await _input.PromptYesNo($"Do you agree with the terms?", true)) { 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); }
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 Task <AccountDetails> LoadAccount(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)); } else { _log.Error("Account found but no valid Signer could be loaded"); } } else { var contacts = GetContacts(); var(contentType, filename, content) = await _client.GetTermsOfServiceAsync(); if (!_optionsService.MainArguments.AcceptTos) { var tosPath = Path.Combine(_settings.ConfigPath, filename); File.WriteAllBytes(tosPath, content); _input.Show($"Terms of service", tosPath); if (_input.PromptYesNo($"Open in default application?")) { Process.Start(tosPath); } if (!_input.PromptYesNo($"Do you agree with the terms?")) { 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); }
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[] { "*****@*****.**" }); } } }
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); } } } }
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 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; }
public async Task FinalizeOrder() { 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", "foo-alt-1.mock.acme2.zyborg.io", "foo-alt-2.mock.acme2.zyborg.io", "foo-alt-3.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 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); } var kpr = PkiKeyPair.GenerateRsaKeyPair(2048); var csr = new PkiCertificateSigningRequest($"cn={dnsIds[0]}", kpr, PkiHashAlgorithm.Sha256); csr.CertificateExtensions.Add( PkiCertificateExtension.CreateDnsSubjectAlternativeNames(dnsIds.Skip(1))); var csrDer = csr.ExportSigningRequest(PkiEncodingFormat.Der); var finalizedOrder = await acme.FinalizeOrderAsync(order.Payload.Finalize, csrDer); Assert.AreEqual("valid", finalizedOrder.Payload.Status); Assert.IsNotNull(finalizedOrder.Payload.Certificate); var getResp = await acme.GetAsync(finalizedOrder.Payload.Certificate); getResp.EnsureSuccessStatusCode(); using (var fs = new FileStream( @"C:\local\prj\bek\ACMESharp\ACMESharpCore\test\ACMESharp.MockServer.UnitTests\finalize-cert.pem", FileMode.Create)) { await getResp.Content.CopyToAsync(fs); } } } }
private async Task <AccountDetails?> LoadAccount(AcmeProtocolClient client, IJwsTool?signer) { _log.Verbose("Loading ACME account"); 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 { _log.Verbose("No account found at {path}, creating new one", AccountPath); try { var(_, filename, content) = await client.GetTermsOfServiceAsync(); _log.Verbose("Terms of service downloaded"); if (!string.IsNullOrEmpty(filename)) { if (!await AcceptTos(filename, content)) { return(null); } } } catch (Exception ex) { _log.Error(ex, "Error getting terms of service"); } var contacts = default(string[]); var externalAccount = default(ExternalAccountBinding); var kid = _accountArguments.EabKeyIdentifier; var key = _accountArguments.EabKey; var alg = _accountArguments.EabAlgorithm ?? "HS256"; var eabFlow = client.Directory?.Meta?.ExternalAccountRequired == "true"; var zeroSslFlow = _settings.BaseUri.Host.Contains("zerossl.com"); // Warn about unneeded EAB if (!eabFlow && !string.IsNullOrWhiteSpace(kid)) { eabFlow = true; _input.CreateSpace(); _input.Show(null, "You have provided an external account binding key, even though " + "the server does not indicate that this is required. We will attempt to register " + "using this key anyway."); } if (zeroSslFlow) { async Task emailRegistration() { var registration = await GetContacts(allowMultiple : false, prefix : ""); var eab = await _zeroSsl.Register(registration.FirstOrDefault() ?? ""); if (eab != null) { kid = eab.Kid; key = eab.Hmac; } else { _log.Error("Unable to retrieve EAB credentials using the provided email address"); } } async Task apiKeyRegistration() { var accessKey = await _input.ReadPassword("API access key"); var eab = await _zeroSsl.Obtain(accessKey ?? ""); if (eab != null) { kid = eab.Kid; key = eab.Hmac; } else { _log.Error("Unable to retrieve EAB credentials using the provided API access key"); } } if (!string.IsNullOrWhiteSpace(_accountArguments.EmailAddress)) { await emailRegistration(); } else { var instruction = "ZeroSsl can be used either by setting up a new " + "account using your email address or by connecting it to your existing " + "account using the API access key or pre-generated EAB credentials, which can " + "be obtained from the Developer section of the dashboard."; _input.CreateSpace(); _input.Show(null, instruction); var chosen = await _input.ChooseFromMenu( "How would you like to create the account?", new List <Choice <Func <Task> > >() { Choice.Create((Func <Task>)apiKeyRegistration, "API access key"), Choice.Create((Func <Task>)emailRegistration, "Email address"), Choice.Create <Func <Task> >(() => Task.CompletedTask, "Input EAB credentials directly") }); await chosen.Invoke(); } } if (eabFlow) { if (string.IsNullOrWhiteSpace(kid)) { var instruction = "This ACME endpoint requires an external account. " + "You will need to provide a key identifier and a key to proceed. " + "Please refer to the providers instructions on how to obtain these."; _input.CreateSpace(); _input.Show(null, instruction); kid = await _input.RequestString("Key identifier"); } if (string.IsNullOrWhiteSpace(key)) { key = await _input.ReadPassword("Key (base64url encoded)"); } externalAccount = new ExternalAccountBinding( alg, JsonConvert.SerializeObject( client.Signer.ExportJwk(), Formatting.None), kid, key ?? "", client.Directory?.NewAccount ?? ""); } else { contacts = await GetContacts(); } try { account = await client.CreateAccountAsync( contacts, termsOfServiceAgreed : true, externalAccountBinding : externalAccount?.Payload() ?? null); } catch (Exception ex) { _log.Error(ex, "Error creating account"); } if (account != null) { try { _log.Debug("Saving account"); var accountKey = new AccountSigner { KeyType = client.Signer.JwsAlg, KeyExport = client.Signer.Export(), }; AccountSigner = accountKey; await File.WriteAllTextAsync(AccountPath, JsonConvert.SerializeObject(account)); } catch (Exception ex) { _log.Error(ex, "Error saving account"); account = null; } } } return(account); }
private async Task <AccountDetails?> LoadAccount(AcmeProtocolClient client, IJwsTool?signer) { _log.Verbose("Loading ACME account"); 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 { _log.Verbose("No account found at {path}, creating new one", AccountPath); try { var(_, filename, content) = await client.GetTermsOfServiceAsync(); _log.Verbose("Terms of service downloaded"); if (!string.IsNullOrEmpty(filename)) { if (!await AcceptTos(filename, content)) { return(null); } } } catch (Exception ex) { _log.Error(ex, "Error getting terms of service"); } var contacts = default(string[]); var externalAccount = default(ExternalAccountBinding); var kid = _accountArguments.EabKeyIdentifier; var key = _accountArguments.EabKey; var alg = _accountArguments.EabAlgorithm ?? "HS256"; var eabFlow = client.Directory?.Meta?.ExternalAccountRequired == "true"; if (eabFlow) { _input.CreateSpace(); _input.Show(null, "This ACME endpoint requires an external account. You will " + "need to provide a key identifier and a key to proceed. Please refer to the " + "providers instructions on how to obtain these."); } else if (!string.IsNullOrWhiteSpace(kid)) { eabFlow = true; _input.CreateSpace(); _input.Show(null, "You have provided external account binding key, but the server" + "claims that is not required. We will attempt to register using this key anyway."); } if (eabFlow) { if (string.IsNullOrWhiteSpace(kid)) { kid = await _input.RequestString("Key identifier"); } if (string.IsNullOrWhiteSpace(key)) { key = await _input.ReadPassword("Key (base64url encoded)"); } externalAccount = new ExternalAccountBinding( alg, JsonConvert.SerializeObject(client.Signer.ExportJwk(), Formatting.None), kid, key ?? "", client.Directory?.NewAccount ?? ""); } else { contacts = await GetContacts(); } try { account = await client.CreateAccountAsync( contacts, termsOfServiceAgreed : true, externalAccountBinding : externalAccount?.Payload() ?? null); } catch (Exception ex) { _log.Error(ex, "Error creating account"); } if (account != null) { try { _log.Debug("Saving account"); var accountKey = new AccountSigner { KeyType = client.Signer.JwsAlg, KeyExport = client.Signer.Export(), }; AccountSigner = accountKey; await File.WriteAllTextAsync(AccountPath, JsonConvert.SerializeObject(account)); } catch (Exception ex) { _log.Error(ex, "Error saving account"); account = null; } } } return(account); }
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); } }
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 })); }