Example #1
0
        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");
        }
Example #3
0
        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
            }));
        }