public void UseStd3AsciiRules_NonLDH_ASCII_Codepoint() { var idnStd3False = new IdnMapping { UseStd3AsciiRules = false }; string unicode = "\u0030\u002D\u0045\u007A"; Assert.Equal(unicode, idnStd3False.GetAscii(unicode), ignoreCase: true); }
public void GetAscii_Invalid() { foreach (var entry in Factory.GetDataset()) { if (!entry.GetASCIIResult.Success) { var map = new IdnMapping(); Assert.Throws<ArgumentException>(() => map.GetAscii(entry.Source)); } } }
public void GetAscii_Success() { foreach (var entry in Factory.GetDataset()) { if (entry.GetASCIIResult.Success) { var map = new IdnMapping(); var asciiResult = map.GetAscii(entry.Source); Assert.Equal(entry.GetASCIIResult.Value, asciiResult, StringComparer.OrdinalIgnoreCase); } } }
public void SimpleValidationTests() { var idn = new IdnMapping(); Assert.Equal("xn--yda", idn.GetAscii("\u0101")); Assert.Equal("xn--yda", idn.GetAscii("\u0101", 0)); Assert.Equal("xn--yda", idn.GetAscii("\u0101", 0, 1)); Assert.Equal("xn--aa-cla", idn.GetAscii("\u0101\u0061\u0041")); Assert.Equal("xn--ab-dla", idn.GetAscii("\u0061\u0101\u0062")); Assert.Equal("xn--ab-ela", idn.GetAscii("\u0061\u0062\u0101")); }
public static void GetUnicodeThrows() { IdnMapping idnMapping = new IdnMapping(); Assert.Throws<ArgumentNullException>("ascii", () => idnMapping.GetUnicode(null, -5)); Assert.Throws<ArgumentNullException>("ascii", () => idnMapping.GetUnicode(null, -5, -10)); Assert.Throws<ArgumentOutOfRangeException>("index", () => idnMapping.GetUnicode("abc", -5, -10)); Assert.Throws<ArgumentOutOfRangeException>("count", () => idnMapping.GetUnicode("abc", 10, -10)); Assert.Throws<ArgumentOutOfRangeException>("byteIndex", () => idnMapping.GetUnicode("abc", 4, 99)); Assert.Throws<ArgumentOutOfRangeException>("ascii", () => idnMapping.GetUnicode("abc", 2, 2)); Assert.Throws<ArgumentException>("ascii", () => idnMapping.GetUnicode("abc", 3, 0)); }
public static void GetUnicode_Invalid(IdnMapping idnMapping, string ascii, int index, int count, Type exceptionType) { if (ascii == null || index + count == ascii.Length) { if (ascii == null || index == 0) { Assert.Throws(exceptionType, () => idnMapping.GetUnicode(ascii)); } Assert.Throws(exceptionType, () => idnMapping.GetUnicode(ascii, index)); } Assert.Throws(exceptionType, () => idnMapping.GetUnicode(ascii, index, count)); }
public void FullyQualifiedDomainNameVsIndividualLabels() { var idn = new IdnMapping(); // ASCII only code points Assert.Equal("\u0061\u0062\u0063", idn.GetAscii("\u0061\u0062\u0063")); // non-ASCII only code points Assert.Equal("xn--d9juau41awczczp", idn.GetAscii("\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067")); // ASCII and non-ASCII code points Assert.Equal("xn--de-jg4avhby1noc0d", idn.GetAscii("\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0")); // Fully Qualified Domain Name Assert.Equal("abc.xn--d9juau41awczczp.xn--de-jg4avhby1noc0d", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0")); }
public void EmbeddedNulls() { var idn = new IdnMapping(); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0101\u0000")); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0101\u0000", 0)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0101\u0000", 0, 2)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0101\u0000\u0101")); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0101\u0000\u0101", 0)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0101\u0000\u0101", 0, 3)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0101\u0000\u0101\u0000")); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0101\u0000\u0101\u0000", 0)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0101\u0000\u0101\u0000", 0, 4)); Assert.Throws<ArgumentException>(() => idn.GetUnicode("abc\u0000", 0, 4)); Assert.Throws<ArgumentException>(() => idn.GetUnicode("ab\u0000c", 0, 4)); }
private string DomainMapper(Match match) { // IdnMapping class with default property values. IdnMapping idn = new IdnMapping(); string domainName = match.Groups[2].Value; try { domainName = idn.GetAscii(domainName); } catch (ArgumentException) { invalid = true; } return match.Groups[1].Value + domainName; }
[InlineData("\u002D\u0062\u002D", true)] // Leading and trailing hyphen minus public void UseStd3AsciiRules_ChangesGetAsciiBehavior(string unicode, bool containsInvalidHyphen) { var idnStd3False = new IdnMapping { UseStd3AsciiRules = false }; var idnStd3True = new IdnMapping { UseStd3AsciiRules = true }; if (containsInvalidHyphen && !s_isWindows) { // ICU always fails on leading/trailing hyphens regardless of the Std3 rules option. Assert.Throws<ArgumentException>("unicode", () => idnStd3False.GetAscii(unicode)); } else { Assert.Equal(unicode, idnStd3False.GetAscii(unicode)); } Assert.Throws<ArgumentException>("unicode", () => idnStd3True.GetAscii(unicode)); }
[InlineData("\u002D\u0062\u002D", true)] // Leading and trailing hyphen minus public void UseStd3AsciiRules_ChangesGetAsciiBehavior(string unicode, bool containsInvalidHyphen) { var idnStd3False = new IdnMapping { UseStd3AsciiRules = false }; var idnStd3True = new IdnMapping { UseStd3AsciiRules = true }; if (containsInvalidHyphen && !s_isWindows) { // ICU always fails on leading/trailing hyphens regardless of the Std3 rules option. Assert.Throws<ArgumentException>("unicode", () => idnStd3False.GetAscii(unicode)); } else { Assert.Equal(unicode, idnStd3False.GetAscii(unicode)); } ArgumentException ae = Assert.Throws<ArgumentException>(() => idnStd3True.GetAscii(unicode)); // sometimes the desktop returns "Unicode" instead of "unicode" for the parameter name. Assert.Equal("unicode", ae.ParamName, ignoreCase: true); }
public void GetUnicode_Succes() { foreach (var entry in Factory.GetDataset()) { if (entry.GetUnicodeResult.Success) { try { var map = new IdnMapping { UseStd3AsciiRules = true, AllowUnassigned = true }; var unicodeResult = map.GetUnicode(entry.Source); Assert.Equal(entry.GetUnicodeResult.Value, unicodeResult, StringComparer.OrdinalIgnoreCase); } catch (ArgumentException) { Assert.Equal(entry.GetUnicodeResult.Value, entry.Source, StringComparer.OrdinalIgnoreCase); } } } }
public static bool IsValidDomain(String domain) { Regex r = new Regex(IP_DOMAIN_REGEX, RegexOptions.IgnoreCase); Match m = r.Match(domain); if (m.Success) { IPAddress ipAddress; return IPAddress.TryParse(m.Groups[1].ToString(), out ipAddress); } Boolean mappingInvalid = false; try { domain = new IdnMapping().GetAscii(domain); } catch (ArgumentException) { mappingInvalid = true; } if (mappingInvalid) { return false; } if (Regex.IsMatch(domain, DOMAIN_NAME_REGEX, RegexOptions.IgnoreCase)) { String[] group = domain.Split('.'); if (group.Length >2) { return IsValidTld(group[2]); } return IsValidTld(group[1]); } return false; }
void Initialize() { if (port == defaultPort || port == 0) { new SmtpPermission(SmtpAccess.Connect).Demand(); } else { new SmtpPermission(SmtpAccess.ConnectToUnrestrictedPort).Demand(); } transport = new SmtpTransport(this); if (Logging.On) { Logging.Associate(Logging.Web, this, transport); } onSendCompletedDelegate = new SendOrPostCallback(SendCompletedWaitCallback); if (MailConfiguration.Smtp != null) { if (MailConfiguration.Smtp.Network != null) { if (host == null || host.Length == 0) { host = MailConfiguration.Smtp.Network.Host; } if (port == 0) { port = MailConfiguration.Smtp.Network.Port; } transport.Credentials = MailConfiguration.Smtp.Network.Credential; transport.EnableSsl = MailConfiguration.Smtp.Network.EnableSsl; if (MailConfiguration.Smtp.Network.TargetName != null) { targetName = MailConfiguration.Smtp.Network.TargetName; } // If the config file contains a domain to be used for the // domain element in the client's EHLO or HELO message, // use it. // // We do not validate whether the domain specified is valid. // It is up to the administrators or user to use the right // value for their scenario. // // Note: per section 4.1.4 of RFC2821, the domain element of // the HELO/EHLO should be used for logging purposes. An // SMTP server should not decide to route an email based on // this value. clientDomain = MailConfiguration.Smtp.Network.ClientDomain; } deliveryFormat = MailConfiguration.Smtp.DeliveryFormat; deliveryMethod = MailConfiguration.Smtp.DeliveryMethod; if (MailConfiguration.Smtp.SpecifiedPickupDirectory != null) { pickupDirectoryLocation = MailConfiguration.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation; } } if (host != null && host.Length != 0) { host = host.Trim(); } if (port == 0) { port = defaultPort; } if (this.targetName == null) { targetName = "SMTPSVC/" + host; } if (clientDomain == null) { // We use the local host name as the default client domain // for the client's EHLO or HELO message. This limits the // information about the host that we share. Additionally, the // FQDN is not available to us or useful to the server (internal // machine connecting to public server). // SMTP RFC's require ASCII only host names in the HELO/EHLO message. string clientDomainRaw = IPGlobalProperties.InternalGetIPGlobalProperties().HostName; IdnMapping mapping = new IdnMapping(); try { clientDomainRaw = mapping.GetAscii(clientDomainRaw); } catch (ArgumentException) { } // For some inputs GetAscii may fail (bad Unicode, etc). If that happens // we must strip out any non-ASCII characters. // If we end up with no characters left, we use the string "LocalHost". This // matches Outlook behavior. StringBuilder sb = new StringBuilder(); char ch; for (int i = 0; i < clientDomainRaw.Length; i++) { ch = clientDomainRaw[i]; if ((ushort)ch <= 0x7F) { sb.Append(ch); } } if (sb.Length > 0) { clientDomain = sb.ToString(); } else { clientDomain = "LocalHost"; } } }
private string ToPunyCode(string hostName) { var idn = new IdnMapping(); return(idn.GetAscii(hostName)); }
public static void GetAscii_Invalid(IdnMapping idnMapping, string unicode, int index, int count, Type exceptionType) { if (unicode == null || index + count == unicode.Length) { if (unicode == null || index == 0) { Assert.Throws(exceptionType, () => idnMapping.GetAscii(unicode)); } Assert.Throws(exceptionType, () => idnMapping.GetAscii(unicode, index)); } Assert.Throws(exceptionType, () => idnMapping.GetAscii(unicode, index, count)); }
public bool ValidateEmail(string email) { if (string.IsNullOrWhiteSpace(email)) { Message.instance.Show(MessageClass.ERROR_EMAIL_EMPTY); return(false); } try { // Normalize the domain email = Regex.Replace(email, @"(@)(.+)$", DomainMapper, RegexOptions.None, TimeSpan.FromMilliseconds(200)); // Examines the domain part of the email and normalizes it. string DomainMapper(Match match) { // Use IdnMapping class to convert Unicode domain names. var idn = new IdnMapping(); // Pull out and process domain name (throws ArgumentException on invalid) var domainName = idn.GetAscii(match.Groups[2].Value); return(match.Groups[1].Value + domainName); } } catch (RegexMatchTimeoutException) { Message.instance.Show(MessageClass.ERROR_EMAIL_INVALID); return(false); } catch (ArgumentException) { Message.instance.Show(MessageClass.ERROR_EMAIL_INVALID); return(false); } try { if (!Regex.IsMatch(email, @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" + @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250))) { Message.instance.Show(MessageClass.ERROR_EMAIL_INVALID); return(false); } } catch (RegexMatchTimeoutException) { Message.instance.Show(MessageClass.ERROR_EMAIL_INVALID); return(false); } var requisicaoWeb = WebRequest.CreateHttp(ConstantClass.SERVER_URL + "Person/Email?email=" + email); requisicaoWeb.Method = "GET"; requisicaoWeb.ContentType = "text"; requisicaoWeb.UserAgent = "RequisicaoWebDemo"; using (var resposta = requisicaoWeb.GetResponse()) { var streamDados = resposta.GetResponseStream(); StreamReader reader = new StreamReader(streamDados); object objResponse = reader.ReadToEnd(); if (bool.Parse(objResponse.ToString()) == false) { Message.instance.Show(MessageClass.ERROR_EMAIL_ALREADY_EXISTS); return(false); } streamDados.Close(); resposta.Close(); } return(true); }
public void SurrogatePairsSeparatedByAscii() { var idn = new IdnMapping(); Assert.Equal("xn--ab-ic6nfag", idn.GetAscii("\uD800\uDF00\u0061\uD800\uDF01\u0042\uD800\uDF02")); }
public HeuristicLinkDetector(IEnumerable <string> topLevelDomains) { TopLevelDomains = new HashSet <string>(topLevelDomains); IDNMapping = new IdnMapping(); }
public override async Task PrepareChallenge() { // Check for substitute domains if (_settings.Validation.AllowDnsSubstitution) { try { // Resolve CNAME in DNS var client = await _dnsClient.GetClients(Challenge.DnsRecordName); var(_, cname) = await client.First().GetTextRecordValues(Challenge.DnsRecordName, 0); // Normalize CNAME var idn = new IdnMapping(); cname = cname.ToLower().Trim().TrimEnd('.'); cname = idn.GetAscii(cname); // Substitute if (cname != Challenge.DnsRecordName) { _log.Information("Detected that {DnsRecordName} is a CNAME that leads to {cname}", Challenge.DnsRecordName, cname); _recordName = cname; } } catch (Exception ex) { _log.Debug("Error checking for substitute domains: {ex}", ex.Message); } } // Create record await CreateRecord(_recordName ?? Challenge.DnsRecordName, Challenge.DnsRecordValue); _log.Information("Answer should now be available at {answerUri}", _recordName); // Verify that the record was created succesfully and wait for possible // propagation/caching/TTL issues to resolve themselves naturally var retry = 0; var maxRetries = _settings.Validation.PreValidateDnsRetryCount; var retrySeconds = _settings.Validation.PreValidateDnsRetryInterval; while (_settings.Validation.PreValidateDns) { if (await PreValidate(retry)) { break; } else { retry += 1; if (retry > maxRetries) { _log.Information("It looks like validation is going to fail, but we will try now anyway..."); break; } else { _log.Information("Will retry in {s} seconds (retry {i}/{j})...", retrySeconds, retry, maxRetries); Thread.Sleep(retrySeconds * 1000); } } } }
public void SurrogatePairsSeparatedByAsciiAndNonAscii() { var idn = new IdnMapping(); Assert.Equal("xn--a-nha4529qfag", idn.GetAscii("\uD800\uDF00\u0101\uD800\uDF01\u0061\uD800\uDF02")); }
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="csrPlugin">Plugin used to generate CSR if it has not been provided in the target</param> /// <param name="runLevel"></param> /// <param name="renewal"></param> /// <param name="target"></param> /// <param name="order"></param> /// <returns></returns> public async Task <CertificateInfo> RequestCertificate(ICsrPlugin?csrPlugin, RunLevel runLevel, Order order) { if (order.Details == null) { throw new InvalidOperationException("No order details found"); } // What are we going to get? var cacheKey = CacheKey(order); var pfxFileInfo = new FileInfo(GetPath(order.Renewal, $"-{cacheKey}{PfxPostFix}")); // Determine/check the common name var identifiers = order.Target.GetHosts(false); var commonNameUni = order.Target.CommonName; var commonNameAscii = string.Empty; if (!string.IsNullOrWhiteSpace(commonNameUni)) { var idn = new IdnMapping(); commonNameAscii = idn.GetAscii(commonNameUni); if (!identifiers.Contains(commonNameAscii, StringComparer.InvariantCultureIgnoreCase)) { _log.Warning($"Common name {commonNameUni} provided is invalid."); commonNameAscii = identifiers.First(); commonNameUni = idn.GetUnicode(commonNameAscii); } } // Determine the friendly name base (for the renewal) var friendlyNameBase = order.Renewal.FriendlyName; if (string.IsNullOrEmpty(friendlyNameBase)) { friendlyNameBase = order.Target.FriendlyName; } if (string.IsNullOrEmpty(friendlyNameBase)) { friendlyNameBase = commonNameUni; } // Determine the friendly name for this specific certificate var friendlyNameIntermediate = friendlyNameBase; if (!string.IsNullOrEmpty(order.FriendlyNamePart)) { friendlyNameIntermediate += $" [{order.FriendlyNamePart}]"; } var friendlyName = $"{friendlyNameIntermediate} @ {_inputService.FormatDate(DateTime.Now)}"; // Try using cached certificate first to avoid rate limiting during // (initial?) deployment troubleshooting. Real certificate requests // will only be done once per day maximum unless the --force parameter // is used. var cache = CachedInfo(order); if (cache != null && cache.CacheFile != null) { if (cache.CacheFile.LastWriteTime > DateTime.Now.AddDays(_settings.Cache.ReuseDays * -1)) { if (runLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Warning("Cached certificate available on disk but not used due to --{switch} switch.", nameof(MainArguments.Force).ToLower()); } else { _log.Warning("Using cached certificate for {friendlyName}. To force a new request of the " + "certificate within {days} days, run with the --{switch} switch.", friendlyNameIntermediate, _settings.Cache.ReuseDays, nameof(MainArguments.Force).ToLower()); return(cache); } } } if (order.Details.Payload.Status != AcmeClient.OrderValid) { // Clear cache and write new cert ClearCache(order.Renewal, postfix: CsrPostFix); if (order.Target.CsrBytes == null) { if (csrPlugin == null) { throw new InvalidOperationException("Missing csrPlugin"); } // Backwards compatible with existing keys, which are not split per order yet. var keyFile = new FileInfo(GetPath(order.Renewal, $".keys")); if (!keyFile.Exists) { keyFile = new FileInfo(GetPath(order.Renewal, $"-{cacheKey}.keys")); } var csr = await csrPlugin.GenerateCsr(keyFile.FullName, commonNameAscii, identifiers); var keySet = await csrPlugin.GetKeys(); order.Target.CsrBytes = csr.GetDerEncoded(); order.Target.PrivateKey = keySet.Private; var csrPath = GetPath(order.Renewal, $"-{cacheKey}{CsrPostFix}"); await File.WriteAllTextAsync(csrPath, _pemService.GetPem("CERTIFICATE REQUEST", order.Target.CsrBytes)); _log.Debug("CSR stored at {path} in certificate cache folder {folder}", Path.GetFileName(csrPath), Path.GetDirectoryName(csrPath)); } _log.Verbose("Submitting CSR"); order.Details = await _client.SubmitCsr(order.Details, order.Target.CsrBytes); if (order.Details.Payload.Status != AcmeClient.OrderValid) { _log.Error("Unexpected order status {status}", order.Details.Payload.Status); throw new Exception($"Unable to complete order"); } } _log.Information("Requesting certificate {friendlyName}", friendlyNameIntermediate); var certInfo = await _client.GetCertificate(order.Details); if (certInfo == null || certInfo.Certificate == null) { throw new Exception($"Unable to get certificate"); } var alternatives = new List <X509Certificate2Collection> { ParseCertificate(certInfo.Certificate, friendlyName, order.Target.PrivateKey) }; foreach (var alt in certInfo.Links["alternate"]) { try { var altCertRaw = await _client.GetCertificate(alt); var altCert = ParseCertificate(altCertRaw, friendlyName, order.Target.PrivateKey); alternatives.Add(altCert); } catch (Exception ex) { _log.Warning("Unable to get alternate certificate: {ex}", ex.Message); } } var selected = Select(alternatives); ClearCache(order.Renewal, postfix: $"*{PfxPostFix}"); ClearCache(order.Renewal, postfix: $"*{PfxPostFixLegacy}"); await File.WriteAllBytesAsync(pfxFileInfo.FullName, selected.Export(X509ContentType.Pfx, order.Renewal.PfxPassword?.Value)); _log.Debug("Certificate written to cache file {path} in certificate cache folder {folder}. It will be " + "reused when renewing within {x} day(s) as long as the Target and Csr parameters remain the same and " + "the --force switch is not used.", pfxFileInfo.Name, pfxFileInfo.Directory.FullName, _settings.Cache.ReuseDays); if (csrPlugin != null) { try { var cert = selected. OfType <X509Certificate2>(). Where(x => x.HasPrivateKey). FirstOrDefault(); if (cert != null) { var certIndex = selected.IndexOf(cert); var newVersion = await csrPlugin.PostProcess(cert); if (newVersion != cert) { newVersion.FriendlyName = friendlyName; selected[certIndex] = newVersion; await File.WriteAllBytesAsync(pfxFileInfo.FullName, selected.Export(X509ContentType.Pfx, order.Renewal.PfxPassword?.Value)); newVersion.Dispose(); } } } catch (Exception ex) { _log.Warning("Private key conversion error: {ex}", ex.Message); } } pfxFileInfo.Refresh(); // Update LastFriendlyName so that the user sees // the most recently issued friendlyName in // the WACS GUI order.Renewal.LastFriendlyName = friendlyNameBase; // Recreate X509Certificate2 with correct flags for Store/Install return(FromCache(pfxFileInfo, order.Renewal.PfxPassword?.Value)); }
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="csrPlugin">Plugin used to generate CSR if it has not been provided in the target</param> /// <param name="runLevel"></param> /// <param name="renewal"></param> /// <param name="target"></param> /// <param name="order"></param> /// <returns></returns> public async Task <CertificateInfo> RequestCertificate( ICsrPlugin?csrPlugin, RunLevel runLevel, Renewal renewal, Target target, OrderDetails order) { // What are we going to get? var cacheKey = CacheKey(renewal, target); var pfxFileInfo = new FileInfo(GetPath(renewal, $"-{cacheKey}{PfxPostFix}")); // Determine/check the common name var identifiers = target.GetHosts(false); var commonNameUni = target.CommonName; var commonNameAscii = string.Empty; if (!string.IsNullOrWhiteSpace(commonNameUni)) { var idn = new IdnMapping(); commonNameAscii = idn.GetAscii(commonNameUni); if (!identifiers.Contains(commonNameAscii, StringComparer.InvariantCultureIgnoreCase)) { _log.Warning($"Common name {commonNameUni} provided is invalid."); commonNameAscii = identifiers.First(); commonNameUni = idn.GetUnicode(commonNameAscii); } } // Determine the friendly name var friendlyNameBase = renewal.FriendlyName; if (string.IsNullOrEmpty(friendlyNameBase)) { friendlyNameBase = target.FriendlyName; } if (string.IsNullOrEmpty(friendlyNameBase)) { friendlyNameBase = commonNameUni; } var friendyName = $"{friendlyNameBase} @ {_inputService.FormatDate(DateTime.Now)}"; // Try using cached certificate first to avoid rate limiting during // (initial?) deployment troubleshooting. Real certificate requests // will only be done once per day maximum unless the --force parameter // is used. var cache = CachedInfo(renewal, target); if (cache != null && cache.CacheFile != null) { if (cache.CacheFile.LastWriteTime > DateTime.Now.AddDays(_settings.Cache.ReuseDays * -1)) { if (runLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Warning("Cached certificate available but not used with the --{switch} switch. " + "Use 'Manage renewals > Run renewal' in the main menu to run unscheduled " + "renewals without hitting rate limits.", nameof(MainArguments.Force).ToLower()); } else { _log.Warning("Using cached certificate for {friendlyName}. To force issue of a " + "new certificate within {days} days, delete the .pfx file from the CertificatePath " + "or run with the --{switch} switch. Be ware that you might run into rate " + "limits doing so.", friendlyNameBase, _settings.Cache.ReuseDays, nameof(MainArguments.Force).ToLower()); return(cache); } } } // Clear cache and write new cert ClearCache(renewal, postfix: CsrPostFix); if (target.CsrBytes == null) { if (csrPlugin == null) { throw new InvalidOperationException("Missing csrPlugin"); } var keyFile = GetPath(renewal, ".keys"); var csr = await csrPlugin.GenerateCsr(keyFile, commonNameAscii, identifiers); var keySet = await csrPlugin.GetKeys(); target.CsrBytes = csr.GetDerEncoded(); target.PrivateKey = keySet.Private; var csrPath = GetPath(renewal, CsrPostFix); File.WriteAllText(csrPath, _pemService.GetPem("CERTIFICATE REQUEST", target.CsrBytes)); _log.Debug("CSR stored at {path} in certificate cache folder {folder}", Path.GetFileName(csrPath), Path.GetDirectoryName(csrPath)); } _log.Verbose("Submitting CSR"); order = await _client.SubmitCsr(order, target.CsrBytes); if (order.Payload.Status != AcmeClient.OrderValid) { _log.Error("Unexpected order status {status}", order.Payload.Status); throw new Exception($"Unable to complete order"); } _log.Information("Requesting certificate {friendlyName}", friendlyNameBase); var rawCertificate = await _client.GetCertificate(order); if (rawCertificate == null) { throw new Exception($"Unable to get certificate"); } // Build pfx archive including any intermediates provided var text = Encoding.UTF8.GetString(rawCertificate); var pfx = new bc.Pkcs.Pkcs12Store(); var startIndex = 0; var endIndex = 0; const string startString = "-----BEGIN CERTIFICATE-----"; const string endString = "-----END CERTIFICATE-----"; while (true) { startIndex = text.IndexOf(startString, startIndex); if (startIndex < 0) { break; } endIndex = text.IndexOf(endString, startIndex); if (endIndex < 0) { break; } endIndex += endString.Length; var pem = text[startIndex..endIndex];
public static List <string> GetImportableItems(int packageId, int itemTypeId) { List <string> items = new List <string>(); // check account int accountCheck = SecurityContext.CheckAccount(DemandAccount.IsAdmin | DemandAccount.NotDemo); if (accountCheck < 0) { return(items); } // load item type if (itemTypeId > 0) { ServiceProviderItemType itemType = PackageController.GetServiceItemType(itemTypeId); // load group ResourceGroupInfo group = ServerController.GetResourceGroup(itemType.GroupId); // Is it DNS Zones? Then create a IDN Mapping object var isDnsZones = group.GroupName == "DNS"; var idn = new IdnMapping(); // get service id int serviceId = PackageController.GetPackageServiceId(packageId, group.GroupName); if (serviceId == 0) { return(items); } // Read existing packages and serviceitems DataTable dtServiceItems = PackageController.GetServiceItemsDataSet(serviceId).Tables[0]; DataTable dtPackageItems = PackageController.GetPackageItemsDataSet(packageId).Tables[0]; // Add already existing packages and serviceitems to lowercase ignorelist List <string> ignorelist = new List <string>(); foreach (DataRow dr in dtServiceItems.Rows) { string serviceItemName = (string)dr["ItemName"]; int serviceItemTypeId = (int)dr["ItemTypeId"]; if (serviceItemTypeId == itemTypeId) { if (!ignorelist.Contains(serviceItemName)) { ignorelist.Add(serviceItemName.ToLower()); } } } foreach (DataRow dr in dtPackageItems.Rows) { string packageItemName = (string)dr["ItemName"]; int packageItemTypeId = (int)dr["ItemTypeId"]; if (packageItemTypeId == itemTypeId) { if (!ignorelist.Contains(packageItemName)) { ignorelist.Add(packageItemName.ToLower()); } } } // instantiate controller IImportController ctrl = null; try { List <string> importableItems = null; ctrl = Activator.CreateInstance(Type.GetType(group.GroupController)) as IImportController; if (ctrl != null) { importableItems = ctrl.GetImportableItems(packageId, itemTypeId, Type.GetType(itemType.TypeName), group); } foreach (string importableItem in importableItems) { if (!ignorelist.Contains(importableItem.ToLower())) { var itemToImport = importableItem; // For DNS zones the compare has been made using ascii, convert to unicode if necessary to make the list of items easier to read if (isDnsZones && itemToImport.StartsWith("xn--")) { itemToImport = idn.GetUnicode(importableItem); } items.Add(itemToImport); } } } catch { /* do nothing */ } } else { return(GetImportableCustomItems(packageId, itemTypeId)); } return(items); }
public void SurrogatePairsSeparatedByNonAscii() { var idn = new IdnMapping(); Assert.Equal("xn--yda263v6b6kfag", idn.GetAscii("\uD800\uDF00\u0101\uD800\uDF01\u305D\uD800\uDF02")); }
public void EmbeddedDomainNameConversion() { var idn = new IdnMapping(); Assert.Equal("abc.xn--d9juau41awczczp.xn--de-jg4avhby1noc0d", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 0)); Assert.Equal("abc.xn--d9juau41awczczp", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 0, 11)); Assert.Equal("abc.xn--d9juau41awczczp.", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 0, 12)); Assert.Equal("abc.xn--d9juau41awczczp.xn--de-jg4avhby1noc0d", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 0, 21)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 3)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 3, 8)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 3, 9)); Assert.Equal("xn--d9juau41awczczp.xn--de-jg4avhby1noc0d", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 4)); Assert.Equal("xn--d9juau41awczczp", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 4, 7)); Assert.Equal("xn--d9juau41awczczp.", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 4, 8)); Assert.Equal("xn--d9juau41awczczp.xn--de-jg4avhby1noc0d", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 4, 17)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 11)); Assert.Throws<ArgumentException>(() => idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 11, 10)); Assert.Equal("xn--de-jg4avhby1noc0d", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 12)); Assert.Equal("xn--de-jg4avhby1noc0d", idn.GetAscii("\u0061\u0062\u0063.\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067.\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 12, 9)); }
/// <summary> /// Convert puny-code to unicode /// </summary> /// <param name="name"></param> /// <returns></returns> private string ProcessName(string name) { var idn = new IdnMapping(); return(idn.GetUnicode(name.ToLower())); }
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List <object> output) { if (!this.suppressRead && !this.handshakeFailed) { int writerIndex = input.WriterIndex; Exception error = null; try { bool continueLoop = true; for (int i = 0; i < MAX_SSL_RECORDS && continueLoop; i++) { int readerIndex = input.ReaderIndex; int readableBytes = writerIndex - readerIndex; if (readableBytes < TlsUtils.SSL_RECORD_HEADER_LENGTH) { // Not enough data to determine the record type and length. return; } int command = input.GetByte(readerIndex); // tls, but not handshake command switch (command) { case TlsUtils.SSL_CONTENT_TYPE_CHANGE_CIPHER_SPEC: case TlsUtils.SSL_CONTENT_TYPE_ALERT: int len = TlsUtils.GetEncryptedPacketLength(input, readerIndex); // Not an SSL/TLS packet if (len == TlsUtils.NOT_ENCRYPTED) { this.handshakeFailed = true; var e = new NotSslRecordException( "not an SSL/TLS record: " + ByteBufferUtil.HexDump(input)); input.SkipBytes(input.ReadableBytes); TlsUtils.NotifyHandshakeFailure(context, e); throw e; } if (len == TlsUtils.NOT_ENOUGH_DATA || writerIndex - readerIndex - TlsUtils.SSL_RECORD_HEADER_LENGTH < len) { // Not enough data return; } // increase readerIndex and try again. input.SkipBytes(len); continue; case TlsUtils.SSL_CONTENT_TYPE_HANDSHAKE: int majorVersion = input.GetByte(readerIndex + 1); // SSLv3 or TLS if (majorVersion == 3) { int packetLength = input.GetUnsignedShort(readerIndex + 3) + TlsUtils.SSL_RECORD_HEADER_LENGTH; if (readableBytes < packetLength) { // client hello incomplete; try again to decode once more data is ready. return; } // See https://tools.ietf.org/html/rfc5246#section-7.4.1.2 // // Decode the ssl client hello packet. // We have to skip bytes until SessionID (which sum to 43 bytes). // // struct { // ProtocolVersion client_version; // Random random; // SessionID session_id; // CipherSuite cipher_suites<2..2^16-2>; // CompressionMethod compression_methods<1..2^8-1>; // select (extensions_present) { // case false: // struct {}; // case true: // Extension extensions<0..2^16-1>; // }; // } ClientHello; // int endOffset = readerIndex + packetLength; int offset = readerIndex + 43; if (endOffset - offset < 6) { continueLoop = false; break; } int sessionIdLength = input.GetByte(offset); offset += sessionIdLength + 1; int cipherSuitesLength = input.GetUnsignedShort(offset); offset += cipherSuitesLength + 2; int compressionMethodLength = input.GetByte(offset); offset += compressionMethodLength + 1; int extensionsLength = input.GetUnsignedShort(offset); offset += 2; int extensionsLimit = offset + extensionsLength; if (extensionsLimit > endOffset) { // Extensions should never exceed the record boundary. continueLoop = false; break; } for (;;) { if (extensionsLimit - offset < 4) { continueLoop = false; break; } int extensionType = input.GetUnsignedShort(offset); offset += 2; int extensionLength = input.GetUnsignedShort(offset); offset += 2; if (extensionsLimit - offset < extensionLength) { continueLoop = false; break; } // SNI // See https://tools.ietf.org/html/rfc6066#page-6 if (extensionType == 0) { offset += 2; if (extensionsLimit - offset < 3) { continueLoop = false; break; } int serverNameType = input.GetByte(offset); offset++; if (serverNameType == 0) { int serverNameLength = input.GetUnsignedShort(offset); offset += 2; if (serverNameLength <= 0 || extensionsLimit - offset < serverNameLength) { continueLoop = false; break; } string hostname = input.ToString(offset, serverNameLength, Encoding.UTF8); //try //{ // select(ctx, IDN.toASCII(hostname, // IDN.ALLOW_UNASSIGNED).toLowerCase(Locale.US)); //} //catch (Throwable t) //{ // PlatformDependent.throwException(t); //} var idn = new IdnMapping() { AllowUnassigned = true }; hostname = idn.GetAscii(hostname); #if NETSTANDARD1_3 // TODO: netcore does not have culture sensitive tolower() hostname = hostname.ToLowerInvariant(); #else hostname = hostname.ToLower(new CultureInfo("en-US")); #endif this.Select(context, hostname); return; } else { // invalid enum value continueLoop = false; break; } } offset += extensionLength; } } break; // Fall-through default: //not tls, ssl or application data, do not try sni continueLoop = false; break; } } } catch (Exception e) { error = e; // unexpected encoding, ignore sni and use default if (Logger.DebugEnabled) { Logger.Warn($"Unexpected client hello packet: {ByteBufferUtil.HexDump(input)}", e); } } if (this.serverTlsSniSettings.DefaultServerHostName != null) { // Just select the default server TLS setting this.Select(context, this.serverTlsSniSettings.DefaultServerHostName); } else { this.handshakeFailed = true; var e = new DecoderException($"failed to get the server TLS setting {error}"); TlsUtils.NotifyHandshakeFailure(context, e); throw e; } } }
public void IllegalChars(bool useStd3AsciiRules) { var idn = new IdnMapping(); idn.UseStd3AsciiRules = useStd3AsciiRules; string testString; for (int i = 0; i <= 0x1F; i++) { testString = "abc" + new string((char)i, 1) + "def"; Assert.Throws<ArgumentException>(() => idn.GetAscii(testString)); Assert.Throws<ArgumentException>(() => idn.GetUnicode(testString)); } testString = "abc" + new string((char)0x7F, 1) + "def"; Assert.Throws<ArgumentException>(() => idn.GetAscii(testString)); Assert.Throws<ArgumentException>(() => idn.GetUnicode(testString)); }
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="binding"></param> /// <returns></returns> public CertificateInfo RequestCertificate(ICsrPlugin csrPlugin, Renewal renewal, Target target, OrderDetails order) { // What are we going to get? var pfxFileInfo = new FileInfo(PfxFilePath(renewal)); // Determine/check the common name var identifiers = target.GetHosts(false); var commonName = target.CommonName; if (!string.IsNullOrWhiteSpace(commonName)) { var idn = new IdnMapping(); commonName = idn.GetAscii(commonName); if (!identifiers.Contains(commonName, StringComparer.InvariantCultureIgnoreCase)) { _log.Warning($"Common name {commonName} provided is invalid."); commonName = identifiers.First(); } } // Determine the friendly name var friendlyName = renewal.FriendlyName; if (string.IsNullOrEmpty(friendlyName)) { friendlyName = target.FriendlyName; } if (string.IsNullOrEmpty(friendlyName)) { friendlyName = commonName; } // Try using cached certificate first to avoid rate limiting during // (initial?) deployment troubleshooting. Real certificate requests // will only be done once per day maximum unless the --force parameter // is used. var cache = CachedInfo(renewal); if (cache != null && cache.PfxFile.LastWriteTime > DateTime.Now.AddDays(-1) && cache.Match(target)) { if (_runLevel.HasFlag(RunLevel.Force)) { _log.Warning("Cached certificate available but not used with --{switch}. Use 'Renew specific' or " + "'Renew all' in the main menu to run unscheduled renewals without hitting rate limits.", nameof(MainArguments.Force).ToLower()); } else { _log.Warning("Using cached certificate for {friendlyName}. To force issue of a new certificate within " + "24 hours, delete the .pfx file from the CertificatePath or run with the --{switch} switch. " + "Be ware that you might run into rate limits doing so.", friendlyName, nameof(MainArguments.Force).ToLower()); return(cache); } } var csr = csrPlugin.GenerateCsr(commonName, identifiers); var csrBytes = csr.CreateSigningRequest(); order = _client.SubmitCsr(order, csrBytes); File.WriteAllText(GetPath(renewal, "-csr.pem"), GetPem("CERTIFICATE REQUEST", csrBytes)); _log.Information("Requesting certificate {friendlyName}", friendlyName); var rawCertificate = _client.GetCertificate(order); if (rawCertificate == null) { throw new Exception($"Unable to get certificate"); } var certificate = new X509Certificate2(rawCertificate); var certificateExport = certificate.Export(X509ContentType.Cert); var crtPem = GetPem("CERTIFICATE", certificateExport); // Get issuer certificate var chain = new X509Chain(); chain.Build(certificate); X509Certificate2 issuerCertificate = chain.ChainElements[1].Certificate; var issuerCertificateExport = issuerCertificate.Export(X509ContentType.Cert); var issuerPem = GetPem("CERTIFICATE", issuerCertificateExport); // Build pfx archive var pfx = new bc.Pkcs.Pkcs12Store(); var bcCertificate = ParsePem <bc.X509.X509Certificate>(crtPem); var bcCertificateEntry = new bc.Pkcs.X509CertificateEntry(bcCertificate); var bcCertificateAlias = bcCertificate.SubjectDN.ToString(); var bcPrivateKeyEntry = new bc.Pkcs.AsymmetricKeyEntry(csrPlugin.GetPrivateKey()); pfx.SetCertificateEntry(bcCertificateAlias, bcCertificateEntry); pfx.SetKeyEntry(bcCertificateAlias, bcPrivateKeyEntry, new[] { bcCertificateEntry }); var bcIssuer = ParsePem <bc.X509.X509Certificate>(issuerPem); var bcIssuerEntry = new bc.Pkcs.X509CertificateEntry(bcIssuer); var bcIssuerAlias = bcIssuer.SubjectDN.ToString(); pfx.SetCertificateEntry(bcIssuerAlias, bcIssuerEntry); var pfxStream = new MemoryStream(); pfx.Save(pfxStream, null, new bc.Security.SecureRandom()); pfxStream.Position = 0; using (var pfxStreamReader = new BinaryReader(pfxStream)) { var tempPfx = new X509Certificate2( pfxStreamReader.ReadBytes((int)pfxStream.Length), (string)null, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); if (csrPlugin.CanConvert()) { try { var converted = csrPlugin.Convert(tempPfx.PrivateKey); if (converted != null) { tempPfx.PrivateKey = converted; } } catch { _log.Warning("Private key conversion error."); } } tempPfx.FriendlyName = $"{friendlyName} {DateTime.Now.ToUserString()}"; File.WriteAllBytes(pfxFileInfo.FullName, tempPfx.Export(X509ContentType.Pfx, renewal.PfxPassword)); pfxFileInfo.Refresh(); } // Update LastFriendlyName so that the user sees // the most recently issued friendlyName in // the WACS GUI renewal.LastFriendlyName = friendlyName; // Recreate X509Certificate2 with correct flags for Store/Install return(new CertificateInfo() { Certificate = ReadForUse(pfxFileInfo, renewal.PfxPassword), PfxFile = pfxFileInfo, PfxFilePassword = renewal.PfxPassword }); }
public IISBindingHelper(ILogService log, IIISClient iisClient) { _log = log; _iisClient = iisClient; _idnMapping = new IdnMapping(); }
public bool IsValidEmail(string strIn) { var invalid = false; if (String.IsNullOrEmpty(strIn)) { return(false); } MatchEvaluator DomainMapper = match => { // IdnMapping class with default property values. IdnMapping idn = new IdnMapping(); string domainName = match.Groups[2].Value; try { domainName = idn.GetAscii(domainName); } catch (ArgumentException) { invalid = true; } return(match.Groups[1].Value + domainName); }; // Use IdnMapping class to convert Unicode domain names. strIn = Regex.Replace(strIn, @"(@)(.+)$", DomainMapper); if (invalid) { return(false); } // Return true if strIn is in valid e-mail format. if (Regex.IsMatch(strIn, @"^(?("")(""[^""]+?""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" + @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9]{2,17}))$", RegexOptions.IgnoreCase)) { string s = strIn.Remove(0, strIn.IndexOf('@') + 1); string emailBody = s.Remove(s.IndexOf('.')); switch (emailBody) { case "live": case "mail": case "yandex": case "yahoo": case "aol": case "eclipso": case "maills": case "gmail": case "googlemail": case "firemail": case "maili": case "hotmail": case "emailn": case "outlook": case "rediffmail": case "oyoony": case "lycos": case "directbox": case "new-post": case "gmx": case "slucia": case "5x2": case "smart-mail": case "spl": case "t-online": case "compu-freemail": case "web": case "x-mail": case "k": case "mc-free": case "freenet": case "k-bg": case "overmail": case "anpa": case "freemailer": case "vcmail": case "mail4nature": case "uims": case "1vbb": case "uni": case "techmail": case "hushmail": case "freemail-24": case "guru": case "email": case "1email": case "canineworld": case "zelx": case "sify": case "softhome": case "kuekomail": case "mailde": case "mail-king": case "noxamail": case "h3c": case "arcor": case "logomail": case "ueberschuss": case "chattler": case "modellraketen": return(true); } } return(false); }
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="binding"></param> /// <returns></returns> public async Task <CertificateInfo> RequestCertificate(ICsrPlugin csrPlugin, RunLevel runLevel, Renewal renewal, Target target, OrderDetails order) { // What are we going to get? var pfxFileInfo = new FileInfo(PfxFilePath(renewal)); // Determine/check the common name var identifiers = target.GetHosts(false); var commonNameUni = target.CommonName; var commonNameAscii = string.Empty; if (!string.IsNullOrWhiteSpace(commonNameUni)) { var idn = new IdnMapping(); commonNameAscii = idn.GetAscii(commonNameUni); if (!identifiers.Contains(commonNameAscii, StringComparer.InvariantCultureIgnoreCase)) { _log.Warning($"Common name {commonNameUni} provided is invalid."); commonNameAscii = identifiers.First(); commonNameUni = idn.GetUnicode(commonNameAscii); } } // Determine the friendly name var friendlyName = renewal.FriendlyName; if (string.IsNullOrEmpty(friendlyName)) { friendlyName = target.FriendlyName; } if (string.IsNullOrEmpty(friendlyName)) { friendlyName = commonNameUni; } // Try using cached certificate first to avoid rate limiting during // (initial?) deployment troubleshooting. Real certificate requests // will only be done once per day maximum unless the --force parameter // is used. var cache = CachedInfo(renewal); if (cache != null && cache.CacheFile.LastWriteTime > DateTime.Now.AddDays(_settings.Cache.ReuseDays * -1) && cache.Match(target)) { if (runLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Warning("Cached certificate available but not used with --{switch}. Use 'Renew specific' or " + "'Renew all' in the main menu to run unscheduled renewals without hitting rate limits.", nameof(MainArguments.Force).ToLower()); } else { _log.Warning("Using cached certificate for {friendlyName}. To force issue of a new certificate within " + "24 hours, delete the .pfx file from the CertificatePath or run with the --{switch} switch. " + "Be ware that you might run into rate limits doing so.", friendlyName, nameof(MainArguments.Force).ToLower()); return(cache); } } if (target.CsrBytes == null) { var csr = await csrPlugin.GenerateCsr(GetPath(renewal, ".keys"), commonNameAscii, identifiers); target.CsrBytes = csr.GetDerEncoded(); target.PrivateKey = (await csrPlugin.GetKeys()).Private; File.WriteAllText(GetPath(renewal, "-csr.pem"), _pemService.GetPem("CERTIFICATE REQUEST", target.CsrBytes)); } _log.Verbose("Submitting CSR"); order = await _client.SubmitCsr(order, target.CsrBytes); if (order.Payload.Status != AcmeClient.OrderValid) { _log.Error("Unexpected order status {status}", order.Payload.Status); throw new Exception($"Unable to complete order"); } _log.Information("Requesting certificate {friendlyName}", friendlyName); var rawCertificate = await _client.GetCertificate(order); if (rawCertificate == null) { throw new Exception($"Unable to get certificate"); } byte[] certificateExport; using (var certificate = new X509Certificate2(rawCertificate)) { certificateExport = certificate.Export(X509ContentType.Cert); } var crtPem = _pemService.GetPem("CERTIFICATE", certificateExport); // Get issuer certificate var issuerCertificate = new X509Certificate2(rawCertificate.Skip(certificateExport.Length).ToArray()); var issuerCertificateExport = issuerCertificate.Export(X509ContentType.Cert); var issuerPem = _pemService.GetPem("CERTIFICATE", issuerCertificateExport); issuerCertificate.Dispose(); // Build pfx archive var pfx = new bc.Pkcs.Pkcs12Store(); var bcCertificate = _pemService.ParsePem <bc.X509.X509Certificate>(crtPem); var bcCertificateEntry = new bc.Pkcs.X509CertificateEntry(bcCertificate); var bcCertificateAlias = bcCertificate.SubjectDN.ToString(); pfx.SetCertificateEntry(bcCertificateAlias, bcCertificateEntry); if (target.PrivateKey != null) { var bcPrivateKeyEntry = new bc.Pkcs.AsymmetricKeyEntry(target.PrivateKey); pfx.SetKeyEntry(bcCertificateAlias, bcPrivateKeyEntry, new[] { bcCertificateEntry }); } var bcIssuer = _pemService.ParsePem <bc.X509.X509Certificate>(issuerPem); var bcIssuerEntry = new bc.Pkcs.X509CertificateEntry(bcIssuer); var bcIssuerAlias = bcIssuer.SubjectDN.ToString(); pfx.SetCertificateEntry(bcIssuerAlias, bcIssuerEntry); var pfxStream = new MemoryStream(); pfx.Save(pfxStream, null, new bc.Security.SecureRandom()); pfxStream.Position = 0; using var pfxStreamReader = new BinaryReader(pfxStream); var tempPfx = new X509Certificate2( pfxStreamReader.ReadBytes((int)pfxStream.Length), (string)null, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); tempPfx.FriendlyName = $"{friendlyName} {_inputService.FormatDate(DateTime.Now)}"; File.WriteAllBytes(pfxFileInfo.FullName, tempPfx.Export(X509ContentType.Pfx, renewal.PfxPassword?.Value)); if (csrPlugin != null) { try { var newVersion = await csrPlugin.PostProcess(tempPfx); if (newVersion != tempPfx) { newVersion.FriendlyName = $"{friendlyName} {_inputService.FormatDate(DateTime.Now)}"; File.WriteAllBytes(pfxFileInfo.FullName, newVersion.Export(X509ContentType.Pfx, renewal.PfxPassword?.Value)); newVersion.Dispose(); } } catch (Exception) { _log.Warning("Private key conversion error."); } } pfxFileInfo.Refresh(); tempPfx.Dispose(); // Update LastFriendlyName so that the user sees // the most recently issued friendlyName in // the WACS GUI renewal.LastFriendlyName = friendlyName; // Recreate X509Certificate2 with correct flags for Store/Install return(new CertificateInfo() { Certificate = ReadForUse(pfxFileInfo, renewal.PfxPassword?.Value), CacheFile = pfxFileInfo, CacheFilePassword = renewal.PfxPassword?.Value }); }
private static string EncodeHostname(string hostname) { var idn = new IdnMapping(); return(idn.GetAscii(hostname)); }
public void TestGetAsciiWithDot() { string result = ""; Exception ex = Record.Exception(()=> result = new IdnMapping().GetAscii(".")); if (ex == null) { // Windows and OSX always throw exception. some versions of Linux succeed and others throw exception Assert.False(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); Assert.False(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)); Assert.Equal(result, "."); } else { Assert.IsType<ArgumentException>(ex); } }
private static string ExtractSniHostname(IByteBuffer input, int offset, int endOffset) { // See https://tools.ietf.org/html/rfc5246#section-7.4.1.2 // // Decode the ssl client hello packet. // // struct { // ProtocolVersion client_version; // Random random; // SessionID session_id; // CipherSuite cipher_suites<2..2^16-2>; // CompressionMethod compression_methods<1..2^8-1>; // select (extensions_present) { // case false: // struct {}; // case true: // Extension extensions<0..2^16-1>; // }; // } ClientHello; // // We have to skip bytes until SessionID (which sum to 34 bytes in this case). offset += 34; if (endOffset - offset >= 6) { int sessionIdLength = input.GetByte(offset); offset += sessionIdLength + 1; int cipherSuitesLength = input.GetUnsignedShort(offset); offset += cipherSuitesLength + 2; int compressionMethodLength = input.GetByte(offset); offset += compressionMethodLength + 1; int extensionsLength = input.GetUnsignedShort(offset); offset += 2; int extensionsLimit = offset + extensionsLength; // Extensions should never exceed the record boundary. if (extensionsLimit <= endOffset) { while (extensionsLimit - offset >= 4) { int extensionType = input.GetUnsignedShort(offset); offset += 2; int extensionLength = input.GetUnsignedShort(offset); offset += 2; if (extensionsLimit - offset < extensionLength) { break; } // SNI // See https://tools.ietf.org/html/rfc6066#page-6 if (0u >= (uint)extensionType) { offset += 2; if (extensionsLimit - offset < 3) { break; } int serverNameType = input.GetByte(offset); offset++; if (0u >= (uint)serverNameType) { int serverNameLength = input.GetUnsignedShort(offset); offset += 2; if ((uint)(serverNameLength - 1) > SharedConstants.TooBigOrNegative /*serverNameLength <= 0*/ || extensionsLimit - offset < serverNameLength) { break; } string hostname = input.ToString(offset, serverNameLength, Encoding.UTF8); var idn = new IdnMapping() { AllowUnassigned = true }; return(idn.GetAscii(hostname).ToLower(UnitedStatesCultureInfo)); } else { // invalid enum value break; } } offset += extensionLength; } } } return(null); }
/// <summary> /// Creates a MailMessage for the current MailAttribute instance. /// </summary> protected EmailMessage GenerateProspectiveMailMessage(MailAttributes mail) { var idnmapping = new IdnMapping(); var emailAddresses = mail.To .Select( t => { var domainSplit = t.Address.Split('@'); return(new EmailAddress(domainSplit[0] + "@" + idnmapping.GetAscii(domainSplit[1])) { Type = "to" }); }) .Union( mail.Cc.Select( t => { var domainSplit = t.Address.Split('@'); return(new EmailAddress(domainSplit[0] + "@" + idnmapping.GetAscii(domainSplit[1])) { Type = "cc" }); })) .Union( mail.Bcc.Select( t => { var domainSplit = t.Address.Split('@'); return(new EmailAddress(domainSplit[0] + "@" + idnmapping.GetAscii(domainSplit[1])) { Type = "bcc" }); })); //create base message var message = new EmailMessage { FromName = mail.From.DisplayName, FromEmail = mail.From.Address, To = emailAddresses, Subject = mail.Subject, Important = mail.Priority == MailPriority.High, PreserveRecipients = true }; // We need to set Reply-To as a custom header if (mail.ReplyTo.Any()) { message.AddHeader("Reply-To", string.Join(" , ", mail.ReplyTo)); } // Adding content to the message foreach (var view in mail.AlternateViews) { var reader = new StreamReader(view.ContentStream, Encoding.UTF8, true, 1024, true); var body = reader.ReadToEnd(); if (view.ContentType.MediaType == MediaTypeNames.Text.Plain) { message.Text = body; } if (view.ContentType.MediaType == MediaTypeNames.Text.Html) { message.Html = body; } } // Going through headers and adding them to the message mail.Headers.ToList().ForEach(h => message.AddHeader(h.Key, h.Value)); // Adding the attachments var attachments = new List <EmailAttachment>(); foreach (var mailAttachment in mail.Attachments.Select(attachment => Utils.AttachmentCollection.ModifyAttachmentProperties(attachment.Key, attachment.Value, false))) { using (var stream = new MemoryStream()) { mailAttachment.ContentStream.CopyTo(stream); var base64Data = Convert.ToBase64String(stream.ToArray()); attachments.Add(new EmailAttachment { Content = base64Data, Name = ReplaceGermanCharacters(mailAttachment.Name), Type = mailAttachment.ContentType.MediaType, }); } } message.Attachments = attachments; return(message); }
private void Initialize() { _transport = new SmtpTransport(this); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Associate(this, _transport); } _onSendCompletedDelegate = new SendOrPostCallback(SendCompletedWaitCallback); if (_host != null && _host.Length != 0) { _host = _host.Trim(); } if (_port == 0) { _port = DefaultPort; } if (_targetName == null) { _targetName = "SMTPSVC/" + _host; } if (_clientDomain == null) { // We use the local host name as the default client domain // for the client's EHLO or HELO message. This limits the // information about the host that we share. Additionally, the // FQDN is not available to us or useful to the server (internal // machine connecting to public server). // SMTP RFC's require ASCII only host names in the HELO/EHLO message. string clientDomainRaw = IPGlobalProperties.GetIPGlobalProperties().HostName; IdnMapping mapping = new IdnMapping(); try { clientDomainRaw = mapping.GetAscii(clientDomainRaw); } catch (ArgumentException) { } // For some inputs GetAscii may fail (bad Unicode, etc). If that happens // we must strip out any non-ASCII characters. // If we end up with no characters left, we use the string "LocalHost". This // matches Outlook behavior. StringBuilder sb = new StringBuilder(); char ch; for (int i = 0; i < clientDomainRaw.Length; i++) { ch = clientDomainRaw[i]; if ((ushort)ch <= 0x7F) { sb.Append(ch); } } if (sb.Length > 0) { _clientDomain = sb.ToString(); } else { _clientDomain = "LocalHost"; } } }
public static void TestEquals(bool allowUnassigned, bool useStd3AsciiRules) { // first check for equals IdnMapping original = new IdnMapping() { AllowUnassigned = allowUnassigned, UseStd3AsciiRules = useStd3AsciiRules }; IdnMapping identical = new IdnMapping() { AllowUnassigned = allowUnassigned, UseStd3AsciiRules = useStd3AsciiRules }; Assert.True(original.Equals(identical)); Assert.Equal(original.GetHashCode(), identical.GetHashCode()); // now three sets of unequals IdnMapping unequal1 = new IdnMapping() { AllowUnassigned = allowUnassigned, UseStd3AsciiRules = !useStd3AsciiRules }; Assert.False(original.Equals(unequal1)); Assert.NotEqual(original.GetHashCode(), unequal1.GetHashCode()); IdnMapping unequal2 = new IdnMapping() { AllowUnassigned = !allowUnassigned, UseStd3AsciiRules = useStd3AsciiRules }; Assert.False(original.Equals(unequal2)); Assert.NotEqual(original.GetHashCode(), unequal2.GetHashCode()); IdnMapping unequal3 = new IdnMapping() { AllowUnassigned = !allowUnassigned, UseStd3AsciiRules = useStd3AsciiRules }; Assert.False(original.Equals(unequal3)); Assert.NotEqual(original.GetHashCode(), unequal3.GetHashCode()); }
void GetUnicode(IdnMapping m, string source, string expected, object label) { Assert.AreEqual(expected, m.GetUnicode(source), label != null ? label.ToString() : expected); }
private string FromPunyCode(string hostName) { var idn = new IdnMapping(); return(idn.GetUnicode(hostName)); }
// // Will convert a host name into its idn equivalent + tell you if it had a valid idn label // internal static unsafe string IdnEquivalent(char *hostname, int start, int end, ref bool allAscii, ref bool atLeastOneValidIdn) { string bidiStrippedHost = null; string idnEquivalent = IdnEquivalent(hostname, start, end, ref allAscii, ref bidiStrippedHost); if (idnEquivalent != null) { string strippedHost = (allAscii ? idnEquivalent : bidiStrippedHost); fixed(char *strippedHostPtr = strippedHost) { int length = strippedHost.Length; int newPos = 0; int curPos = 0; bool foundAce = false; bool checkedAce = false; bool foundDot = false; do { foundAce = false; checkedAce = false; foundDot = false; //find the dot or hit the end newPos = curPos; while (newPos < length) { char c = strippedHostPtr[newPos]; if (!checkedAce) { checkedAce = true; if ((newPos + 3 < length) && IsIdnAce(strippedHostPtr, newPos)) { newPos += 4; foundAce = true; continue; } } if ((c == '.') || (c == '\u3002') || //IDEOGRAPHIC FULL STOP (c == '\uFF0E') || //FULLWIDTH FULL STOP (c == '\uFF61')) //HALFWIDTH IDEOGRAPHIC FULL STOP { foundDot = true; break; } ++newPos; } if (foundAce) { // check ace validity try { IdnMapping map = new IdnMapping(); map.GetUnicode(new string(strippedHostPtr, curPos, newPos - curPos)); atLeastOneValidIdn = true; break; } catch (ArgumentException) { // not valid ace so treat it as a normal ascii label } } curPos = newPos + (foundDot ? 1 : 0); } while (curPos < length); } } else { atLeastOneValidIdn = false; } return(idnEquivalent); }
public void SurrogatePairsConsecutive() { var idn = new IdnMapping(); Assert.Equal("xn--097ccd", idn.GetAscii("\uD800\uDF00\uD800\uDF01\uD800\uDF02")); }
internal static unsafe string UnicodeEquivalent(char *hostname, int start, int end, ref bool allAscii, ref bool atLeastOneValidIdn) { IdnMapping map = new IdnMapping(); // hostname already validated allAscii = true; atLeastOneValidIdn = false; string idn = null; if (end <= start) { return(idn); } string unescapedHostname = UriHelper.StripBidiControlCharacter(hostname, start, (end - start)); string unicodeEqvlHost = null; int curPos = 0; int newPos = 0; int length = unescapedHostname.Length; bool asciiLabel = true; bool foundAce = false; bool checkedAce = false; bool foundDot = false; // We run a loop where for every label // a) if label is ascii and no ace then we lowercase it // b) if label is ascii and ace and not valid idn then just lowercase it // c) if label is ascii and ace and is valid idn then get its unicode eqvl // d) if label is unicode then clean it by running it through idnmapping do { asciiLabel = true; foundAce = false; checkedAce = false; foundDot = false; //find the dot or hit the end newPos = curPos; while (newPos < length) { char c = unescapedHostname[newPos]; if (!checkedAce) { checkedAce = true; if ((newPos + 3 < length) && (c == 'x') && IsIdnAce(unescapedHostname, newPos)) { foundAce = true; } } if (asciiLabel && (c > '\x7F')) { asciiLabel = false; allAscii = false; } if ((c == '.') || (c == '\u3002') || //IDEOGRAPHIC FULL STOP (c == '\uFF0E') || //FULLWIDTH FULL STOP (c == '\uFF61')) //HALFWIDTH IDEOGRAPHIC FULL STOP { foundDot = true; break; } ++newPos; } if (!asciiLabel) { string asciiForm = unescapedHostname.Substring(curPos, newPos - curPos); try { asciiForm = map.GetAscii(asciiForm); } catch (ArgumentException) { throw new UriFormatException(SR.net_uri_BadUnicodeHostForIdn); } unicodeEqvlHost += map.GetUnicode(asciiForm); if (foundDot) { unicodeEqvlHost += "."; } } else { bool aceValid = false; if (foundAce) { // check ace validity try { unicodeEqvlHost += map.GetUnicode(unescapedHostname.Substring(curPos, newPos - curPos)); if (foundDot) { unicodeEqvlHost += "."; } aceValid = true; atLeastOneValidIdn = true; } catch (ArgumentException) { // not valid ace so treat it as a normal ascii label } } if (!aceValid) { // for invalid aces we just lowercase the label unicodeEqvlHost += unescapedHostname.Substring(curPos, newPos - curPos).ToLowerInvariant(); if (foundDot) { unicodeEqvlHost += "."; } } } curPos = newPos + (foundDot ? 1 : 0); } while (curPos < length); return(unicodeEqvlHost); }
/// <summary> /// Request a certificate from lets encrypt using the DNS challenge, placing the challenge record in Azure DNS. /// The certifiacte is not assigned, but just returned. /// </summary> /// <param name="azureDnsEnvironment"></param> /// <param name="acmeConfig"></param> /// <returns></returns> public async Task <CertificateInstallModel> RequestDnsChallengeCertificate(IAcmeDnsRequest acmeConfig) { logger.LogInformation("Starting request DNS Challenge certificate for {AcmeEnvironment} and {Email}", acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.RegistrationEmail); var acmeContext = await GetOrCreateAcmeContext(acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.RegistrationEmail); var idn = new IdnMapping(); var order = await acmeContext.NewOrder(new[] { "*." + idn.GetAscii(acmeConfig.Host.Substring(2)) }); var a = await order.Authorizations(); var authz = a.First(); var challenge = await authz.Dns(); var dnsTxt = acmeContext.AccountKey.DnsTxt(challenge.Token); logger.LogInformation("Got DNS challenge token {Token}", dnsTxt); ///add dns entry await this.dnsProvider.PersistChallenge("_acme-challenge", dnsTxt); if (!(await this.dnsLookupService.Exists(acmeConfig.Host, dnsTxt, this.dnsProvider.MinimumTtl))) { throw new TimeoutException($"Unable to validate that _acme-challenge was stored in txt _acme-challenge record after {this.dnsProvider.MinimumTtl} seconds"); } Challenge chalResp = await challenge.Validate(); while (chalResp.Status == ChallengeStatus.Pending || chalResp.Status == ChallengeStatus.Processing) { logger.LogInformation("Dns challenge response status {ChallengeStatus} more info at {ChallengeStatusUrl} retrying in 5 sec", chalResp.Status, chalResp.Url.ToString()); await Task.Delay(5000); chalResp = await challenge.Resource(); } logger.LogInformation("Finished validating dns challenge token, response was {ChallengeStatus} more info at {ChallengeStatusUrl}", chalResp.Status, chalResp.Url); var privateKey = await GetOrCreateKey(acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.Host); var cert = await order.Generate(new Certes.CsrInfo { CountryName = acmeConfig.CsrInfo?.CountryName, State = acmeConfig.CsrInfo?.State, Locality = acmeConfig.CsrInfo?.Locality, Organization = acmeConfig.CsrInfo?.Organization, OrganizationUnit = acmeConfig.CsrInfo?.OrganizationUnit }, privateKey); var certPem = cert.ToPem(); var pfxBuilder = cert.ToPfx(privateKey); var pfx = pfxBuilder.Build(acmeConfig.Host, acmeConfig.PFXPassword); await this.dnsProvider.Cleanup(dnsTxt); return(new CertificateInstallModel() { CertificateInfo = new CertificateInfo() { Certificate = new X509Certificate2(pfx, acmeConfig.PFXPassword, X509KeyStorageFlags.DefaultKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable), Name = $"{acmeConfig.Host} {DateTime.Now}", Password = acmeConfig.PFXPassword, PfxCertificate = pfx }, Host = acmeConfig.Host }); }
internal static SslPolicyErrors VerifyCertificateProperties( X509Chain chain, X509Certificate2 remoteCertificate, bool checkCertName, bool isServer, string hostName) { SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None; if (!chain.Build(remoteCertificate)) { sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors; } if (checkCertName) { if (string.IsNullOrEmpty(hostName)) { sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNameMismatch; } else { int hostnameMatch; using (SafeX509Handle certHandle = Interop.Crypto.X509Duplicate(remoteCertificate.Handle)) { IPAddress hostnameAsIp; if (IPAddress.TryParse(hostName, out hostnameAsIp)) { byte[] addressBytes = hostnameAsIp.GetAddressBytes(); hostnameMatch = Interop.Crypto.CheckX509IpAddress( certHandle, addressBytes, addressBytes.Length, hostName, hostName.Length); } else { // The IdnMapping converts Unicode input into the IDNA punycode sequence. // It also does host case normalization. The bypass logic would be something // like "all characters being within [a-z0-9.-]+" // // Since it's not documented as being thread safe, create a new one each time. IdnMapping mapping = new IdnMapping(); string matchName = mapping.GetAscii(hostName); hostnameMatch = Interop.Crypto.CheckX509Hostname(certHandle, matchName, matchName.Length); } } if (hostnameMatch != 1) { Debug.Assert(hostnameMatch == 0, "hostnameMatch should be (0,1) was " + hostnameMatch); sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNameMismatch; } } } return(sslPolicyErrors); }
public static bool IsValidEmail(TextBox textBox, bool changeColor = true) { string email = textBox.Text; if (string.IsNullOrWhiteSpace(email)) { return(false); } try { // Normalize the domain email = Regex.Replace(email, @"(@)(.+)$", DomainMapper, RegexOptions.None, TimeSpan.FromMilliseconds(200)); // Examines the domain part of the email and normalizes it. string DomainMapper(Match match) { // Use IdnMapping class to convert Unicode domain names. var idn = new IdnMapping(); // Pull out and process domain name (throws ArgumentException on invalid) var domainName = idn.GetAscii(match.Groups[2].Value); return(match.Groups[1].Value + domainName); } } catch (RegexMatchTimeoutException e) { if (changeColor) { ChangeColor(textBox); } return(false); } catch (ArgumentException e) { if (changeColor) { ChangeColor(textBox); } return(false); } try { bool result = Regex.IsMatch(email, @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" + @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)); if (!result) { if (changeColor) { ChangeColor(textBox); } } return(result); } catch (RegexMatchTimeoutException) { if (changeColor) { ChangeColor(textBox); } return(false); } }
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="binding"></param> /// <returns></returns> public CertificateInfo RequestCertificate(Target binding) { // What are we going to get? var identifiers = binding.GetHosts(false); var friendlyName = FriendlyName(binding); var pfxPassword = Properties.Settings.Default.PFXPassword; var pfxFileInfo = new FileInfo(PfxFilePath(binding)); // Try using cached certificate first to avoid rate limiting during // (initial?) deployment troubleshooting. Real certificate requests // will only be done once per day maximum. if (pfxFileInfo.Exists && pfxFileInfo.LastWriteTime > DateTime.Now.AddDays(-1)) { try { var cached = new CertificateInfo() { Certificate = ReadForUse(pfxFileInfo, pfxPassword), PfxFile = pfxFileInfo }; var idn = new IdnMapping(); if (cached.SubjectName == identifiers.First() && cached.HostNames.Count == identifiers.Count && cached.HostNames.All(h => identifiers.Contains(idn.GetAscii(h)))) { if (_options.ForceRenewal) { _log.Warning("Cached certificate available but not used with --forcerenewal. Use 'Renew specific' or 'Renew all' in the main menu to run unscheduled renewals without hitting rate limits."); } else { _log.Warning("Using cached certificate for {friendlyName}. To force issue of a new certificate within 24 hours, delete the .pfx file from the CertificatePath or run with the --forcerenewal switch. Be ware that you might run into rate limits doing so.", friendlyName); return cached; } } } catch { // File corrupt or invalid password? _log.Warning("Unable to read from certificate cache"); } } using (var cp = CertificateProvider.GetProvider("BouncyCastle")) { // Generate the private key and CSR var rsaPkp = GetRsaKeyParameters(); var rsaKeys = cp.GeneratePrivateKey(rsaPkp); var csr = GetCsr(cp, identifiers, rsaKeys, binding.CommonName); byte[] derRaw; using (var bs = new MemoryStream()) { cp.ExportCsr(csr, EncodingFormat.DER, bs); derRaw = bs.ToArray(); } var derB64U = JwsHelper.Base64UrlEncode(derRaw); // Save request parameters to disk using (var fs = new FileStream(GetPath(binding, "-gen-key.json"), FileMode.Create)) cp.SavePrivateKey(rsaKeys, fs); using (var fs = new FileStream(GetPath(binding, "-key.pem"), FileMode.Create)) cp.ExportPrivateKey(rsaKeys, EncodingFormat.PEM, fs); using (var fs = new FileStream(GetPath(binding, "-gen-csr.json"), FileMode.Create)) cp.SaveCsr(csr, fs); using (var fs = new FileStream(GetPath(binding, "-csr.pem"), FileMode.Create)) cp.ExportCsr(csr, EncodingFormat.PEM, fs); // Request the certificate from the ACME server _log.Information("Requesting certificate {friendlyName}", friendlyName); var certificateRequest = _client.Acme.RequestCertificate(derB64U); if (certificateRequest.StatusCode != HttpStatusCode.Created) { throw new Exception($"Request status {certificateRequest.StatusCode}"); } // Main certicate and issuer certificate Crt certificate; Crt issuerCertificate; // Certificate request was successful, save the certificate itself var crtDerFile = GetPath(binding, $"-crt.der"); _log.Information("Saving certificate to {crtDerFile}", _certificatePath); using (var file = File.Create(crtDerFile)) certificateRequest.SaveCertificate(file); // Save certificate in PEM format too var crtPemFile = GetPath(binding, $"-crt.pem"); using (FileStream source = new FileStream(crtDerFile, FileMode.Open), target = new FileStream(crtPemFile, FileMode.Create)) { certificate = cp.ImportCertificate(EncodingFormat.DER, source); cp.ExportCertificate(certificate, EncodingFormat.PEM, target); } // Get issuer certificate and save in DER and PEM formats issuerCertificate = GetIssuerCertificate(certificateRequest, cp); using (var target = new FileStream(GetPath(binding, "-crt.der", "ca-"), FileMode.Create)) cp.ExportCertificate(issuerCertificate, EncodingFormat.DER, target); var issuerPemFile = GetPath(binding, "-crt.pem", "ca-"); using (var target = new FileStream(issuerPemFile, FileMode.Create)) cp.ExportCertificate(issuerCertificate, EncodingFormat.PEM, target); // Save chain in PEM format using (FileStream intermediate = new FileStream(issuerPemFile, FileMode.Open), certificateStrean = new FileStream(crtPemFile, FileMode.Open), chain = new FileStream(GetPath(binding, "-chain.pem"), FileMode.Create)) { certificateStrean.CopyTo(chain); intermediate.CopyTo(chain); } // All raw data has been saved, now generate the PFX file using (var target = new FileStream(pfxFileInfo.FullName, FileMode.Create)) { try { cp.ExportArchive(rsaKeys, new[] { certificate, issuerCertificate }, ArchiveFormat.PKCS12, target, pfxPassword); } catch (Exception ex) { _log.Error("Error exporting archive {@ex}", ex); } } // Flags used for the internally cached certificate var internalFlags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable; // See http://paulstovell.com/blog/x509certificate2 try { // Convert Private Key to different CryptoProvider _log.Verbose("Converting private key..."); var res = new X509Certificate2(pfxFileInfo.FullName, pfxPassword, internalFlags); var privateKey = (RSACryptoServiceProvider)res.PrivateKey; res.PrivateKey = Convert(privateKey); res.FriendlyName = friendlyName; File.WriteAllBytes(pfxFileInfo.FullName, res.Export(X509ContentType.Pfx, pfxPassword)); pfxFileInfo.Refresh(); } catch (Exception ex) { // If we couldn't convert the private key that // means we're left with a pfx generated with the // 'wrong' Crypto provider therefor delete it to // make sure it's retried on the next run. _log.Warning("Error converting private key to Microsoft RSA SChannel Cryptographic Provider, which means it might not be usable for Exchange."); _log.Verbose("{ex}", ex); } // Recreate X509Certificate2 with correct flags for Store/Install return new CertificateInfo() { Certificate = ReadForUse(pfxFileInfo, pfxPassword), PfxFile = pfxFileInfo }; } }