public async Task <PendingOrder> BeginCertificateOrder(ILog log, CertRequestConfig config, string orderUri = null) { var domains = new List <string> { config.PrimaryDomain }; domains.AddRange(config.SubjectAlternativeNames); var order = await _client.CreateOrderAsync(domains.Distinct()); var authorizations = new List <PendingAuthorization>(); foreach (var authz in order.Payload.Authorizations) { var pendingAuthz = new PendingAuthorization { Identifier = new IdentifierItem { Id = authz } //TODO }; authorizations.Add(pendingAuthz); } return(new PendingOrder { OrderUri = order.OrderUrl, Authorizations = authorizations }); }
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 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); } } }
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); }
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); } } } }
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); } } } }
internal OrderDetails CreateOrder(IEnumerable <string> identifiers) { return(Retry(() => _client.CreateOrderAsync(identifiers).Result)); }
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); } }
internal async Task <OrderDetails> CreateOrder(IEnumerable <string> identifiers) => await Retry(() => _client.CreateOrderAsync(identifiers));
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"); }); }
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 })); }