public static MailAddressCollection Parse(string addresses) { // Escape embedded encoding. addresses = Functions.DecodeMailHeader(addresses); // Create a new collection of MailAddresses to be returned. MailAddressCollection addressCollection = new MailAddressCollection(); int cursor = 0, lastCursor = 0; string displayName = ""; while (cursor < addresses.Length) { int quoteCursor = addresses.IndexOf("\"", cursor, StringComparison.Ordinal); if (quoteCursor == -1) { quoteCursor = addresses.Length + 1; } int aposCursor = addresses.IndexOf("'", cursor, StringComparison.Ordinal); if (aposCursor == -1) { aposCursor = addresses.Length + 1; } int angleCursor = addresses.IndexOf("<", cursor, StringComparison.Ordinal); if (angleCursor == -1) { angleCursor = addresses.Length + 1; } int bracketCursor = addresses.IndexOf("[", cursor, StringComparison.Ordinal); if (bracketCursor == -1) { bracketCursor = addresses.Length + 1; } int parenthesisCursor = addresses.IndexOf("(", cursor, StringComparison.Ordinal); if (parenthesisCursor == -1) { parenthesisCursor = addresses.Length + 1; } int commaCursor = addresses.IndexOf(",", cursor, StringComparison.Ordinal); if (commaCursor == -1) { commaCursor = addresses.Length + 1; } int semicolonCursor = addresses.IndexOf(";", cursor, StringComparison.Ordinal); if (semicolonCursor == -1) { semicolonCursor = addresses.Length + 1; } bool processed = false; if (quoteCursor < aposCursor && quoteCursor < angleCursor && quoteCursor < bracketCursor && quoteCursor < parenthesisCursor && quoteCursor < commaCursor && quoteCursor < semicolonCursor) { // The address display name is enclosed in quotes. int endQuoteCursor = addresses.IndexOf("\"", quoteCursor + 1, StringComparison.Ordinal); if (endQuoteCursor > -1) { displayName = addresses.Substring(quoteCursor + 1, endQuoteCursor - quoteCursor - 1).Replace("\\(", "(").Replace("\\)", ")"); cursor = endQuoteCursor + 1; } else { cursor = addresses.Length; } processed = true; } else if (aposCursor < angleCursor && aposCursor < bracketCursor && aposCursor < parenthesisCursor && aposCursor < commaCursor && aposCursor < semicolonCursor) { // The address display name may be enclosed in apostrophes. int endAposCursor = addresses.IndexOf("'", aposCursor + 1, StringComparison.Ordinal); if (endAposCursor > -1) { displayName = addresses.Substring(aposCursor + 1, endAposCursor - aposCursor - 1).Replace("\\(", "(").Replace("\\)", ")"); cursor = endAposCursor + 1; } else { // The address contains an apostophe, but it's not enclosed in apostrophes. angleCursor = addresses.IndexOf("<", cursor, StringComparison.Ordinal); if (angleCursor == -1) { angleCursor = addresses.Length + 1; } bracketCursor = addresses.IndexOf("[", cursor, StringComparison.Ordinal); if (bracketCursor == -1) { bracketCursor = addresses.Length + 1; } if (angleCursor < bracketCursor) { displayName = addresses.Substring(lastCursor, angleCursor - lastCursor).Trim().Replace("\\(", "(").Replace("\\)", ")"); cursor = angleCursor; } else if (bracketCursor > -1) { displayName = addresses.Substring(lastCursor, bracketCursor - lastCursor).Trim().Replace("\\(", "(").Replace("\\)", ")"); cursor = angleCursor; } else { cursor = addresses.Length; } } processed = true; } else if (angleCursor < bracketCursor && angleCursor < parenthesisCursor && angleCursor < commaCursor && angleCursor < semicolonCursor) { // The address is enclosed in angle brackets. int endAngleCursor = addresses.IndexOf(">", angleCursor + 1, StringComparison.Ordinal); if (endAngleCursor > -1) { // If we didn't find a display name between quotes or apostrophes, look at all characters prior to the angle bracket. if (displayName.Length < 1) { displayName = addresses.Substring(lastCursor, angleCursor - lastCursor).Trim().Replace("\\(", "(").Replace("\\)", ")"); } string address = addresses.Substring(angleCursor + 1, endAngleCursor - angleCursor - 1); addressCollection.Add(new MailAddress(address, displayName)); displayName = ""; cursor = endAngleCursor + 1; } else { cursor = addresses.Length; } processed = true; } else if (bracketCursor < parenthesisCursor && bracketCursor < commaCursor && bracketCursor < semicolonCursor) { // The address is enclosed in brackets. int endBracketCursor = addresses.IndexOf("]", bracketCursor + 1, StringComparison.Ordinal); if (endBracketCursor > -1) { // If we didn't find a display name between quotes or apostrophes, look at all characters prior to the bracket. if (displayName.Length < 1) { displayName = addresses.Substring(lastCursor, bracketCursor - lastCursor).Trim().Replace("\\(", "(").Replace("\\)", ")"); } string address = addresses.Substring(bracketCursor + 1, endBracketCursor - bracketCursor - 1); if (displayName.Length > 0) { addressCollection.Add(new MailAddress(address, displayName)); } else { addressCollection.Add(new MailAddress(address)); } displayName = ""; cursor = endBracketCursor + 1; processed = true; } else { cursor = addresses.Length; } } else if (parenthesisCursor < commaCursor && parenthesisCursor < semicolonCursor) { if ((parenthesisCursor == 0) || (addresses[parenthesisCursor - 1] != '\\')) { // The display name is enclosed in parentheses. int endParenthesisCursor = 0; while (endParenthesisCursor > -1) { endParenthesisCursor = addresses.IndexOf(")", parenthesisCursor + 1, StringComparison.Ordinal); if (endParenthesisCursor > 0) { if (addresses[endParenthesisCursor - 1] != '\\') { break; } } else { break; } } string address = addresses.Substring(lastCursor, parenthesisCursor - lastCursor).Trim(); displayName = addresses.Substring(parenthesisCursor + 1, endParenthesisCursor - parenthesisCursor - 1).Replace("\\(", "(").Replace("\\)", ")"); addressCollection.Add(new MailAddress(address, displayName)); cursor = parenthesisCursor + 1; processed = true; } } if (!processed) { if (commaCursor < semicolonCursor) { if (commaCursor > lastCursor) { // We've found the next address, delimited by a comma. string address = addresses.Substring(cursor, commaCursor - cursor).Trim(); addressCollection.Add(new MailAddress(address)); } cursor = commaCursor + 1; } else if (semicolonCursor < addresses.Length) { if (semicolonCursor > lastCursor) { // We've found the next address, delimited by a semicolon. string address = addresses.Substring(cursor, semicolonCursor - cursor).Trim(); addressCollection.Add(new MailAddress(address)); } cursor = semicolonCursor + 1; } else { // Process any remaining address. string address = addresses.Substring(cursor).Trim(); addressCollection.Add(new MailAddress(address)); cursor = addresses.Length; } } lastCursor = cursor; } // If no encoded email address was parsed, try adding the entire string. if (addressCollection.Count < 1) { addressCollection.Add(addresses); } return(addressCollection); }
/// <summary> /// Helper function to look up and validate public keys for each recipient. /// </summary> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> /// <param name="addressesWithPublicKeys">Collection containing recipients with valid public keys.</param> /// <param name="addressesNeedingPublicKeys">Collection containing recipients without valid public keys.</param> private void SmimeResolvePublicKeys(MailMessage message, out HashSet <string> addressesWithPublicKeys, out Dictionary <string, MailAddress> addressesNeedingPublicKeys) { // Initialize collections for all recipients. addressesWithPublicKeys = new HashSet <string>(); addressesNeedingPublicKeys = new Dictionary <string, MailAddress>(); MailAddressCollection[] addressRanges = new MailAddressCollection[] { message.To, message.CC, message.Bcc }; foreach (MailAddressCollection addressRange in addressRanges) { foreach (MailAddress toAddress in addressRange) { string canonicalToAddress = toAddress.Address.ToUpper(); if (SmimeCertificateCache.ContainsKey(canonicalToAddress)) { if (!addressesWithPublicKeys.Contains(canonicalToAddress)) { addressesWithPublicKeys.Add(canonicalToAddress); } } else { if (!addressesNeedingPublicKeys.ContainsKey(canonicalToAddress)) { addressesNeedingPublicKeys.Add(canonicalToAddress, toAddress); } } } } // If any addresses haven't been mapped to public keys, map them. if (addressesNeedingPublicKeys.Count > 0) { // Read from the Windows certificate store if valid certificates aren't specified. if (SmimeValidCertificates == null || SmimeValidCertificates.Count < 1) { // Load from the current user. X509Store store = new X509Store(StoreLocation.CurrentUser); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); SmimeValidCertificates = store.Certificates; store.Close(); // Add any tied to the local machine. store = new X509Store(StoreLocation.LocalMachine); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); SmimeValidCertificates.AddRange(store.Certificates); store.Close(); } // Loop through certificates and check for matching recipients. foreach (X509Certificate2 cert in SmimeValidCertificates) { // Look at certificates with email subject names. string canonicalCertSubject = ""; if (cert.Subject.StartsWith("E=")) { canonicalCertSubject = cert.Subject.Substring(2).ToUpper(); } else if (cert.Subject.StartsWith("CN=")) { canonicalCertSubject = cert.Subject.Substring(3).ToUpper(); } else { canonicalCertSubject = cert.Subject.ToUpper(); } int certSubjectComma = canonicalCertSubject.IndexOf(","); if (certSubjectComma > -1) { canonicalCertSubject = canonicalCertSubject.Substring(0, certSubjectComma); } // Only proceed if the key is for a recipient of this email. if (!addressesNeedingPublicKeys.ContainsKey(canonicalCertSubject)) { continue; } // Verify the certificate chain. if ((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireCertificateVerification) > 0) { if (!cert.Verify()) { continue; } } // Ensure valid key usage scenarios. if ((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireKeyUsageOfDataEncipherment) > 0 || (message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireEnhancedKeyUsageofSecureEmail) > 0) { bool keyDataEncipherment = false, enhancedKeySecureEmail = false; foreach (X509Extension extension in cert.Extensions) { if (!keyDataEncipherment && extension.Oid.FriendlyName == "Key Usage") { X509KeyUsageExtension ext = (X509KeyUsageExtension)extension; if ((ext.KeyUsages & X509KeyUsageFlags.DataEncipherment) != X509KeyUsageFlags.None) { keyDataEncipherment = true; if (!((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireEnhancedKeyUsageofSecureEmail) > 0)) { break; } } } if (!enhancedKeySecureEmail && extension.Oid.FriendlyName == "Enhanced Key Usage") { X509EnhancedKeyUsageExtension ext = (X509EnhancedKeyUsageExtension)extension; OidCollection oids = ext.EnhancedKeyUsages; foreach (Oid oid in oids) { if (oid.FriendlyName == "Secure Email") { enhancedKeySecureEmail = true; break; } } } } if ((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireKeyUsageOfDataEncipherment) > 0 && !keyDataEncipherment) { continue; } if ((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireEnhancedKeyUsageofSecureEmail) > 0 && !enhancedKeySecureEmail) { continue; } } // If we've made it this far, we can use the certificate for a recipient. MailAddress originalAddress = addressesNeedingPublicKeys[canonicalCertSubject]; SmimeCertificateCache.Add(canonicalCertSubject, cert); addressesWithPublicKeys.Add(canonicalCertSubject); addressesNeedingPublicKeys.Remove(canonicalCertSubject); // Shortcut to abort processing of additional certificates if all recipients are accounted for. if (addressesNeedingPublicKeys.Count < 1) { break; } } } }