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)); }
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); }
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); }
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()); }
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); } }
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)); }
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); } }
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); } }
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)); }
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 }); }
/// <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); } }
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); }
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"); } } }
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 }); }
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; }
/// <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); } }
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); } } }
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); * } * }*/ }