private void Init() { if (File.Exists(_opts.CaKeyPairSavePath)) { using (var fs = new FileStream(_opts.CaKeyPairSavePath, FileMode.Open)) { _keyPair = PkiKeyPair.Load(fs); } } if (_keyPair == null) { switch (_opts.KeyPairAlgorithm) { case PkiAsymmetricAlgorithm.Rsa: _keyPair = PkiKeyPair.GenerateRsaKeyPair(_opts.BitLength ?? 2048); break; case PkiAsymmetricAlgorithm.Ecdsa: _keyPair = PkiKeyPair.GenerateEcdsaKeyPair(_opts.BitLength ?? 256); break; default: throw new Exception("unsupported Key Pair Algorithm"); } using (var fs = new FileStream(_opts.CaKeyPairSavePath, FileMode.CreateNew)) { _keyPair.Save(fs); } } if (File.Exists(_opts.CaCertificateSavePath)) { using (var fs = new FileStream(_opts.CaCertificateSavePath, FileMode.Open)) { CaCertificate = PkiCertificate.Load(fs); } } if (CaCertificate == null) { var caCsr = new PkiCertificateSigningRequest( _opts.CaSubjectName, _keyPair, _opts.SignatureHashAlgorithm); CaCertificate = caCsr.CreateCa( DateTimeOffset.Now.ToUniversalTime(), DateTimeOffset.Now.AddYears(10).ToUniversalTime()); using (var fs = new FileStream(_opts.CaCertificateSavePath, FileMode.CreateNew)) { CaCertificate.Save(fs); } } }
public void SaveLoadCertificate(PkiAsymmetricAlgorithm algor, int bits) { var hashAlgor = PkiHashAlgorithm.Sha256; var isurName = "CN=SelfSigned"; var isurKeys = PkiKeyTests.GenerateKeyPair(algor, bits); var subjName = "CN=foo.example.com"; var subjKeys = PkiKeyTests.GenerateKeyPair(algor, bits); var isurCsr = new PkiCertificateSigningRequest(isurName, isurKeys, hashAlgor); var subjCsr = new PkiCertificateSigningRequest(subjName, subjKeys, hashAlgor); subjCsr.CertificateExtensions.Add(PkiCertificateExtension.CreateDnsSubjectAlternativeNames( new[] { "foo-alt1.example.com", "foo-alt2.example.com", } )); var selfOut = Path.Combine(_testTemp, $"certsave-{algor}-{bits}-{hashAlgor}-self.ser"); var signedOut = Path.Combine(_testTemp, $"certsave-{algor}-{bits}-{hashAlgor}-signed.ser"); var isurCert = isurCsr.CreateCa( DateTime.Now.AddMonths(-5), DateTime.Now.AddMonths(5)); var subjCert = subjCsr.Create(isurCert, isurKeys.PrivateKey, DateTime.Now.AddMonths(-1), DateTime.Now.AddMonths(1), new[] { (byte)0x2b }); using (var ms = new MemoryStream()) { isurCert.Save(ms); File.WriteAllBytes(selfOut, ms.ToArray()); } using (var ms = new MemoryStream()) { subjCert.Save(ms); File.WriteAllBytes(signedOut, ms.ToArray()); } PkiCertificate isurCert2; PkiCertificate subjCert2; using (var fs = new FileStream(selfOut, FileMode.Open)) isurCert2 = PkiCertificate.Load(fs); using (var fs = new FileStream(signedOut, FileMode.Open)) subjCert2 = PkiCertificate.Load(fs); var bclIsur = isurCert.ToBclCertificate(); var bclSubj = subjCert.ToBclCertificate(); var bclIsur2 = isurCert2.ToBclCertificate(); var bclSubj2 = subjCert2.ToBclCertificate(); Assert.AreEqual(bclIsur.GetSerialNumberString(), bclIsur2.GetSerialNumberString(), "Issuer Serial Number"); Assert.AreEqual(bclIsur.GetCertHashString(), bclIsur2.GetCertHashString(), "Issuer Hash"); Assert.AreEqual(bclIsur.GetRawCertDataString(), bclIsur2.GetRawCertDataString(), "Issuer Raw Data"); Assert.AreEqual(bclSubj.GetSerialNumberString(), bclSubj2.GetSerialNumberString(), "Subject Serial Number"); Assert.AreEqual(bclSubj.GetCertHashString(), bclSubj2.GetCertHashString(), "Subject Hash"); Assert.AreEqual(bclSubj.GetRawCertDataString(), bclSubj2.GetRawCertDataString(), "Subject Raw Data"); }
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 })); }