/// <summary> /// Finds a certificate in the specified collection. /// </summary> /// <param name="collection">The collection.</param> /// <param name="thumbprint">The thumbprint of the certificate.</param> /// <param name="subjectName">Subject name of the certificate.</param> /// <param name="needPrivateKey">if set to <c>true</c> [need private key].</param> /// <returns></returns> public static X509Certificate2 Find(X509Certificate2Collection collection, string thumbprint, string subjectName, bool needPrivateKey) { // find by thumbprint. if (!String.IsNullOrEmpty(thumbprint)) { collection = collection.Find(X509FindType.FindByThumbprint, thumbprint, false); foreach (X509Certificate2 certificate in collection) { if (!needPrivateKey || certificate.HasPrivateKey) { if (String.IsNullOrEmpty(subjectName)) { return(certificate); } List <string> subjectName2 = X509Utils.ParseDistinguishedName(subjectName); if (X509Utils.CompareDistinguishedName(certificate, subjectName2)) { return(certificate); } } } return(null); } // find by subject name. if (!String.IsNullOrEmpty(subjectName)) { List <string> subjectName2 = X509Utils.ParseDistinguishedName(subjectName); foreach (X509Certificate2 certificate in collection) { if (X509Utils.CompareDistinguishedName(certificate, subjectName2)) { if ((!needPrivateKey || certificate.HasPrivateKey) && X509Utils.GetRSAPublicKeySize(certificate) >= 0) { return(certificate); } } } collection = collection.Find(X509FindType.FindBySubjectName, subjectName, false); foreach (X509Certificate2 certificate in collection) { if ((!needPrivateKey || certificate.HasPrivateKey) && X509Utils.GetRSAPublicKeySize(certificate) >= 0) { return(certificate); } } } // certificate not found. return(null); }
/// <summary> /// Returns the file name to use for the certificate. /// </summary> private string GetFileName(X509Certificate2 certificate) { // build file name. string commonName = certificate.FriendlyName; List <string> names = X509Utils.ParseDistinguishedName(certificate.Subject); for (int ii = 0; ii < names.Count; ii++) { if (names[ii].StartsWith("CN=")) { commonName = names[ii].Substring(3).Trim(); break; } } StringBuilder fileName = new StringBuilder(); // remove any special characters. for (int ii = 0; ii < commonName.Length; ii++) { char ch = commonName[ii]; if ("<>:\"/\\|?*".IndexOf(ch) != -1) { ch = '+'; } fileName.Append(ch); } fileName.Append(" ["); fileName.Append(certificate.Thumbprint); fileName.Append(']'); return(fileName.ToString()); }
/// <summary> /// Sets the parameters to suitable defaults. /// </summary> private static void SetSuitableDefaults( ref string applicationUri, ref string applicationName, ref string subjectName, ref IList <String> domainNames) { // parse the subject name if specified. List <string> subjectNameEntries = null; if (!String.IsNullOrEmpty(subjectName)) { subjectNameEntries = X509Utils.ParseDistinguishedName(subjectName); } // check the application name. if (String.IsNullOrEmpty(applicationName)) { if (subjectNameEntries == null) { throw new ArgumentNullException(nameof(applicationName), "Must specify a applicationName or a subjectName."); } // use the common name as the application name. for (int ii = 0; ii < subjectNameEntries.Count; ii++) { if (subjectNameEntries[ii].StartsWith("CN=", StringComparison.Ordinal)) { applicationName = subjectNameEntries[ii].Substring(3).Trim(); break; } } } if (String.IsNullOrEmpty(applicationName)) { throw new ArgumentNullException(nameof(applicationName), "Must specify a applicationName or a subjectName."); } // remove special characters from name. StringBuilder buffer = new StringBuilder(); for (int ii = 0; ii < applicationName.Length; ii++) { char ch = applicationName[ii]; if (Char.IsControl(ch) || ch == '/' || ch == ',' || ch == ';') { ch = '+'; } buffer.Append(ch); } applicationName = buffer.ToString(); // ensure at least one host name. if (domainNames == null || domainNames.Count == 0) { domainNames = new List <string>(); domainNames.Add(Utils.GetHostName()); } // create the application uri. if (String.IsNullOrEmpty(applicationUri)) { StringBuilder builder = new StringBuilder(); builder.Append("urn:"); builder.Append(domainNames[0]); builder.Append(':'); builder.Append(applicationName); applicationUri = builder.ToString(); } Uri uri = Utils.ParseUri(applicationUri); if (uri == null) { throw new ArgumentNullException(nameof(applicationUri), "Must specify a valid URL."); } // create the subject name, if (String.IsNullOrEmpty(subjectName)) { subjectName = Utils.Format("CN={0}", applicationName); } if (!subjectName.Contains("CN=")) { subjectName = Utils.Format("CN={0}", subjectName); } if (domainNames != null && domainNames.Count > 0) { if (!subjectName.Contains("DC=") && !subjectName.Contains('=')) { subjectName += Utils.Format(", DC={0}", domainNames[0]); } else { subjectName = Utils.ReplaceDCLocalhost(subjectName, domainNames[0]); } } }
/// <summary> /// Extracts the DNS names specified in the certificate. /// </summary> /// <param name="certificate">The certificate.</param> /// <returns>The DNS names.</returns> public static IList <string> GetDomainsFromCertficate(X509Certificate2 certificate) { List <string> dnsNames = new List <string>(); // extracts the domain from the subject name. List <string> fields = X509Utils.ParseDistinguishedName(certificate.Subject); StringBuilder builder = new StringBuilder(); for (int ii = 0; ii < fields.Count; ii++) { if (fields[ii].StartsWith("DC=")) { if (builder.Length > 0) { builder.Append('.'); } builder.Append(fields[ii].Substring(3)); } } if (builder.Length > 0) { dnsNames.Add(builder.ToString().ToUpperInvariant()); } // extract the alternate domains from the subject alternate name extension. X509SubjectAltNameExtension alternateName = X509Extensions.FindExtension <X509SubjectAltNameExtension>(certificate); if (alternateName != null) { for (int ii = 0; ii < alternateName.DomainNames.Count; ii++) { string hostname = alternateName.DomainNames[ii]; // do not add duplicates to the list. bool found = false; for (int jj = 0; jj < dnsNames.Count; jj++) { if (String.Compare(dnsNames[jj], hostname, StringComparison.OrdinalIgnoreCase) == 0) { found = true; break; } } if (!found) { dnsNames.Add(hostname.ToUpperInvariant()); } } for (int ii = 0; ii < alternateName.IPAddresses.Count; ii++) { string ipAddress = alternateName.IPAddresses[ii]; if (!dnsNames.Contains(ipAddress)) { dnsNames.Add(ipAddress); } } } // return the list. return(dnsNames); }
/// <summary> /// Loads the private key from a PFX file in the certificate store. /// </summary> public X509Certificate2 LoadPrivateKey(string thumbprint, string subjectName, string password) { if (m_certificateSubdir == null || !m_certificateSubdir.Exists) { return(null); } if (string.IsNullOrEmpty(thumbprint) && string.IsNullOrEmpty(subjectName)) { return(null); } foreach (FileInfo file in m_certificateSubdir.GetFiles("*.der")) { try { X509Certificate2 certificate = new X509Certificate2(file.FullName); if (!String.IsNullOrEmpty(thumbprint)) { if (!string.Equals(certificate.Thumbprint, thumbprint, StringComparison.CurrentCultureIgnoreCase)) { continue; } } if (!String.IsNullOrEmpty(subjectName)) { if (!X509Utils.CompareDistinguishedName(subjectName, certificate.Subject)) { if (subjectName.Contains('=')) { continue; } if (!X509Utils.ParseDistinguishedName(certificate.Subject).Any(s => s.Equals("CN=" + subjectName, StringComparison.OrdinalIgnoreCase))) { continue; } } } // skip if not RSA certificate if (X509Utils.GetRSAPublicKeySize(certificate) < 0) { continue; } string fileRoot = file.Name.Substring(0, file.Name.Length - file.Extension.Length); StringBuilder filePath = new StringBuilder() .Append(m_privateKeySubdir.FullName) .Append(Path.DirectorySeparatorChar) .Append(fileRoot); X509KeyStorageFlags[] storageFlags = { X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet }; FileInfo privateKeyFile = new FileInfo(filePath.ToString() + ".pfx"); password = password ?? String.Empty; foreach (var flag in storageFlags) { try { certificate = new X509Certificate2( privateKeyFile.FullName, password, flag); if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true)) { return(certificate); } } catch (Exception) { certificate?.Dispose(); certificate = null; } } } catch (Exception e) { Utils.LogError(e, "Could not load private key for certificate " + subjectName); } } return(null); }
/// <summary> /// Loads the private key from a PFX/PEM file in the certificate store. /// </summary> public async Task <X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, string password) { if (NoPrivateKeys || m_certificateSubdir == null || !m_certificateSubdir.Exists) { return(null); } if (string.IsNullOrEmpty(thumbprint) && string.IsNullOrEmpty(subjectName)) { return(null); } // on some platforms, specifically in virtualized environments, // reloading a previously created and saved private key may fail on the first attempt. const int retryDelay = 100; int retryCounter = 3; while (retryCounter-- > 0) { bool certificateFound = false; Exception importException = null; foreach (FileInfo file in m_certificateSubdir.GetFiles("*.der")) { try { X509Certificate2 certificate = new X509Certificate2(file.FullName); if (!String.IsNullOrEmpty(thumbprint)) { if (!string.Equals(certificate.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase)) { continue; } } if (!String.IsNullOrEmpty(subjectName)) { if (!X509Utils.CompareDistinguishedName(subjectName, certificate.Subject)) { if (subjectName.Contains('=')) { continue; } if (!X509Utils.ParseDistinguishedName(certificate.Subject).Any(s => s.Equals("CN=" + subjectName, StringComparison.Ordinal))) { continue; } } } // skip if not RSA certificate if (X509Utils.GetRSAPublicKeySize(certificate) < 0) { continue; } string fileRoot = file.Name.Substring(0, file.Name.Length - file.Extension.Length); StringBuilder filePath = new StringBuilder() .Append(m_privateKeySubdir.FullName) .Append(Path.DirectorySeparatorChar) .Append(fileRoot); X509KeyStorageFlags[] storageFlags = { X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet }; FileInfo privateKeyFilePfx = new FileInfo(filePath + ".pfx"); FileInfo privateKeyFilePem = new FileInfo(filePath + ".pem"); password = password ?? String.Empty; if (privateKeyFilePfx.Exists) { certificateFound = true; foreach (var flag in storageFlags) { try { certificate = new X509Certificate2( privateKeyFilePfx.FullName, password, flag); if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true)) { Utils.LogInfo(Utils.TraceMasks.Security, "Imported the PFX private key for [{0}].", certificate.Thumbprint); return(certificate); } } catch (Exception ex) { importException = ex; certificate?.Dispose(); } } } // if PFX file doesn't exist, check for PEM file. else if (privateKeyFilePem.Exists) { certificateFound = true; try { byte[] pemDataBlob = File.ReadAllBytes(privateKeyFilePem.FullName); certificate = CertificateFactory.CreateCertificateWithPEMPrivateKey(certificate, pemDataBlob, password); if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true)) { Utils.LogInfo(Utils.TraceMasks.Security, "Imported the PEM private key for [{0}].", certificate.Thumbprint); return(certificate); } } catch (Exception exception) { certificate?.Dispose(); importException = exception; } } else { Utils.LogError(Utils.TraceMasks.Security, "A private key for the certificate with thumbprint [{0}] does not exist.", certificate.Thumbprint); continue; } } catch (Exception e) { Utils.LogError(e, "Could not load private key for certificate {0}", subjectName); } } // found a certificate, but some error occurred if (certificateFound) { Utils.LogError(Utils.TraceMasks.Security, "The private key for the certificate with subject {0} failed to import.", subjectName); if (importException != null) { Utils.LogError(importException, "Certificate import failed."); } } else { if (!String.IsNullOrEmpty(thumbprint)) { Utils.LogError(Utils.TraceMasks.Security, "A Private key for the certificate with thumbpint {0} was not found.", thumbprint); } // if no private key was found, no need to retry break; } // retry within a few ms if (retryCounter > 0) { Utils.LogInfo(Utils.TraceMasks.Security, "Retry to import private key after {0} ms.", retryDelay); await Task.Delay(retryDelay).ConfigureAwait(false); } } return(null); }