Esempio n. 1
0
        private async Task CreateCertificate(string password)
        {
            if (_challengeLocation == null)
            {
                throw new InvalidOperationException("Challenge must be completed before certificate creation");
            }

            var authorisation = await _acmeClient.GetAuthorization(_challengeLocation);

            while (authorisation.Data.Status == EntityStatus.Pending)
            {
                await Task.Delay(10000);

                authorisation = await _acmeClient.GetAuthorization(_challengeLocation);
            }

            if (authorisation.Data.Status != EntityStatus.Valid)
            {
                throw new InvalidOperationException($"Authorisation failed, status = {authorisation.Data.Status}");
            }

            var csr = new CertificationRequestBuilder();

            csr.AddName("CN", Hostname);
            var cert = await _acmeClient.NewCertificate(csr);

            Bytes = cert.ToPfx().Build(Hostname, password);
        }
Esempio n. 2
0
        protected async Task <byte[]> GenerateCertificateAsync(CertificateInfo certificateInfo, IOrderContext orderCtx)
        {
            //Создаем CSR-builder и добавляем туда все домены и CN
            var csrBuilder = new CertificationRequestBuilder();

            csrBuilder.AddName("CN", certificateInfo.CommonName);

            foreach (var domain in certificateInfo.Domains)
            {
                csrBuilder.SubjectAlternativeNames.Add(domain);
            }

            //Отправляем запрос на завершение Order'а (генерацию сертификата)
            var order = await orderCtx.Finalize(csrBuilder.Generate());

            if (order.Status != OrderStatus.Valid)
            {
                throw  new InvalidOperationException("Order finalization error!");
            }

            //Получаем цепочку сертификатов и возвращаем ее виде PFX-файла
            var certChain = await orderCtx.Download();

            LogNewCertificateInfo(certChain);

            return(certChain.ToPfx(csrBuilder.Key).Build(certificateInfo.CommonName, certificateInfo.PfxPassword));
        }
Esempio n. 3
0
            public async Task CanGenerateCertificateDns()
            {
                var dirUri = await GetAcmeUriV2();

                var hosts    = new[] { $"www-dns-{DomainSuffix}.es256.certes-ci.dymetis.com", $"mail-dns-{DomainSuffix}.es256.certes-ci.dymetis.com" };
                var ctx      = new AcmeContext(dirUri, GetKeyV2(), http: GetAcmeHttpClient(dirUri));
                var orderCtx = await AuthzDns(ctx, hosts);

                while (orderCtx == null)
                {
                    Output.WriteLine("DNS authz failed, retrying...");
                    orderCtx = await AuthzDns(ctx, hosts);
                }

                var csr = new CertificationRequestBuilder();

                csr.AddName($"C=CA, ST=Ontario, L=Toronto, O=Certes, OU=Dev, CN={hosts[0]}");
                foreach (var h in hosts)
                {
                    csr.SubjectAlternativeNames.Add(h);
                }

                var der = csr.Generate();

                var finalizedOrder = await orderCtx.Finalize(der);

                var certificate = await orderCtx.Download();

                await ClearAuthorizations(orderCtx);
            }
Esempio n. 4
0
        /// <summary>
        /// Creates CSR from the order.
        /// </summary>
        /// <param name="context">The order context.</param>
        /// <param name="key">The private key.</param>
        /// <returns>The CSR.</returns>
        public static async Task <CertificationRequestBuilder> CreateCsr(this IOrderContext context, IKey key)
        {
            var builder = new CertificationRequestBuilder(key);
            var order   = await context.Resource();

            foreach (var identifier in order.Identifiers)
            {
                builder.SubjectAlternativeNames.Add(identifier.Value);
            }

            return(builder);
        }
Esempio n. 5
0
        async Task <Order> FinalizeOrderAsync()
        {
            Console.WriteLine("Constructing private key and CSR");

            IEnumerable <string> domains = _Order.Identifiers.Where(i => i.Type == IdentifierType.Dns).Select(i => i.Value);

            //when ordering wildcard certs the cn shouldn't be the wildcard but the ordering of the identifiers
            //often does not match the order of domains in the original order setup
            string cn = domains.First().StartsWith("*", StringComparison.InvariantCultureIgnoreCase) && domains.Count() > 1
                               ? domains.Skip(1).Take(1).First() : domains.First();
            var csrBuilder = new CertificationRequestBuilder();

            csrBuilder.AddName($"{CertDistinguishedName}, CN={cn}");

            //setup the san if necessary
            if (domains.Count() > 1)
            {
                csrBuilder.SubjectAlternativeNames = domains.Where(d => d != cn).ToList();
            }

            byte[] csr        = csrBuilder.Generate();
            var    privateKey = csrBuilder.Key;

            _CertManager.CertPrivateKey = privateKey.ToPem();

            if (_CancellationToken.IsCancellationRequested)
            {
                return(null);
            }

            Console.WriteLine("Finalizing order");
            try
            {
                return(await _OrderContext.Finalize(csr));
            }
            catch (AcmeRequestException ex)
            {
                Console.WriteLine(ex.Message);
                if (ex.Error != null)
                {
                    Console.WriteLine(ex.Error.Detail);
                }
            }

            if (_CancellationToken.IsCancellationRequested)
            {
                return(null);
            }

            return(await _OrderContext.Resource());
        }
Esempio n. 6
0
        private async Task <byte[]> RequestNewCertificate(String domainName)
        {
            using (var client = new AcmeClient(_settings.AcmeUri))
            {
                if (_settings.Diagnostics)
                {
                    Console.WriteLine($"[ACMECERTMGR] Calling {_settings.AcmeUri} to requesting certificate for  {domainName}.");
                }

                var result = await GetAuthorizationAsync(client, domainName);

                if (_settings.Diagnostics)
                {
                    Console.WriteLine($"[ACMECERTMGR] Made call to {_settings.AcmeUri} for new cert for {domainName}.");
                }

                if (result.Data.Status != EntityStatus.Valid)
                {
                    var acmeResponse = JsonConvert.DeserializeObject <AcmeResponseModel>(result.Json);
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"[ACMECERTMGR] Did not succeed in call to get new cert: {acmeResponse.Status} {acmeResponse.Error.Detail}.");
                    Console.ResetColor();

                    return(null);
                }

                if (_settings.Diagnostics)
                {
                    Console.WriteLine($"[ACMECERTMGR] Success making call to {_settings.AcmeUri} for new cert for {domainName}.");
                }

                var csr = new CertificationRequestBuilder();
                csr.AddName("CN", domainName);
                var cert = await client.NewCertificate(csr);

                var buffer = cert.ToPfx().Build(domainName, _settings.PfxPassword);

                if (_settings.Diagnostics)
                {
                    Console.WriteLine($"[ACMECERTMGR] Created new certificate and returning byte array for {domainName}.");
                }

                return(buffer);
            }
        }
        public async Task CanIssueSan()
        {
            var accountKey = await Helper.LoadkeyV1();

            var csr = new CertificationRequestBuilder();

            csr.AddName("C=CA, ST=Ontario, L=Toronto, O=Certes, OU=Dev, CN=www.certes-ci.dymetis.com");
            csr.SubjectAlternativeNames.Add("mail.certes-ci.dymetis.com");
            csr.SubjectAlternativeNames.Add("sso.certes-ci.dymetis.com");

            var dirUri = await IntegrationHelper.GetAcmeUriV1();

            using (var client = new AcmeClient(IntegrationHelper.GetAcmeHttpHandler(dirUri)))
            {
                client.Use(accountKey.Export());

                await AuthorizeDns(client, "www.certes-ci.dymetis.com");
                await AuthorizeDns(client, "mail.certes-ci.dymetis.com");
                await AuthorizeDns(client, "sso.certes-ci.dymetis.com");

                // should returns the valid ID
                var authz = await client.NewAuthorization(new AuthorizationIdentifier
                {
                    Type  = AuthorizationIdentifierTypes.Dns,
                    Value = "www.certes-ci.dymetis.com",
                });

                Assert.Equal(EntityStatus.Valid, authz.Data.Status);

                var authzByLoc = await client.GetAuthorization(authz.Location);

                Assert.Equal(authz.Data.Identifier.Value, authzByLoc.Data.Identifier.Value);

                var cert = await client.NewCertificate(csr);

                var pfx = cert.ToPfx();

                pfx.AddTestCert();

                pfx.Build("my.pfx", "abcd1234");
                await client.RevokeCertificate(cert);
            }
        }
Esempio n. 8
0
        private async Task <X509Certificate2> CompleteCertificateRequestAsync(string hostName, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var csr = new CertificationRequestBuilder();
            var dn  = "CN=" + hostName;

            csr.AddName(dn);

            _logger.LogInformation("Sending certifcate request for '{dn}'", dn);

            var cert = await _client.NewCertificate(csr);

            _logger.LogResponse("NewCertificate", cert);

            var pfx = cert.ToPfx().Build(hostName, string.Empty);

            return(new X509Certificate2(pfx, string.Empty, X509KeyStorageFlags.Exportable));
        }
Esempio n. 9
0
        public async Task OrderFinalizeAsync()
        {
            var order = await GetOrderInfoAsync();

            var topDomain = await GetTopDomainAsync();

            var csrBuilder = new CertificationRequestBuilder();

            csrBuilder.AddName($"C=Country, ST=State, L=City, O=Org, CN={topDomain}");

            //setup the san if necessary
            csrBuilder.SubjectAlternativeNames = order.Domains.Where(a => a != topDomain).ToList();

            byte[] csrByte = csrBuilder.Generate();

            await OrderContext.Finalize(csrByte);

            File.WriteAllText(Path.Combine(_workDir, $"{topDomain}-{FreeCertConsts.CertPemPrivateKeyName}"), csrBuilder.Key.ToPem(),
                              Encoding.UTF8);
        }
        public async Task CanIssueSan()
        {
            var csr = new CertificationRequestBuilder();

            csr.AddName("CN=CA, ST=Ontario, L=Toronto, O=Certes, OU=Dev, CN=www.certes-ci.dymetis.com");
            csr.SubjectAlternativeNames.Add("mail.certes-ci.dymetis.com");
            csr.SubjectAlternativeNames.Add("sso.certes-ci.dymetis.com");

            using (var client = new AcmeClient(WellKnownServers.LetsEncryptStaging))
            {
                client.Use(Helper.Loadkey().Export());

                await Task.WhenAll(
                    AuthorizeDns(client, "www.certes-ci.dymetis.com"),
                    AuthorizeDns(client, "mail.certes-ci.dymetis.com"),
                    AuthorizeDns(client, "sso.certes-ci.dymetis.com"));

                var cert = await client.NewCertificate(csr);
            }
        }
            public async Task CanGenerateCertificateDns()
            {
                var dirUri = await GetAcmeUriV2();

                var hosts    = new[] { $"www-dns-{DomainSuffix}.es256.certes-ci.dymetis.com", $"mail-dns-{DomainSuffix}.es256.certes-ci.dymetis.com" };
                var ctx      = new AcmeContext(dirUri, GetKeyV2(), http: GetAcmeHttpClient(dirUri));
                var orderCtx = await AuthzDns(ctx, hosts);

                while (orderCtx == null)
                {
                    Output.WriteLine("DNS authz failed, retrying...");
                    orderCtx = await AuthzDns(ctx, hosts);
                }

                var csr = new CertificationRequestBuilder();

                csr.AddName($"C=CA, ST=Ontario, L=Toronto, O=Certes, OU=Dev, CN={hosts[0]}");
                foreach (var h in hosts)
                {
                    csr.SubjectAlternativeNames.Add(h);
                }

                var der = csr.Generate();

                var finalizedOrder = await orderCtx.Finalize(der);

                var certificate = await orderCtx.Download();

                // deactivate authz so the subsequence can trigger challenge validation
                var authrizations = await orderCtx.Authorizations();

                foreach (var authz in authrizations)
                {
                    var authzRes = await authz.Deactivate();

                    Assert.Equal(AuthorizationStatus.Deactivated, authzRes.Status);
                }
            }
Esempio n. 12
0
        private async Task <X509Certificate2> CompleteCertificateRequestAsync(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var distinguishedName = "CN=" + _options.Value.DomainNames[0];

            _logger.LogDebug("Creating cert for {distinguishedName}", distinguishedName);

            var csb = new CertificationRequestBuilder();

            csb.AddName(distinguishedName);
            foreach (var name in _options.Value.DomainNames.Skip(1))
            {
                csb.SubjectAlternativeNames.Add(name);
            }

            var acmeCert = await _client.NewCertificate(csb);

            _logger.LogResponse("NewCertificate", acmeCert);

            var pfxBuilder = acmeCert.ToPfx();
            var pfx        = pfxBuilder.Build("Let's Encrypt - " + _options.Value.DomainNames, string.Empty);

            return(new X509Certificate2(pfx, string.Empty, X509KeyStorageFlags.Exportable));
        }
Esempio n. 13
0
        public async Task <ProcessStepResult> PerformCertificateRequestProcess(string primaryDnsIdentifier, string[] alternativeDnsIdentifiers, CertRequestConfig config)
        {
            // create our new certificate
            var orderContext = _currentOrders[config.PrimaryDomain];

            //update order status
            var order = await orderContext.Resource();

            // order.Generate()
            var csrKey = KeyFactory.NewKey(KeyAlgorithm.RS256);
            var csr    = new CsrInfo
            {
                CommonName = config.PrimaryDomain
            };

            //alternative to certes IOrderContextExtension.Finalize
            var builder = new CertificationRequestBuilder(csrKey);

            foreach (var authzCtx in await orderContext.Authorizations())
            {
                var authz = await authzCtx.Resource();

                if (!builder.SubjectAlternativeNames.Contains(authz.Identifier.Value))
                {
                    if (config.PrimaryDomain != $"*.{authz.Identifier.Value}")
                    {
                        //only add domain to SAN if it is not derived from a wildcard domain eg test.com from *.test.com
                        builder.SubjectAlternativeNames.Add(authz.Identifier.Value);
                    }
                }
            }

            // if main request is for a wildcard domain, add that to SAN list
            if (config.PrimaryDomain.StartsWith("*."))
            {
                //add wildcard domain to san
                builder.SubjectAlternativeNames.Add(config.PrimaryDomain);
            }
            builder.AddName("CN", config.PrimaryDomain);

            /* foreach (var f in csr.AllFieldsDictionary)
             * {
             *   builder.AddName(f.Key, f.Value);
             * }*/

            if (string.IsNullOrWhiteSpace(csr.CommonName))
            {
                builder.AddName("CN", builder.SubjectAlternativeNames[0]);
            }

            var certResult = await orderContext.Finalize(builder.Generate());

            var pem = await orderContext.Download();

            var cert = new CertificateInfo(pem, csrKey);

            var certFriendlyName = config.PrimaryDomain + "[Certify]";
            var certFolderPath   = _settingsFolder + "\\assets\\pfx";

            if (!System.IO.Directory.Exists(certFolderPath))
            {
                System.IO.Directory.CreateDirectory(certFolderPath);
            }

            string certFile = Guid.NewGuid().ToString() + ".pfx";
            string pfxPath  = certFolderPath + "\\" + certFile;

            System.IO.File.WriteAllBytes(pfxPath, cert.ToPfx(certFriendlyName, ""));

            return(new ProcessStepResult {
                IsSuccess = true, Result = pfxPath
            });
        }
Esempio n. 14
0
        /// <summary>
        /// 使用Let's Encrypt服務產生X509憑證
        /// </summary>
        /// <param name="options">Kestrel伺服器監聽選項實例</param>
        /// <param name="savePassword">憑證儲存密碼</param>
        /// <param name="email">電子郵件</param>
        /// <param name="domains">網域</param>
        /// <returns>可等候程序之X509憑證二進制原始資料</returns>
        public static async Task <byte[]> CreateX509BinaryByLetsEncryptAsync(this ListenOptions options, string savePassword, string email, params string[] domains)
        {
            using (var client = new AcmeClient(WellKnownServers.LetsEncrypt)) {
                // Create new registration
                var account = await client.NewRegistraton("mailto:" + email);

                // Accept terms of services
                account.Data.Agreement = account.GetTermsOfServiceUri();
                account = await client.UpdateRegistration(account);

                // Initialize authorization
                Dictionary <string, string> keyAuthStringMap = new Dictionary <string, string>();
                List <Challenge>            challenges       = new List <Challenge>();
                foreach (var domain in domains)
                {
                    var authz = await client.NewAuthorization(new AuthorizationIdentifier {
                        Type  = AuthorizationIdentifierTypes.Dns,
                        Value = domain
                    });

                    // Comptue key authorization for http-01
                    var httpChallengeInfo = authz.Data.Challenges.Where(c => c.Type == ChallengeTypes.Http01).First();
                    challenges.Add(httpChallengeInfo);
                    var keyAuthString = client.ComputeKeyAuthorization(httpChallengeInfo);
                    keyAuthStringMap[httpChallengeInfo.Token] = keyAuthString;
                }

                IWebHost k = new WebHostBuilder()
                             .UseKestrel()
                             .UseUrls("http://*")
                             .ConfigureServices(services => {
                    services.AddRouting();
                })
                             .Configure(app => {
                    var trackPackageRouteHandler = new RouteHandler(context => {
                        return(context.Response.WriteAsync($"404 Not Found"));
                    });

                    var routeBuilder = new RouteBuilder(app, trackPackageRouteHandler);
                    routeBuilder.MapGet(".well-known/acme-challenge/{id}", context => {
                        var result = keyAuthStringMap[context.GetRouteValue("id") as string];
                        Console.WriteLine("Server Call :" + context.GetRouteValue("id"));
                        return(context.Response.WriteAsync(result));
                    });

                    var routes = routeBuilder.Build();
                    app.UseRouter(routes);


                    Console.WriteLine("Server Start");
                })
                             .Build();
                k.Start();

                // Do something to fullfill the challenge,
                // e.g. upload key auth string to well known path, or make changes to DNS
                await Task.Delay(10000);

                foreach (var challenge in challenges)
                {
                    // Info ACME server to validate the identifier
                    var httpChallenge = await client.CompleteChallenge(challenge);

                    //httpChallenge.

                    // Check authorization status
                    var authz = await client.GetAuthorization(httpChallenge.Location);

                    while (authz.Data.Status == EntityStatus.Pending)
                    {
                        await Task.Delay(3000);

                        authz = await client.GetAuthorization(httpChallenge.Location);
                    }
                }

                var csr = new CertificationRequestBuilder();
                csr.AddName("CN", domains.First());
                foreach (var domain in domains.Skip(1))
                {
                    csr.SubjectAlternativeNames.Add(domain);
                }
                var cert = await client.NewCertificate(csr);

                var pfxBuilder = cert.ToPfx();

                var x509result = pfxBuilder.Build("letsEncrypt", savePassword);
                Console.WriteLine("Let's Encrypt OK");

                var serviceLife = k.Services.GetService <IApplicationLifetime>();

                serviceLife.StopApplication();
                k.Dispose();
                Console.WriteLine("Server Stop");

                return(x509result);
            }
        }
Esempio n. 15
0
        public override async Task <AcmeContext> Process(AcmeContext context)
        {
            if (context?.Account == null)
            {
                throw new Exception("Account not specified.");
            }

            if (string.IsNullOrWhiteSpace(Options.Name))
            {
                throw new Exception("Certificate name not specficied.");
            }

            if (context.Certificates == null)
            {
                context.Certificates = new Dictionary <string, AcmeCertificate>();
            }

            if (!string.IsNullOrWhiteSpace(Options.ExportCer))
            {
                var cert = context.Certificates.TryGet(Options.Name);
                if (cert == null)
                {
                    throw new Exception($"Certificate {Options.Name} not found.");
                }

                if (cert.Raw == null)
                {
                    throw new Exception($"Certificate {Options.Name} not ready.");
                }

                await FileUtil.WriteAllBytes(Options.ExportCer, cert.Raw);
            }
            else if (!string.IsNullOrWhiteSpace(Options.ExportPfx))
            {
                var cert = context.Certificates.TryGet(Options.Name);
                if (cert == null)
                {
                    throw new Exception($"Cert {Options.Name} not found.");
                }

                if (cert.Raw == null)
                {
                    throw new Exception($"Certificate {Options.Name} not ready.");
                }

                var pfxBuilder = cert.ToPfx();
                var pfx        = pfxBuilder.Build(Options.Name, Options.Password);
                await FileUtil.WriteAllBytes(Options.ExportPfx, pfx);
            }
            else if (!string.IsNullOrWhiteSpace(Options.ExportKey))
            {
                var cert = context.Certificates.TryGet(Options.Name);
                if (cert == null)
                {
                    throw new Exception($"Cert {Options.Name} not found.");
                }

                using (var stream = File.Create(Options.ExportKey))
                {
                    cert.Key.Save(stream);
                }
            }
            else if (Options.RevokeCer)
            {
                var cert = context.Certificates.TryGet(Options.Name);
                if (cert == null)
                {
                    throw new Exception($"Cert {Options.Name} not found.");
                }

                using (var client = new AcmeClient(Options.Server))
                {
                    client.Use(context.Account.Key);

                    cert = await client.RevokeCertificate(cert);

                    context.Certificates[Options.Name] = cert;
                }
            }
            else
            {
                if (!Options.Force && context.Certificates.ContainsKey(Options.Name))
                {
                    throw new Exception($"Certificate {Options.Name} already exists. Use --force to overwrite.");
                }

                var values = await GetAllValues();

                var csrBuilder = new CertificationRequestBuilder();
                csrBuilder.AddName(Options.DistinguishedName);

                foreach (var value in values)
                {
                    csrBuilder.SubjectAlternativeNames.Add(value);
                }

                using (var client = new AcmeClient(Options.Server))
                {
                    client.Use(context.Account.Key);

                    var cert = await client.NewCertificate(csrBuilder);

                    context.Certificates[Options.Name] = cert;
                }
            }

            return(context);
        }
Esempio n. 16
0
        internal async Task Init()
        {
            System.Console.WriteLine("ARK Bot");
            System.Console.WriteLine("------------------------------------------------------");
            System.Console.WriteLine();

            log4net.Config.XmlConfigurator.Configure();

            //load config and check for errors
            if (!File.Exists(Constants.ConfigFilePath))
            {
                WriteAndWaitForKey($@"The required file config.json is missing from application directory. Please copy defaultconfig.json, set the correct values for your environment and restart the application.");
                return;
            }

            if (!new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
            {
                WriteAndWaitForKey($@"This application must be run as administrator in order to function properly.");
                return;
            }

            _config = null;
            string exceptionMessage = null;

            try
            {
                _config = JsonConvert.DeserializeObject <Config>(File.ReadAllText(Constants.ConfigFilePath));
            }
            catch (Exception ex)
            {
                exceptionMessage = ex.Message;
            }
            if (_config == null)
            {
                WriteAndWaitForKey(
                    $@"The required file config.json is empty or contains errors. Please copy defaultconfig.json, set the correct values for your environment and restart the application.",
                    exceptionMessage);
                return;
            }

            var sb = new StringBuilder();

            if (string.IsNullOrWhiteSpace(_config.BotId) || !new Regex(@"^[a-z0-9]+$", RegexOptions.IgnoreCase | RegexOptions.Singleline).IsMatch(_config.BotId))
            {
                sb.AppendLine($@"Error: {nameof(_config.BotId)} is not a valid id.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotId))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.BotName))
            {
                sb.AppendLine($@"Error: {nameof(_config.BotName)} is not set.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotName))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.BotNamespace) || !Uri.IsWellFormedUriString(_config.BotNamespace, UriKind.Absolute))
            {
                sb.AppendLine($@"Error: {nameof(_config.BotNamespace)} is not set or not a valid url.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotNamespace))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.TempFileOutputDirPath) || !Directory.Exists(_config.TempFileOutputDirPath))
            {
                sb.AppendLine($@"Error: {nameof(_config.TempFileOutputDirPath)} is not a valid directory path.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.TempFileOutputDirPath))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.BotToken))
            {
                sb.AppendLine($@"Error: {nameof(_config.BotToken)} is not set.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotToken))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.SteamOpenIdRedirectUri))
            {
                sb.AppendLine($@"Error: {nameof(_config.SteamOpenIdRedirectUri)} is not set.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamOpenIdRedirectUri))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.SteamOpenIdRelyingServiceListenPrefix))
            {
                sb.AppendLine($@"Error: {nameof(_config.SteamOpenIdRelyingServiceListenPrefix)} is not set.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamOpenIdRelyingServiceListenPrefix))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.GoogleApiKey))
            {
                sb.AppendLine($@"Error: {nameof(_config.GoogleApiKey)} is not set.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.GoogleApiKey))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.SteamApiKey))
            {
                sb.AppendLine($@"Error: {nameof(_config.SteamApiKey)} is not set.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamApiKey))}");
                sb.AppendLine();
            }
            if (_config.BackupsEnabled && (string.IsNullOrWhiteSpace(_config.BackupsDirectoryPath) || !FileHelper.IsValidDirectoryPath(_config.BackupsDirectoryPath)))
            {
                sb.AppendLine($@"Error: {nameof(_config.BackupsDirectoryPath)} is not a valid directory path.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BackupsDirectoryPath))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.WebApiListenPrefix))
            {
                sb.AppendLine($@"Error: {nameof(_config.WebApiListenPrefix)} is not set.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.WebApiListenPrefix))}");
                sb.AppendLine();
            }
            if (string.IsNullOrWhiteSpace(_config.WebAppListenPrefix))
            {
                sb.AppendLine($@"Error: {nameof(_config.WebAppListenPrefix)} is not set.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.WebAppListenPrefix))}");
                sb.AppendLine();
            }
            if (_config.Ssl?.Enabled == true)
            {
                if (string.IsNullOrWhiteSpace(_config.Ssl.Name))
                {
                    sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Name)} is not set.");
                    sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Name))}");
                    sb.AppendLine();
                }
                if (string.IsNullOrWhiteSpace(_config.Ssl.Password))
                {
                    sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Password)} is not set.");
                    sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Password))}");
                    sb.AppendLine();
                }
                if (string.IsNullOrWhiteSpace(_config.Ssl.Email))
                {
                    sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Email)} is not set.");
                    sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Email))}");
                    sb.AppendLine();
                }
                if (!(_config.Ssl.Domains?.Length >= 1) || _config.Ssl.Domains.Any(x => string.IsNullOrWhiteSpace(x)))
                {
                    sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Domains)} is not set.");
                    sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Domains))}");
                    sb.AppendLine();
                }
                if (string.IsNullOrWhiteSpace(_config.Ssl.ChallengeListenPrefix))
                {
                    sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.ChallengeListenPrefix)} is not set.");
                    sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.ChallengeListenPrefix))}");
                    sb.AppendLine();
                }
            }

            var clusterkeys = _config.Clusters?.Select(x => x.Key).ToArray();
            var serverkeys  = _config.Servers?.Select(x => x.Key).ToArray();

            if (serverkeys?.Length > 0 && serverkeys.Length != serverkeys.Distinct(StringComparer.OrdinalIgnoreCase).Count())
            {
                sb.AppendLine($@"Error: {nameof(_config.Servers)} contain non-unique keys.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.Servers))}");
                sb.AppendLine();
            }
            if (clusterkeys?.Length > 0 && clusterkeys.Length != clusterkeys.Distinct(StringComparer.OrdinalIgnoreCase).Count())
            {
                sb.AppendLine($@"Error: {nameof(_config.Clusters)} contain non-unique keys.");
                sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.Clusters))}");
                sb.AppendLine();
            }
            if (_config.Servers?.Length > 0)
            {
                foreach (var server in _config.Servers)
                {
                    if (server.Cluster != null && !clusterkeys.Contains(server.Cluster))
                    {
                        sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Cluster)} reference missing cluster key ""{server.Cluster}"".");
                        sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Cluster))}");
                        sb.AppendLine();
                    }
                    if (string.IsNullOrWhiteSpace(server.SaveFilePath) || !File.Exists(server.SaveFilePath))
                    {
                        sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.SaveFilePath)} is not a valid file path for server instance ""{server.Key}"".");
                        sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.SaveFilePath))}");
                        sb.AppendLine();
                    }
                    if (string.IsNullOrWhiteSpace(server.Ip))
                    {
                        sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Ip)} is not set for server instance ""{server.Key}"".");
                        sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Ip))}");
                        sb.AppendLine();
                    }
                    if (server.Port <= 0)
                    {
                        sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Port)} is not valid for server instance ""{server.Key}"".");
                        sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Port))}");
                        sb.AppendLine();
                    }
                    //if (server.RconPort <= 0)
                    //{
                    //    sb.AppendLine($@"Error: {nameof(config.Servers)}.{nameof(server.RconPort)} is not valid for server instance ""{server.Key}"".");
                    //    sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.RconPort))}");
                    //    sb.AppendLine();
                    //}
                }
            }
            if (_config.Clusters?.Length > 0)
            {
                foreach (var cluster in _config.Clusters)
                {
                    if (string.IsNullOrWhiteSpace(cluster.SavePath) || !Directory.Exists(cluster.SavePath))
                    {
                        sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(cluster.SavePath)} is not a valid directory path for cluster instance ""{cluster.Key}"".");
                        sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(cluster, nameof(cluster.SavePath))}");
                        sb.AppendLine();
                    }
                }
            }

            //todo: for now this section is not really needed unless !imprintcheck is used
            //if (config.ArkMultipliers == null)
            //{
            //    sb.AppendLine($@"Error: {nameof(config.ArkMultipliers)} section is missing from config file.");
            //    sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(config, nameof(config.ArkMultipliers))}");
            //    sb.AppendLine();
            //}

            if (_config.AnonymizeWebApiData)
            {
                System.Console.WriteLine("Anonymizing all data in the WebAPI (anonymizeWebApiData=true)" + Environment.NewLine);
            }

            if (string.IsNullOrWhiteSpace(_config.MemberRoleName))
            {
                _config.MemberRoleName = "ark";
            }

            //load aliases and check integrity
            var aliases = ArkSpeciesAliases.Instance;

            if (aliases == null || !aliases.CheckIntegrity)
            {
                sb.AppendLine($@"Error: ""{ArkSpeciesAliases._filepath}"" is missing, contains invalid json or duplicate aliases.");
                if (aliases != null)
                {
                    foreach (var duplicateAlias in aliases.Aliases?.SelectMany(x => x).GroupBy(x => x)
                             .Where(g => g.Count() > 1)
                             .Select(g => g.Key))
                    {
                        sb.AppendLine($@"Duplicate alias: ""{duplicateAlias}""");
                    }
                }
                sb.AppendLine();
            }

            var errors = sb.ToString();

            if (errors.Length > 0)
            {
                WriteAndWaitForKey(errors);
                return;
            }

            IProgress <string> progress = new Progress <string>(message =>
            {
                Console.AddLog(message);
            });

            var constants = new ArkBot.Constants();

            //if (config.Debug)
            //{
            //    //we reset the state so that every run will be the same
            //    if (File.Exists(constants.DatabaseFilePath)) File.Delete(constants.DatabaseFilePath);
            //    if (File.Exists(constants.SavedStateFilePath)) File.Delete(constants.SavedStateFilePath);

            //    //optionally use a saved database state
            //    var databaseStateFilePath = Path.Combine(config.JsonOutputDirPath, "Database.state");
            //    if (File.Exists(databaseStateFilePath)) File.Copy(databaseStateFilePath, constants.DatabaseFilePath);
            //}

            _savedstate = null;
            try
            {
                if (File.Exists(constants.SavedStateFilePath))
                {
                    _savedstate       = JsonConvert.DeserializeObject <SavedState>(File.ReadAllText(constants.SavedStateFilePath));
                    _savedstate._Path = constants.SavedStateFilePath;
                }
            }
            catch { /*ignore exceptions */ }
            _savedstate = _savedstate ?? new SavedState(constants.SavedStateFilePath);
            //var context = new ArkContext(config, constants, progress);

            //var playedTimeWatcher = new PlayedTimeWatcher(_config);

            var options = new SteamOpenIdOptions
            {
                ListenPrefixes = new[] { _config.SteamOpenIdRelyingServiceListenPrefix },
                RedirectUri    = _config.SteamOpenIdRedirectUri,
            };
            var openId = new BarebonesSteamOpenId(options,
                                                  new Func <bool, ulong, ulong, Task <string> >(async(success, steamId, discordId) =>
            {
                var razorConfig = new TemplateServiceConfiguration
                {
                    DisableTempFileLocking = true,
                    CachingProvider        = new DefaultCachingProvider(t => { })
                };

                using (var service = RazorEngineService.Create(razorConfig))
                {
                    var html = await FileHelper.ReadAllTextTaskAsync(constants.OpenidresponsetemplatePath);
                    return(service.RunCompile(html, constants.OpenidresponsetemplatePath, null, new { Success = success, botName = _config.BotName, botUrl = _config.BotUrl }));
                }
            }));

            var discord = new DiscordSocketClient(new DiscordSocketConfig
            {
                WebSocketProvider = WS4NetProvider.Instance, //required for Win 7
                LogLevel          = LogSeverity.Warning
            });

            discord.Log += msg =>
            {
                Console.AddLog(msg.Message);
                return(Task.CompletedTask);
            };

            var discordCommands = new CommandService(new CommandServiceConfig
            {
            });

            var anonymizeData = new ArkBotAnonymizeData();

            //setup dependency injection
            var thisAssembly = Assembly.GetExecutingAssembly();
            var builder      = new ContainerBuilder();

            builder.RegisterType <ArkServerContext>().AsSelf();
            builder.RegisterInstance(anonymizeData).AsSelf().As <ArkAnonymizeData>();
            if (_config.UseCompatibilityChangeWatcher)
            {
                builder.RegisterType <ArkSaveFileWatcherTimer>().As <IArkSaveFileWatcher>();
            }
            else
            {
                builder.RegisterType <ArkSaveFileWatcher>().As <IArkSaveFileWatcher>();
            }
            builder.RegisterInstance(discord).AsSelf();
            builder.RegisterInstance(discordCommands).AsSelf();
            builder.RegisterType <AutofacDiscordServiceProvider>().As <IServiceProvider>().SingleInstance();
            builder.RegisterType <ArkDiscordBot>();
            builder.RegisterType <UrlShortenerService>().As <IUrlShortenerService>().SingleInstance();
            builder.RegisterInstance(constants).As <IConstants>();
            builder.RegisterInstance(_savedstate).As <ISavedState>();
            builder.RegisterInstance(_config as Config).As <IConfig>();
            //builder.RegisterInstance(playedTimeWatcher).As<IPlayedTimeWatcher>();
            builder.RegisterInstance(openId).As <IBarebonesSteamOpenId>();
            builder.RegisterType <EfDatabaseContext>().AsSelf().As <IEfDatabaseContext>()
            .WithParameter(new TypedParameter(typeof(string), constants.DatabaseConnectionString));
            builder.RegisterType <EfDatabaseContextFactory>();
            builder.RegisterType <Migrations.Configuration>().PropertiesAutowired();
            builder.RegisterType <ArkServerService>().As <IArkServerService>().SingleInstance();
            builder.RegisterType <SavegameBackupService>().As <ISavegameBackupService>().SingleInstance();
            builder.RegisterType <PlayerLastActiveService>().As <IPlayerLastActiveService>().SingleInstance();

            //register vote handlers
            builder.RegisterType <BanVoteHandler>().As <IVoteHandler <BanVote> >();
            builder.RegisterType <UnbanVoteHandler>().As <IVoteHandler <UnbanVote> >();
            builder.RegisterType <RestartServerVoteHandler>().As <IVoteHandler <RestartServerVote> >();
            builder.RegisterType <UpdateServerVoteHandler>().As <IVoteHandler <UpdateServerVote> >();
            builder.RegisterType <DestroyWildDinosVoteHandler>().As <IVoteHandler <DestroyWildDinosVote> >();
            builder.RegisterType <SetTimeOfDayVoteHandler>().As <IVoteHandler <SetTimeOfDayVote> >();
            builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
            builder.RegisterHubs(Assembly.GetExecutingAssembly());
            builder.RegisterType <ArkContextManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance();
            builder.RegisterType <VotingManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance();
            builder.RegisterType <DiscordManager>().AsSelf().SingleInstance();
            builder.RegisterType <ScheduledTasksManager>().AsSelf().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).SingleInstance();
            builder.RegisterType <NotificationManager>().AsSelf().SingleInstance();

            builder.RegisterType <AutofacDependencyResolver>().As <IDependencyResolver>().SingleInstance();

            builder.RegisterType <WebApiStartup>().AsSelf();
            builder.RegisterType <WebApp.WebAppStartup>().AsSelf();

            var webapiConfig = new System.Web.Http.HttpConfiguration();
            var webappConfig = new System.Web.Http.HttpConfiguration();

            builder.RegisterInstance(webapiConfig).Keyed <System.Web.Http.HttpConfiguration>("webapi");
            builder.RegisterInstance(webappConfig).Keyed <System.Web.Http.HttpConfiguration>("webapp");

            builder.RegisterWebApiFilterProvider(webapiConfig);
            builder.Register(c => new AccessControlAuthorizationFilter(c.Resolve <IConfig>()))
            .AsWebApiAuthorizationFilterFor <WebApi.Controllers.BaseApiController>()
            .InstancePerRequest();

            //kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context =>
            //        resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
            //         ).WhenInjectedInto<IStockTicker>();

            Container = builder.Build();

            var dir = Path.GetDirectoryName(constants.DatabaseFilePath);

            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            //update database
            System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion <EfDatabaseContext, Migrations.Configuration>(true, Container.Resolve <Migrations.Configuration>()));

            //create database immediately to support direct (non-ef) access in application
            using (var db = Container.Resolve <IEfDatabaseContext>())
            {
                db.Database.Initialize(false);
            }

            _contextManager = Container.Resolve <ArkContextManager>();
            //server/cluster contexts
            if (_config.Clusters?.Length > 0)
            {
                foreach (var cluster in _config.Clusters)
                {
                    var context = new ArkClusterContext(cluster, anonymizeData);
                    _contextManager.AddCluster(context);
                }
            }

            if (_config.Servers?.Length > 0)
            {
                var playerLastActiveService = Container.Resolve <IPlayerLastActiveService>();
                var backupService           = Container.Resolve <ISavegameBackupService>();
                foreach (var server in _config.Servers)
                {
                    var clusterContext = _contextManager.GetCluster(server.Cluster);
                    var context        = Container.Resolve <ArkServerContext>(
                        new TypedParameter(typeof(ServerConfigSection), server),
                        new TypedParameter(typeof(ArkClusterContext), clusterContext));
                    var initTask = context.Initialize(); //fire and forget
                    _contextManager.AddServer(context);
                }

                // Initialize managers so that they are ready to handle events such as ArkContextManager.InitializationCompleted-event.
                var scheduledTasksManager = Container.Resolve <ScheduledTasksManager>();
                var votingManager         = Container.Resolve <VotingManager>();
                var notificationMangager  = Container.Resolve <NotificationManager>();

                // Trigger manual updates for all servers (initialization)
                foreach (var context in _contextManager.Servers)
                {
                    ManuallyUpdateServers.Add(new MenuItemViewModel
                    {
                        Header           = context.Config.Key,
                        Command          = new DelegateCommand <string>(OnManuallyTriggerServerUpdate),
                        CommandParameter = context.Config.Key
                    });
                    _contextManager.QueueUpdateServerManual(context);
                }

                // Trigger manual updates for all clusters (initialization)
                foreach (var context in _contextManager.Clusters)
                {
                    ManuallyUpdateClusters.Add(new MenuItemViewModel
                    {
                        Header           = context.Config.Key,
                        Command          = new DelegateCommand <string>(OnManuallyTriggerClusterUpdate),
                        CommandParameter = context.Config.Key
                    });
                    _contextManager.QueueUpdateClusterManual(context);
                }
            }

            //run the discord bot
            if (_config.DiscordBotEnabled)
            {
                _runDiscordBotCts  = new CancellationTokenSource();
                _runDiscordBotTask = await Task.Factory.StartNew(async() => await RunDiscordBot(), _runDiscordBotCts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
            }
            else
            {
                Console.AddLog("Discord bot is disabled.");
            }

            //load the species stats data
            await ArkSpeciesStats.Instance.LoadOrUpdate();

            //ssl
            if (_config.Ssl?.Enabled == true)
            {
                var path   = $"{_config.Ssl.Name}.pfx";
                var revoke = false;
                var renew  = false;
                if (File.Exists(path))
                {
                    try
                    {
                        using (var rlt = new X509Certificate2(path, _config.Ssl.Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet))
                        {
                            if (DateTime.Now < rlt.NotBefore || DateTime.Now > rlt.NotAfter.AddDays(-31))
                            {
                                using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
                                {
                                    store.Open(OpenFlags.ReadWrite);
                                    if (store.Certificates.Contains(rlt))
                                    {
                                        store.Remove(rlt);
                                    }
                                    store.Close();
                                }

                                renew = revoke = true;
                            }
                        }
                    }
                    catch (Exception ex) { Logging.LogException("Failed to remove ssl certificate from store.", ex, this.GetType()); }
                }
                else
                {
                    renew = true;
                }

                if (renew)
                {
                    var success = false;
                    Console.AddLog(@"SSL Certificate request issued...");
                    try
                    {
                        using (var client = new AcmeClient(WellKnownServers.LetsEncrypt))
                        {
                            var account = await client.NewRegistraton($"mailto:{_config.Ssl.Email}");

                            account.Data.Agreement = account.GetTermsOfServiceUri();
                            account = await client.UpdateRegistration(account);

                            var authz = await client.NewAuthorization(new AuthorizationIdentifier
                            {
                                Type  = AuthorizationIdentifierTypes.Dns,
                                Value = _config.Ssl.Domains.First()
                            });

                            var httpChallengeInfo = authz.Data.Challenges.Where(c => c.Type == ChallengeTypes.Http01).First();
                            var keyAuthString     = client.ComputeKeyAuthorization(httpChallengeInfo);

                            using (var webapp = Microsoft.Owin.Hosting.WebApp.Start(_config.Ssl.ChallengeListenPrefix, (appBuilder) =>
                            {
                                var challengePath = new PathString("/.well-known/acme-challenge/");
                                appBuilder.Use(new Func <AppFunc, AppFunc>((next) =>
                                {
                                    AppFunc appFunc = async environment =>
                                    {
                                        IOwinContext context = new OwinContext(environment);
                                        if (!context.Request.Path.Equals(challengePath))
                                        {
                                            await next.Invoke(environment);
                                        }

                                        context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK;
                                        context.Response.ContentType = "application/text";
                                        await context.Response.WriteAsync(keyAuthString);
                                    };
                                    return(appFunc);
                                }));
                            }))
                            {
                                var httpChallenge = await client.CompleteChallenge(httpChallengeInfo);

                                authz = await client.GetAuthorization(httpChallenge.Location);

                                while (authz.Data.Status == EntityStatus.Pending)
                                {
                                    await Task.Delay(500);

                                    authz = await client.GetAuthorization(httpChallenge.Location);
                                }

                                if (authz.Data.Status == EntityStatus.Valid)
                                {
                                    if (revoke)
                                    {
                                        //await client.RevokeCertificate(path); //todo: how to revoke a cert when we do not have the AcmeCertificate-object?
                                        if (File.Exists(path))
                                        {
                                            File.Delete(path);
                                        }
                                    }

                                    var csr = new CertificationRequestBuilder();
                                    foreach (var domain in _config.Ssl.Domains)
                                    {
                                        csr.AddName("CN", domain);
                                    }
                                    var cert = await client.NewCertificate(csr);

                                    var pfxBuilder = cert.ToPfx();
                                    var pfx        = pfxBuilder.Build(_config.Ssl.Name, _config.Ssl.Password);
                                    File.WriteAllBytes(path, pfx);

                                    success = true;
                                }
                            }
                        }

                        if (success)
                        {
                            Console.AddLog(@"SSL Certificate request completed!");
                        }
                        else
                        {
                            Console.AddLog(@"SSL Certificate challenge failed!");
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.AddLog($@"SSL Certificate request failed! (""{ex.Message}"")");
                        Logging.LogException("Failed to issue ssl certificate.", ex, this.GetType());
                    }
                }

                if (File.Exists(path))
                {
                    var hostname = _config.Ssl.Domains.First();

                    var attribute = (GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), true)[0];
                    var appId     = attribute.Value;

                    try
                    {
                        using (var rlt = new X509Certificate2(path, _config.Ssl.Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet))
                        {
                            using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
                            {
                                store.Open(OpenFlags.ReadWrite);

                                var certs = store.Certificates.Find(X509FindType.FindBySubjectName, _config.Ssl.Domains.First(), false);

                                if (!store.Certificates.Contains(rlt))
                                {
                                    store.Add(rlt);
                                }
                                store.Close();
                            }

                            if (_config.Ssl.UseCompatibilityNonSNIBindings)
                            {
                                Console.AddLog(@"Binding SSL Certificate to ip/port...");
                            }
                            else
                            {
                                Console.AddLog(@"Binding SSL Certificate to hostname/port...");
                            }
                            foreach (var port in _config.Ssl.Ports)
                            {
                                var commands = new[]
                                {
                                    _config.Ssl.UseCompatibilityNonSNIBindings ? $"netsh http delete sslcert ipport=0.0.0.0:{port}" : $"netsh http delete sslcert hostnameport={hostname}:{port}",
                                         _config.Ssl.UseCompatibilityNonSNIBindings ? $"netsh http add sslcert ipport=0.0.0.0:{port} certhash={rlt.Thumbprint} appid={{{appId}}} certstore=my" : $"netsh http add sslcert hostnameport={hostname}:{port} certhash={rlt.Thumbprint} appid={{{appId}}} certstore=my"
                                };

                                var exitCode = 0;
                                foreach (var cmd in commands)
                                {
                                    await Task.Run(() =>
                                    {
                                        using (var proc = Process.Start(new ProcessStartInfo
                                        {
                                            FileName = "cmd.exe",
                                            Arguments = $"/c {cmd}",
                                            Verb = "runas",
                                            UseShellExecute = false,
                                            WindowStyle = ProcessWindowStyle.Hidden,
                                            CreateNoWindow = true
                                        }))
                                        {
                                            proc.WaitForExit();
                                            exitCode = proc.ExitCode; //only add (last cmd) is interesting
                                        }
                                    });
                                }

                                if (_config.Ssl.UseCompatibilityNonSNIBindings)
                                {
                                    Console.AddLog("[" + (exitCode == 0 ? "Success" : "Failed") + $"] ipport: 0.0.0.0:{port}, thumbprint={rlt.Thumbprint}, appid={{{appId}}}");
                                }
                                else
                                {
                                    Console.AddLog("[" + (exitCode == 0 ? "Success" : "Failed") + $"] hostnameport: {hostname}:{port}, thumbprint={rlt.Thumbprint}, appid={{{appId}}}");
                                }
                            }
                        }
                    }
                    catch (CryptographicException ex)
                    {
                        Logging.LogException("Failed to open SSL certificate.", ex, this.GetType(), LogLevel.FATAL, ExceptionLevel.Unhandled);
                        WriteAndWaitForKey("Failed to open SSL certificate (wrong password?)");
                        return;
                    }
                }
            }

            //webapi
            _webapi = Microsoft.Owin.Hosting.WebApp.Start(_config.WebApiListenPrefix, app =>
            {
                var startup = Container.Resolve <WebApiStartup>();
                startup.Configuration(app, _config, Container, webapiConfig);
            });
            Console.AddLog("Web API started");



            //webapp
            _webapp = Microsoft.Owin.Hosting.WebApp.Start(_config.WebAppListenPrefix, app =>
            {
                var startup = Container.Resolve <WebApp.WebAppStartup>();
                startup.Configuration(app, _config, Container, webappConfig);
            });
            Console.AddLog("Web App started");

            if (_config.WebAppRedirectListenPrefix?.Length > 0)
            {
                foreach (var redir in _config.WebAppRedirectListenPrefix)
                {
                    _webappRedirects.Add(Microsoft.Owin.Hosting.WebApp.Start <WebApp.WebAppRedirectStartup>(url: redir));
                    Console.AddLog("Web App redirect added");
                }
            }
        }
        private async Task FetchCertificateFromLetsEncryptAsync()
        {
            _timer = new Timer(Renew, null, TimeSpan.FromDays(1), Timeout.InfiniteTimeSpan);

            var challengerResponder = new LetEncryptHttpChallengerResponder();

            var host = new WebHostBuilder()
                       .UseKestrel(options =>
            {
                options.Listen(_address, 80);
            })
                       .UseSetting(WebHostDefaults.ApplicationKey, GetType().FullName)
                       .ConfigureServices(services =>
            {
                services.AddSingleton(typeof(IStartup),
                                      challengerResponder);
            }).Build();

            var task = host.RunAsync();

            using (host)
                using (var client = new AcmeClient(WellKnownServers.LetsEncrypt))
                {
                    // Create new registration
                    var account = await client.NewRegistraton("mailto:" + _email);

                    // Accept terms of services
                    account.Data.Agreement = account.GetTermsOfServiceUri();
                    await client.UpdateRegistration(account);

                    // Initialize authorization
                    var authz = await client.NewAuthorization(new AuthorizationIdentifier
                    {
                        Type  = AuthorizationIdentifierTypes.Dns,
                        Value = _domain
                    });

                    // Comptue key authorization for http-01
                    var httpChallengeInfo = authz.Data.Challenges.First(c => c.Type == ChallengeTypes.Http01);
                    var keyAuthString     = client.ComputeKeyAuthorization(httpChallengeInfo);

                    challengerResponder.ChallengeResponse = keyAuthString;


                    var httpChallenge = await client.CompleteChallenge(httpChallengeInfo);

                    // Check authorization status (use the proper challenge to check Authorization State)
                    authz = await client.GetAuthorization(httpChallenge.Location); // or dnsChallenge.Location

                    while (authz.Data.Status == EntityStatus.Pending)
                    {
                        // Wait for ACME server to validate the identifier
                        await Task.Delay(250);

                        authz = await client.GetAuthorization(httpChallenge.Location);
                    }

                    if (authz.Data.Status != EntityStatus.Valid)
                    {
                        throw new InvalidOperationException("Failed to authorize certificate: " + authz.Data.Status);
                    }

                    // Create certificate
                    var csr = new CertificationRequestBuilder();
                    csr.AddName("CN", _domain);
                    var cert = await client.NewCertificate(csr);

                    // Export Pfx
                    var pfxBuilder = cert.ToPfx();
                    var pfx        = pfxBuilder.Build(_domain + " cert", "");
                    _certificate = new X509Certificate2(pfx);

                    _setCachedCertificate?.Invoke(_domain, pfx);
                }
            await task;
        }
Esempio n. 18
0
        public async Task <CertificateRequestResult> GetCertificateAsync(string hostName, string pfxPassword, Action <string, string> challengeCallback, Action <string> cleanupCallback, bool skipTest = false)
        {
            if (hostName == null)
            {
                throw new ArgumentNullException(nameof(hostName));
            }
            if (string.IsNullOrWhiteSpace(hostName))
            {
                throw new ArgumentException("Value cannot be empty or whitespace only string.", nameof(hostName));
            }
            if (challengeCallback == null)
            {
                throw new ArgumentNullException(nameof(challengeCallback));
            }
            if (this.client == null)
            {
                throw new ObjectDisposedException("AcmeContext");
            }

            // Test authorization
            if (!skipTest)
            {
                Trace.WriteLine("Testing authorization:");
                Trace.Indent();
                var probeResult = TestAuthorization(hostName, challengeCallback, cleanupCallback);
                Trace.Unindent();
                if (!probeResult)
                {
                    throw new Exception("Test authorization failed");
                }
            }

            // Get authorization
            Trace.WriteLine("Getting authorization:");
            Trace.Indent();
            var authorizationResult = await GetAuthorization(hostName, challengeCallback, cleanupCallback);

            Trace.Unindent();
            if (authorizationResult != EntityStatus.Valid)
            {
                throw new Exception($"Authorization failed with status {authorizationResult}");
            }

            // Get certificate
            Trace.Write("Requesting certificate...");
            var csr = new CertificationRequestBuilder();

            csr.AddName($"CN={hostName}");
            var acmeCert = await this.client.NewCertificate(csr);

            var cert = new X509Certificate2(acmeCert.Raw);

            Trace.WriteLine("OK");

            // Export PFX
            Trace.Write("Exporting PFX...");
            var pfxBuilder = acmeCert.ToPfx();

            pfxBuilder.FullChain = false;
            var pfxData = pfxBuilder.Build(hostName, pfxPassword);

            Trace.WriteLine("OK");

            return(new CertificateRequestResult {
                Certificate = cert,
                PrivateKey = acmeCert.Key,
                PfxData = pfxData
            });
        }
Esempio n. 19
0
        /// <summary>
        /// Gets a certificate in pfx format for the specified domain names.  If there is an error, an exception is thrown.
        /// </summary>
        /// <param name="emailAddress">The email address to use for authorization.</param>
        /// <param name="domains">The domain names to authorize.  If any domain fails authoriation, an exception is thrown and the certificate request is not completed.</param>
        /// <param name="pfx_password">The password to use for the pfx (certificate) file.  You will need to use this to open the pfx file later.</param>
        /// <param name="prepareForChallenge">This is called by the GetCertificate method once for each domain, specifying the challenge that will be sent and the expected response.  It is the caller's responsibility to ensure that when the challenge is received, the expected response is sent.</param>
        public static async Task <byte[]> GetCertificate(string emailAddress, string[] domains, string pfx_password, Action <CertificateChallenge> prepareForChallenge, Action <string> statusUpdate)
        {
            // Remove duplicates from domains array, preserving order.
            HashSet <string> domainSet  = new HashSet <string>();
            List <string>    domainList = new List <string>();

            foreach (string domain in domains)
            {
                if (domainSet.Contains(domain))
                {
                    continue;
                }
                else
                {
                    domainSet.Add(domain);
                    domainList.Add(domain);
                }
            }
            domains = domainList.ToArray();
            if (domains.Length == 0)
            {
                throw new ArgumentException("No domains specified", "domains");
            }

            statusUpdate("Starting certificate renewal for domains \"" + string.Join("\", \"", domains));

            using (AcmeClient client = new AcmeClient(WellKnownServers.LetsEncrypt))
            {
                // Create new registration
                AcmeAccount account = await client.NewRegistraton("mailto:" + emailAddress);

                // Accept terms of services
                account.Data.Agreement = account.GetTermsOfServiceUri();
                account = await client.UpdateRegistration(account);


                foreach (string domain in domains)
                {
                    statusUpdate("Authorizing domain " + domain);
                    // Initialize authorization
                    AuthorizationIdentifier authorizationIdentifier = new AuthorizationIdentifier();
                    authorizationIdentifier.Type  = AuthorizationIdentifierTypes.Dns;
                    authorizationIdentifier.Value = domain;
                    AcmeResult <Authorization> authz = await client.NewAuthorization(authorizationIdentifier);

                    // Compute key authorization for http-01
                    Challenge httpChallengeInfo = authz.Data.Challenges.Where(c => c.Type == ChallengeTypes.Http01).First();
                    string    keyAuthString     = client.ComputeKeyAuthorization(httpChallengeInfo);

                    // Do something to fullfill the challenge,
                    // e.g. upload key auth string to well known path, or make changes to DNS
                    prepareForChallenge(new CertificateChallenge(domain, httpChallengeInfo.Token, keyAuthString));

                    // Invite ACME server to validate the identifier
                    AcmeResult <Challenge> httpChallenge = await client.CompleteChallenge(httpChallengeInfo);

                    // Check authorization status
                    authz = await client.GetAuthorization(httpChallenge.Location);

                    Stopwatch sw = new Stopwatch();
                    sw.Start();
                    while (authz.Data.Status == EntityStatus.Pending)
                    {
                        if (sw.Elapsed > TimeSpan.FromMinutes(5))
                        {
                            throw new Exception("Timed out waiting for domain \"" + domain + "\" authorization");
                        }
                        // Wait for ACME server to validate the identifier
                        await Task.Delay(10000);

                        authz = await client.GetAuthorization(httpChallenge.Location);
                    }

                    if (authz.Data.Status != EntityStatus.Valid)
                    {
                        throw new Exception("Failed to authorize domain \"" + domain + "\"");
                    }
                }
                statusUpdate("Authorization complete. Creating certificate.");

                // Create certificate
                CertificationRequestBuilder csr = new CertificationRequestBuilder();
                for (int i = 0; i < domains.Length; i++)
                {
                    if (i == 0)
                    {
                        csr.AddName("CN", domains[i]);
                    }
                    else
                    {
                        csr.SubjectAlternativeNames.Add(domains[i]);
                    }
                }

                AcmeCertificate cert = await client.NewCertificate(csr);

                // Export Pfx
                PfxBuilder pfxBuilder = cert.ToPfx();
                byte[]     pfx        = pfxBuilder.Build("LetsEncryptAuto", pfx_password);
                return(pfx);
            }
        }
Esempio n. 20
0
        static async Task <byte[]> RequestNewCertificate(string[] domainNames, AcmeSettings acmeSettings, Func <string, string, Task> challengeResponseReceiver)
        {
            using (var client = new AcmeClient(new Uri(acmeSettings.AcmeUri)))
            {
                // Create new registration
                var account = await client.NewRegistraton($"mailto:{acmeSettings.EmailAddress}");

                // Accept terms of services
                account.Data.Agreement = account.GetTermsOfServiceUri();
                account = await client.UpdateRegistration(account);

                bool unauthorizedDomain = false;
                // optimization: paralellize this
                // var result = Parallel.ForEach(domainNames, async (domaiName, state) => { state.Break(); });
                // await Task.WhenAll(domainNames.Select(async (domainName) => { await Task.FromResult(0); }));
                foreach (var domainName in domainNames)
                {
                    // Initialize authorization
                    var authz = await client.NewAuthorization(new AuthorizationIdentifier
                    {
                        Type  = AuthorizationIdentifierTypes.Dns,
                        Value = domainName
                    });

                    // Comptue key authorization for http-01
                    var httpChallengeInfo = authz.Data.Challenges.Where(c => c.Type == ChallengeTypes.Http01).First();
                    var keyAuthString     = client.ComputeKeyAuthorization(httpChallengeInfo);

                    // Do something to fullfill the challenge,
                    // e.g. upload key auth string to well known path, or make changes to DNS

                    // var cacheGrain = base.GrainFactory.GetGrain<ICacheGrain<string>>($"challenge:{httpChallengeInfo}");
                    // await cacheGrain.Set(new Immutable<string>(keyAuthString), TimeSpan.FromHours(2));
                    await challengeResponseReceiver(httpChallengeInfo.Token, keyAuthString);

                    // Info ACME server to validate the identifier
                    var httpChallenge = await client.CompleteChallenge(httpChallengeInfo);

                    // Check authorization status
                    int tryCount = 1;
                    do
                    {
                        // Wait for ACME server to validate the identifier
                        await Task.Delay(5000);

                        authz = await client.GetAuthorization(httpChallenge.Location);
                    }while (authz.Data.Status == EntityStatus.Pending && ++tryCount <= 10);

                    if (authz.Data.Status != EntityStatus.Valid)
                    {
                        unauthorizedDomain = true;
                        break;
                    }
                }

                if (!unauthorizedDomain)
                {
                    // Create certificate
                    var csr = new CertificationRequestBuilder();
                    csr.AddName("CN", domainNames.First()); // "www.my_domain.com";
                    foreach (var alternativeName in domainNames.Skip(1))
                    {
                        csr.SubjectAlternativeNames.Add(alternativeName);
                    }
                    var cert = await client.NewCertificate(csr);

                    // Export Pfx
                    var pfxBuilder = cert.ToPfx();
                    var pfx        = pfxBuilder.Build(domainNames.First(), acmeSettings.PfxPassword);

                    return(pfx);
                }
                else
                {
                    return(null);
                }
            }
        }
Esempio n. 21
0
        static async Task Main(string[] args)
        {
            var acme    = new AcmeContext(WellKnownServers.LetsEncryptV2);
            var account = await acme.NewAccount("*****@*****.**", true);

            // Save the account key for later use
            var pemKey = acme.AccountKey.ToPem();

            var order = await acme.NewOrder(new[] { "paohou.net", "*.paohou.net" });

            Challenge validateResult;

            RequestFactory.Configure(new TencentCloudOptions()
            {
                DefaultRequestMethod = RequestMethod.POST,
                SecretId             = Environment.GetEnvironmentVariable("TENCENT_CLOUD_SECRETID", EnvironmentVariableTarget.User),
                SecretKey            = Environment.GetEnvironmentVariable("TENCENT_CLOUD_SECRETKEY", EnvironmentVariableTarget.User)
            });
            await DomainRecordUtil.Delete("paohou.net", "_acme-challenge");

            foreach (var authz in await order.Authorizations())
            {
                var dnsChallenge = await authz.Dns();

                var dnsTxt = acme.AccountKey.DnsTxt(dnsChallenge.Token);
                Console.WriteLine(await RequestFactory.Request(new CreateRecordRequestModel("paohou.net", "_acme-challenge", "TXT",
                                                                                            dnsTxt)));
            }
            foreach (var authz in await order.Authorizations())
            {
                do
                {
                    var dnsChallenge = await authz.Dns();

                    var dnsTxt = acme.AccountKey.DnsTxt(dnsChallenge.Token);
                    Console.WriteLine("TXT记录 _acme-challenge:" + dnsTxt);
                    Console.WriteLine("请确认");
                    Console.ReadLine();
                    await dnsChallenge.Validate();

                    validateResult = await dnsChallenge.Resource();

                    Console.WriteLine(validateResult.Status.ToString());
                } while (validateResult.Status == ChallengeStatus.Pending);
            }
//            var privateKey = KeyFactory.NewKey(KeyAlgorithm.ES256);
            IEnumerable <string> domains = (await order.Resource()).Identifiers.Where(i => i.Type == IdentifierType.Dns).Select(i => i.Value);


            string cn = domains.First().StartsWith("*", StringComparison.InvariantCultureIgnoreCase) && domains.Count() > 1
                ? domains.Skip(1).Take(1).First() : domains.First();
            var csrBuilder = new CertificationRequestBuilder();

            csrBuilder.AddName($"C=China, ST=State, L=Chengdu, O=LZQ, CN={cn}");

            //setup the san if necessary
            csrBuilder.SubjectAlternativeNames = domains.Where(a => a != cn).ToList();

            byte[] csrByte = csrBuilder.Generate();
            await order.Finalize(csrByte);

            var cert = await order.Download();

            Console.WriteLine("证书:");
            Console.WriteLine(cert.ToPem());
            Console.WriteLine("私钥:");
            Console.WriteLine(csrBuilder.Key.ToPem());

            /*            var lookup = new LookupClient();
             *          var result = await lookup.QueryAsync("_acme-challenge.xcmaster.com", QueryType.TXT);
             *
             *          var record = result.Answers.TxtRecords().FirstOrDefault();
             *          if (record == null)
             *          {
             *              Console.WriteLine("找不到记录");
             *          }
             *          else
             *          {
             *              foreach (var item in record.Text)
             *              {
             *
             *                  Console.WriteLine(item);
             *              }
             *          }*/
        }