Exemplo n.º 1
0
        private void ComputeKeyAuthorization(AcmeContext context, string[] values)
        {
            var authorizations = context.Authorizations?.TryGet(Options.Type);

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

                foreach (var name in values)
                {
                    var auth = authorizations?.TryGet(name);

                    var challenge = auth?
                                    .Data?
                                    .Challenges?
                                    .Where(c => c.Type == Options.KeyAuthentication)
                                    .FirstOrDefault();

                    if (challenge == null)
                    {
                        ConsoleLogger.Warn("{0} NotFound", name);
                    }
                    else
                    {
                        if (string.IsNullOrWhiteSpace(challenge.KeyAuthorization) || Options.Force)
                        {
                            challenge.KeyAuthorization = client.ComputeKeyAuthorization(challenge);
                        }

                        ConsoleLogger.Info("{0} {1}", name, challenge.KeyAuthorization);
                    }
                }
            }
        }
Exemplo n.º 2
0
        private async Task StartChallenge()
        {
            var authz = await _acmeClient.NewAuthorization(new AuthorizationIdentifier
            {
                Type  = AuthorizationIdentifierTypes.Dns,
                Value = Hostname
            });

            _challenge       = authz.Data.Challenges.First(c => c.Type == ChallengeTypes.Http01);
            KeyAuthorisation = _acmeClient.ComputeKeyAuthorization(_challenge);
        }
Exemplo n.º 3
0
        private async Task <AcmeResult <Authorization> > GetAuthorizationAsync(AcmeClient client, string domainName)
        {
            var account = await client.NewRegistraton($"mailto:{_settings.EmailAddress}");

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

            var auth = await client.NewAuthorization(new AuthorizationIdentifier
            {
                Type  = AuthorizationIdentifierTypes.Dns,
                Value = domainName
            });

            var challenge = auth.Data.Challenges.Where(c => c.Type == ChallengeTypes.Http01).First();
            var response  = client.ComputeKeyAuthorization(challenge);
            await _storage.SetChallengeAndResponseAsync(challenge.Token, response);

            var httpChallenge = await client.CompleteChallenge(challenge);

            return(await PollResultAsync(client, httpChallenge.Location));
        }
Exemplo n.º 4
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);
                }
            }
        }
Exemplo n.º 5
0
        private async Task NewAuthorization(AcmeContext context, string[] values)
        {
            if (context.Authorizations == null)
            {
                context.Authorizations = new Dictionary <string, IDictionary <string, AcmeResult <Authorization> > >();
            }

            var authorizations = context.Authorizations[Options.Type] =
                context.Authorizations.TryGet(Options.Type) ?? new Dictionary <string, AcmeResult <Authorization> >();

            var errors = new List <Exception>();

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

                foreach (var name in values)
                {
                    if (!Options.Force)
                    {
                        var auth = authorizations.TryGet(name);
                        if (auth != null)
                        {
                            if (auth.Data.Status == EntityStatus.Pending ||
                                auth.Data.Status == EntityStatus.Processing ||
                                auth.Data.Status == EntityStatus.Valid && auth.Data.Expires > DateTimeOffset.Now)
                            {
                                ConsoleLogger.Warn("Authorization for identifier {0} {1} already exists, use --force option to create a new authorization.", auth.Data.Identifier.Type, auth.Data.Identifier.Value);
                                continue;
                            }
                        }
                    }

                    var id = new AuthorizationIdentifier()
                    {
                        Type  = Options.Type,
                        Value = name
                    };

                    try
                    {
                        var auth = await client.NewAuthorization(id);

                        authorizations[auth.Data.Identifier.Value] = auth;

                        ConsoleLogger.Info("Authorization for identifier {0} {1} created", auth.Data.Identifier.Type, auth.Data.Identifier.Value);
                        foreach (var challenge in auth.Data.Challenges ?? Array.Empty <Challenge>())
                        {
                            challenge.KeyAuthorization = client.ComputeKeyAuthorization(challenge);
                            ConsoleLogger.Info("{0}: {1}", challenge.Type, challenge.KeyAuthorization);
                        }
                    }
                    catch (Exception ex)
                    {
                        errors.Add(ex);
                    }
                }
            }

            if (errors.Count > 0)
            {
                throw new AggregateException(errors);
            }
        }
Exemplo n.º 6
0
        private async Task ValidateDomainOwnershipAsync(string hostName, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            _logger.LogDebug("Requesting authorization to create certificates for {hostname}", hostName);
            var auth = await _client.NewAuthorization(new AuthorizationIdentifier
            {
                Type  = AuthorizationIdentifierTypes.Dns,
                Value = hostName,
            });

            _logger.LogResponse("NewAuthorization", auth);

            cancellationToken.ThrowIfCancellationRequested();

            var httpChallenge = auth.Data.Challenges.FirstOrDefault(c => c.Type == ChallengeTypes.Http01);

            if (httpChallenge == null)
            {
                throw new InvalidOperationException($"Did not receive challenge information for challenge type {ChallengeTypes.Http01}");
            }

            var keyAuth = _client.ComputeKeyAuthorization(httpChallenge);

            _challengeStore.AddChallengeResponse(httpChallenge.Token, keyAuth);

            cancellationToken.ThrowIfCancellationRequested();

            _logger.LogDebug("Requesting completion of challenge to prove ownership of {hostname}", hostName);

            var challengeCompletion = await _client.CompleteChallenge(httpChallenge);

            _logger.LogResponse("CompleteChallenge", challengeCompletion);

            var retries = 60;
            var delay   = TimeSpan.FromSeconds(2);

            AcmeResult <AuthorizationEntity> authorization;

            while (retries > 0)
            {
                retries--;

                cancellationToken.ThrowIfCancellationRequested();

                authorization = await _client.GetAuthorization(challengeCompletion.Location);

                _logger.LogResponse("GetAuthorization", authorization);

                switch (authorization.Data.Status)
                {
                case EntityStatus.Valid:
                    return;

                case EntityStatus.Pending:
                case EntityStatus.Processing:
                    await Task.Delay(delay);

                    continue;

                case EntityStatus.Invalid:
                    throw InvalidAuthorizationError(hostName, authorization);

                case EntityStatus.Revoked:
                    throw new InvalidOperationException($"The authorization to verify hostname '{hostName}' has been revoked.");

                case EntityStatus.Unknown:
                default:
                    throw new ArgumentOutOfRangeException("Unexpected response from server while validating domain ownership.");
                }
            }

            throw new TimeoutException("Timed out waiting for domain ownership validation.");
        }
Exemplo n.º 7
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);
            }
        }
Exemplo n.º 8
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");
                }
            }
        }
Exemplo n.º 9
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);
            }
        }
        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;
        }