From c21c9a8da23b15532b290d3f2d7b0e29809e6fa8 Mon Sep 17 00:00:00 2001 From: Dijji <27084934+Dijji@users.noreply.github.com> Date: Sun, 2 Aug 2020 13:01:05 +0100 Subject: [PATCH 1/5] Updated ignore file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 929085f..74265c2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ /.vs/XstReader/v15 /Releases /documents +/.vs/PstFileAttachmentExporter +/PstFileAttachmentExporter/bin/Debug/netcoreapp3.1 +/PstFileAttachmentExporter/obj From d1e096b50b2316cf5d2f7ef4a8d74da0b94fff52 Mon Sep 17 00:00:00 2001 From: just1fi3d <37636572+just1fi3d@users.noreply.github.com> Date: Fri, 31 Jul 2020 15:48:25 -0700 Subject: [PATCH 2/5] Added support for reading encrypted or signed messages --- MailMessageMimeParser.cs | 172 +++++++++++++++++++++++++++++++++++++++ MainWindow.xaml.cs | 43 +++++++++- Message.cs | 10 +++ View.cs | 14 +++- XstFile.cs | 55 ++++++++----- XstReader.csproj | 2 + 6 files changed, 270 insertions(+), 26 deletions(-) create mode 100644 MailMessageMimeParser.cs diff --git a/MailMessageMimeParser.cs b/MailMessageMimeParser.cs new file mode 100644 index 0000000..9fb55d0 --- /dev/null +++ b/MailMessageMimeParser.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Mail; +using System.IO; +using System.Text.RegularExpressions; +using System.Collections; +using System.Collections.Specialized; +using System.Net.Mime; +using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.Pkcs; + +namespace XstReader +{ + static class MailMessageMimeParser + { + + //parse mime message into a given message object adds alll attachments and inserts inline content to message body + public static void parseMessage(Message m, String mimeText) + { + Dictionary headers = getHeaders(new StringReader(mimeText)); + string Boundary = Regex.Match(headers["content-type"], @"boundary=""(.*?)""", RegexOptions.IgnoreCase).Groups[1].Value; + string[] messageParts = getMimeParts(Boundary, mimeText); + + foreach(string part in messageParts) + { + Dictionary partHeaders = getHeaders(new StringReader(part)); + //message body + if (partHeaders.Keys.Contains("content-type") && partHeaders["content-type"].Trim().Contains("text/html;")) + { + m.BodyHtml = DecodeQuotedPrintable(partHeaders["mimeBody"]); + m.NativeBody = BodyType.HTML; + } + //real attachments + else if (partHeaders.Keys.Contains("content-disposition") && partHeaders["content-disposition"].Trim().Contains("attachment;")) + { + string filename = Regex.Match(partHeaders["content-disposition"], @"filename=""(.*?)""", RegexOptions.IgnoreCase).Groups[1].Value; + //add base64 content to an attachement on the message. + Attachment a = new Attachment(); + a.LongFileName = filename; + a.AttachMethod = AttachMethods.afByValue; + a.AttachmentBytes = Convert.FromBase64String(partHeaders["mimeBody"]); + a.Size = a.AttachmentBytes.Length; + m.Attachments.Add(a); + } + //inline images + else if (partHeaders.Keys.Contains("content-id")) + { + Attachment a = new Attachment(); + string contentid = Regex.Match(partHeaders["content-id"], @"<(.*)>", RegexOptions.IgnoreCase).Groups[1].Value; + string name = Regex.Match(partHeaders["content-type"], @".*name=""(.*)""", RegexOptions.IgnoreCase).Groups[1].Value; + a.AttachMethod = AttachMethods.afByValue; + a.ContentId = contentid; + a.LongFileName = name; + a.Flags = AttachFlags.attRenderedInBody; + a.AttachmentBytes = Convert.FromBase64String(partHeaders["mimeBody"]); + a.Size = a.AttachmentBytes.Length; + m.Attachments.Add(a); + } + } + } + + //decrpts mime message bytes with a valid cert in the user cert store + // returns the decrypted message as a string + public static string decryptMessage(byte[] encryptedMessageBytes) + { + //get cert store and collection of valid certs + X509Store store = new X509Store("MY", StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; + X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); + + //decrypt bytes with EnvelopedCms + EnvelopedCms ec = new EnvelopedCms(); + ec.Decode(encryptedMessageBytes); + ec.Decrypt(fcollection); + byte[] decryptedData = ec.ContentInfo.Content; + + return System.Text.Encoding.ASCII.GetString(decryptedData); + } + + //Signed messages are base64 endcoded and broken up with \r\n + //This extracts the base64 content from signed message that has been wrapped in an encrypted message and decodes it + // returns the decoded message string + public static string DecodeSignedMessage(string s) + { + //parse out base64 encoded content in "signed-data" + string base64Message = s.Split(new string[] { "filename=smime.p7m" }, StringSplitOptions.None)[1]; + string data = base64Message.Replace("\r\n", ""); + + // parse out signing data from content + SignedCms sc = new SignedCms(); + sc.Decode(Convert.FromBase64String(data)); + + return System.Text.Encoding.ASCII.GetString(sc.ContentInfo.Content); + } + + //parse out mime headers from a mime section + //returns a dictionary with the header type as the key and its value as the value + private static Dictionary getHeaders(StringReader mimeText) + { + Dictionary Headers = new Dictionary(); + + string line = string.Empty; + string lastHeader = string.Empty; + while ((!string.IsNullOrEmpty(line = mimeText.ReadLine()) && (line.Trim().Length != 0))) + { + + //If the line starts with a whitespace it is a continuation of the previous line + if (Regex.IsMatch(line, @"^\s")) + { + Headers[lastHeader] = Headers[lastHeader] + " " + line.TrimStart('\t', ' '); + } + else + { + string headerkey = line.Substring(0, line.IndexOf(':')).ToLower(); + string value = line.Substring(line.IndexOf(':') + 1).TrimStart(' '); + if (value.Length > 0) + Headers[headerkey] = line.Substring(line.IndexOf(':') + 1).TrimStart(' '); + lastHeader = headerkey; + } + } + + string mimeBody = ""; + while ((line = mimeText.ReadLine()) != null) + { + mimeBody += line +"\r\n"; + } + Headers["mimeBody"] = mimeBody; + return Headers; + } + + // splits a mime message into its individual parts + // returns a string[] with the parts + private static string[] getMimeParts(string initialBoundary, string mimetext) + { + String partRegex = @"\r\n------=_NextPart_.*\r\n"; + string[] test = Regex.Split(mimetext, partRegex); + + return test; + } + + //decodes quoted printable text into UTF-8 + // returns the decoded text + private static string DecodeQuotedPrintable(string input) + { + Regex regex = new Regex(@"(\=[0-9A-F][0-9A-F])+|=\r\n", RegexOptions.IgnoreCase); + string value = regex.Replace(input, new MatchEvaluator(HexDecoderEvaluator)); + return value; + } + + //converts hex endcoded values to UTF-8 + //returns the UTF-8 representation of the hex encoded value + private static string HexDecoderEvaluator(Match m) + { + if (m.Groups[1].Success) + { + byte[] bytes = new byte[m.Value.Length / 3]; + + for (int i = 0; i < bytes.Length; i++) + { + string hex = m.Value.Substring(i * 3 + 1, 2); + int iHex = Convert.ToInt32(hex, 16); + bytes[i] = Convert.ToByte(iHex); + } + return System.Text.Encoding.UTF8.GetString(bytes); + } + return ""; + } + } +} diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 9e65489..05f9155 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -566,6 +565,48 @@ private void ShowMessage(Message m) { if (m != null) { + //email is signed and/or encrypted and no body was included + if (m.Attachments.Count() == 1 && m.Attachments[0].FileName == "smime.p7m" && m.GetBodyAsHtmlString() == null) + { + Attachment a = m.Attachments[0]; + + //get attachment bytes + var ms = new MemoryStream(); + xstFile.SaveAttachment(ms, a); + byte[] attachmentBytes = ms.ToArray(); + string messageFromBytes = System.Text.Encoding.Default.GetString(attachmentBytes); + + // the message is not encrypted just signed + if (messageFromBytes.Contains("application/x-pkcs7-signature")) + { + MailMessageMimeParser.parseMessage(m, messageFromBytes); + } + else + { + var decryptedMessage = MailMessageMimeParser.decryptMessage(attachmentBytes); + string cleartextMessage = ""; + + //Message is only signed + if (decryptedMessage.Contains("filename=smime.p7m")) + { + cleartextMessage = MailMessageMimeParser.DecodeSignedMessage(decryptedMessage); + } + // message is only encrypted not signed + else + { + cleartextMessage = decryptedMessage; + } + MailMessageMimeParser.parseMessage(m, cleartextMessage); + } + + //remove P7M encrypted file from attachments list + m.Attachments.RemoveAt(0); + // if no attachments left unset the has attachments flag + if (m.Attachments.Count == 0) + { + m.Flags ^= MessageFlags.mfHasAttach; + } + } // Can't bind HTML content, so push it into the control, if the message is HTML if (m.ShowHtml) { diff --git a/Message.cs b/Message.cs index bcc6c22..9918f8e 100644 --- a/Message.cs +++ b/Message.cs @@ -137,6 +137,15 @@ public string ExportFileExtension } } + public void ClearContents() + { + // Clear out any previous content + Body = null; + BodyHtml = null; + Html = null; + Attachments.Clear(); + } + public string GetBodyAsHtmlString() { if (BodyHtml != null) @@ -205,6 +214,7 @@ public FlowDocument GetBodyAsFlowDocument() FlowDocument doc = new FlowDocument(); var decomp = new RtfDecompressor(); + using (System.IO.MemoryStream ms = decomp.Decompress(RtfCompressed, true)) { ms.Position = 0; diff --git a/View.cs b/View.cs index 4ccedf3..0d09e52 100644 --- a/View.cs +++ b/View.cs @@ -99,6 +99,8 @@ public void SelectedAttachmentsChanged(IEnumerable selection) public void SetMessage(Message m) { + if (CurrentMessage != null) + CurrentMessage.ClearContents(); stackMessage.Clear(); UpdateCurrentMessage(m); } @@ -299,6 +301,7 @@ class Attachment public int Size { get; set; } public NID Nid { get; set; } public AttachMethods AttachMethod { get; set; } + public byte[] AttachmentBytes { get; set; } public dynamic Content { get; set; } public bool IsFile { get { return AttachMethod == AttachMethods.afByValue; } } public bool IsEmail { get { return /*AttachMethod == AttachMethods.afStorage ||*/ AttachMethod == AttachMethods.afEmbeddedMessage; } } @@ -351,11 +354,14 @@ public List Properties { // We read the full set of attachment property values only on demand if (properties == null) - { + { properties = new List(); - foreach (var p in XstFile.ReadAttachmentProperties(this)) - { - properties.Add(p); + if (AttachmentBytes == null) + { + foreach (var p in XstFile.ReadAttachmentProperties(this)) + { + properties.Add(p); + } } } return properties; diff --git a/XstFile.cs b/XstFile.cs index 568944c..8484d64 100644 --- a/XstFile.cs +++ b/XstFile.cs @@ -5,9 +5,13 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Windows; + + namespace XstReader { // Main handling for xst (.ost and .pst) files @@ -272,32 +276,39 @@ public void SaveAttachment(string fullFileName, DateTime? creationTime, Attachme public void SaveAttachment(Stream s, Attachment a) { - using (FileStream fs = ndb.GetReadStream()) + if (a.AttachmentBytes != null) { - BTree subNodeTreeMessage = a.subNodeTreeProperties; + s.Write(a.AttachmentBytes, 0, a.AttachmentBytes.Length); + } + else + { + using (FileStream fs = ndb.GetReadStream()) + { + BTree subNodeTreeMessage = a.subNodeTreeProperties; - if (subNodeTreeMessage == null) - // No subNodeTree given: assume we can look it up in the main tree - ndb.LookupNodeAndReadItsSubNodeBtree(fs, a.Parent.Nid, out subNodeTreeMessage); + if (subNodeTreeMessage == null) + // No subNodeTree given: assume we can look it up in the main tree + ndb.LookupNodeAndReadItsSubNodeBtree(fs, a.Parent.Nid, out subNodeTreeMessage); - var subNodeTreeAttachment = ltp.ReadProperties(fs, subNodeTreeMessage, a.Nid, pgAttachmentContent, a); + var subNodeTreeAttachment = ltp.ReadProperties(fs, subNodeTreeMessage, a.Nid, pgAttachmentContent, a); - if ((object)a.Content != null) - { - // If the value is inline, we just write it out - if (a.Content.GetType() == typeof(byte[])) - { - s.Write(a.Content, 0, a.Content.Length); - } - // Otherwise we need to dereference the node pointing to the data, - // using the subnode tree belonging to the attachment - else if (a.Content.GetType() == typeof(NID)) + if ((object)a.Content != null) { - var nb = NDB.LookupSubNode(subNodeTreeAttachment, (NID)a.Content); + // If the value is inline, we just write it out + if (a.Content.GetType() == typeof(byte[])) + { + s.Write(a.Content, 0, a.Content.Length); + } + // Otherwise we need to dereference the node pointing to the data, + // using the subnode tree belonging to the attachment + else if (a.Content.GetType() == typeof(NID)) + { + var nb = NDB.LookupSubNode(subNodeTreeAttachment, (NID)a.Content); - // Copy the data to the output file stream without getting it all into memory at once, - // as there can be a lot of data - ndb.CopyDataBlocks(fs, s, nb.DataBid); + // Copy the data to the output file stream without getting it all into memory at once, + // as there can be a lot of data + ndb.CopyDataBlocks(fs, s, nb.DataBid); + } } } } @@ -305,6 +316,7 @@ public void SaveAttachment(Stream s, Attachment a) public Message OpenAttachedMessage(Attachment a) { + using (FileStream fs = ndb.GetReadStream()) { BTree subNodeTreeMessage = a.subNodeTreeProperties; @@ -314,7 +326,6 @@ public Message OpenAttachedMessage(Attachment a) ndb.LookupNodeAndReadItsSubNodeBtree(fs, a.Parent.Nid, out subNodeTreeMessage); var subNodeTreeAttachment = ltp.ReadProperties(fs, subNodeTreeMessage, a.Nid, pgAttachmentContent, a); - if (a.Content.GetType() == typeof(PtypObjectValue)) { Message m = new Message { Nid = new NID(((PtypObjectValue)a.Content).Nid) }; @@ -329,6 +340,7 @@ public Message OpenAttachedMessage(Attachment a) } ReadMessageTables(fs, childSubNodeTree, m, true); + var mine = System.Text.Encoding.Default.GetString(m.RtfCompressed); return m; } @@ -337,6 +349,7 @@ public Message OpenAttachedMessage(Attachment a) } } + private struct LineProp { public int line; diff --git a/XstReader.csproj b/XstReader.csproj index efe4f01..dab6a40 100644 --- a/XstReader.csproj +++ b/XstReader.csproj @@ -75,6 +75,7 @@ + @@ -97,6 +98,7 @@ + From 7d23c9a654f4cefd86fb16503a7689143c49c2d6 Mon Sep 17 00:00:00 2001 From: just1fi3d <37636572+just1fi3d@users.noreply.github.com> Date: Tue, 4 Aug 2020 09:57:12 -0700 Subject: [PATCH 3/5] Moved MailMessageMimeParser functionality to message class --- .vs/XstReader/v16/.suo | Bin 0 -> 44032 bytes MailMessageMimeParser.cs | 172 ----------------------------------- MainWindow.xaml.cs | 35 +------ Message.cs | 191 +++++++++++++++++++++++++++++++++++++++ XstFile.cs | 1 - XstReader.csproj | 1 - 6 files changed, 194 insertions(+), 206 deletions(-) create mode 100644 .vs/XstReader/v16/.suo delete mode 100644 MailMessageMimeParser.cs diff --git a/.vs/XstReader/v16/.suo b/.vs/XstReader/v16/.suo new file mode 100644 index 0000000000000000000000000000000000000000..e28476f9748a456cd1eb4de6bdaae9ab59836123 GIT binary patch literal 44032 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 xa1$}*#{{i`oTy_8e literal 0 HcmV?d00001 diff --git a/MailMessageMimeParser.cs b/MailMessageMimeParser.cs deleted file mode 100644 index 9fb55d0..0000000 --- a/MailMessageMimeParser.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Net.Mail; -using System.IO; -using System.Text.RegularExpressions; -using System.Collections; -using System.Collections.Specialized; -using System.Net.Mime; -using System.Security.Cryptography.X509Certificates; -using System.Security.Cryptography.Pkcs; - -namespace XstReader -{ - static class MailMessageMimeParser - { - - //parse mime message into a given message object adds alll attachments and inserts inline content to message body - public static void parseMessage(Message m, String mimeText) - { - Dictionary headers = getHeaders(new StringReader(mimeText)); - string Boundary = Regex.Match(headers["content-type"], @"boundary=""(.*?)""", RegexOptions.IgnoreCase).Groups[1].Value; - string[] messageParts = getMimeParts(Boundary, mimeText); - - foreach(string part in messageParts) - { - Dictionary partHeaders = getHeaders(new StringReader(part)); - //message body - if (partHeaders.Keys.Contains("content-type") && partHeaders["content-type"].Trim().Contains("text/html;")) - { - m.BodyHtml = DecodeQuotedPrintable(partHeaders["mimeBody"]); - m.NativeBody = BodyType.HTML; - } - //real attachments - else if (partHeaders.Keys.Contains("content-disposition") && partHeaders["content-disposition"].Trim().Contains("attachment;")) - { - string filename = Regex.Match(partHeaders["content-disposition"], @"filename=""(.*?)""", RegexOptions.IgnoreCase).Groups[1].Value; - //add base64 content to an attachement on the message. - Attachment a = new Attachment(); - a.LongFileName = filename; - a.AttachMethod = AttachMethods.afByValue; - a.AttachmentBytes = Convert.FromBase64String(partHeaders["mimeBody"]); - a.Size = a.AttachmentBytes.Length; - m.Attachments.Add(a); - } - //inline images - else if (partHeaders.Keys.Contains("content-id")) - { - Attachment a = new Attachment(); - string contentid = Regex.Match(partHeaders["content-id"], @"<(.*)>", RegexOptions.IgnoreCase).Groups[1].Value; - string name = Regex.Match(partHeaders["content-type"], @".*name=""(.*)""", RegexOptions.IgnoreCase).Groups[1].Value; - a.AttachMethod = AttachMethods.afByValue; - a.ContentId = contentid; - a.LongFileName = name; - a.Flags = AttachFlags.attRenderedInBody; - a.AttachmentBytes = Convert.FromBase64String(partHeaders["mimeBody"]); - a.Size = a.AttachmentBytes.Length; - m.Attachments.Add(a); - } - } - } - - //decrpts mime message bytes with a valid cert in the user cert store - // returns the decrypted message as a string - public static string decryptMessage(byte[] encryptedMessageBytes) - { - //get cert store and collection of valid certs - X509Store store = new X509Store("MY", StoreLocation.CurrentUser); - store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); - X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; - X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); - - //decrypt bytes with EnvelopedCms - EnvelopedCms ec = new EnvelopedCms(); - ec.Decode(encryptedMessageBytes); - ec.Decrypt(fcollection); - byte[] decryptedData = ec.ContentInfo.Content; - - return System.Text.Encoding.ASCII.GetString(decryptedData); - } - - //Signed messages are base64 endcoded and broken up with \r\n - //This extracts the base64 content from signed message that has been wrapped in an encrypted message and decodes it - // returns the decoded message string - public static string DecodeSignedMessage(string s) - { - //parse out base64 encoded content in "signed-data" - string base64Message = s.Split(new string[] { "filename=smime.p7m" }, StringSplitOptions.None)[1]; - string data = base64Message.Replace("\r\n", ""); - - // parse out signing data from content - SignedCms sc = new SignedCms(); - sc.Decode(Convert.FromBase64String(data)); - - return System.Text.Encoding.ASCII.GetString(sc.ContentInfo.Content); - } - - //parse out mime headers from a mime section - //returns a dictionary with the header type as the key and its value as the value - private static Dictionary getHeaders(StringReader mimeText) - { - Dictionary Headers = new Dictionary(); - - string line = string.Empty; - string lastHeader = string.Empty; - while ((!string.IsNullOrEmpty(line = mimeText.ReadLine()) && (line.Trim().Length != 0))) - { - - //If the line starts with a whitespace it is a continuation of the previous line - if (Regex.IsMatch(line, @"^\s")) - { - Headers[lastHeader] = Headers[lastHeader] + " " + line.TrimStart('\t', ' '); - } - else - { - string headerkey = line.Substring(0, line.IndexOf(':')).ToLower(); - string value = line.Substring(line.IndexOf(':') + 1).TrimStart(' '); - if (value.Length > 0) - Headers[headerkey] = line.Substring(line.IndexOf(':') + 1).TrimStart(' '); - lastHeader = headerkey; - } - } - - string mimeBody = ""; - while ((line = mimeText.ReadLine()) != null) - { - mimeBody += line +"\r\n"; - } - Headers["mimeBody"] = mimeBody; - return Headers; - } - - // splits a mime message into its individual parts - // returns a string[] with the parts - private static string[] getMimeParts(string initialBoundary, string mimetext) - { - String partRegex = @"\r\n------=_NextPart_.*\r\n"; - string[] test = Regex.Split(mimetext, partRegex); - - return test; - } - - //decodes quoted printable text into UTF-8 - // returns the decoded text - private static string DecodeQuotedPrintable(string input) - { - Regex regex = new Regex(@"(\=[0-9A-F][0-9A-F])+|=\r\n", RegexOptions.IgnoreCase); - string value = regex.Replace(input, new MatchEvaluator(HexDecoderEvaluator)); - return value; - } - - //converts hex endcoded values to UTF-8 - //returns the UTF-8 representation of the hex encoded value - private static string HexDecoderEvaluator(Match m) - { - if (m.Groups[1].Success) - { - byte[] bytes = new byte[m.Value.Length / 3]; - - for (int i = 0; i < bytes.Length; i++) - { - string hex = m.Value.Substring(i * 3 + 1, 2); - int iHex = Convert.ToInt32(hex, 16); - bytes[i] = Convert.ToByte(iHex); - } - return System.Text.Encoding.UTF8.GetString(bytes); - } - return ""; - } - } -} diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 05f9155..8a9505a 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -566,46 +566,17 @@ private void ShowMessage(Message m) if (m != null) { //email is signed and/or encrypted and no body was included - if (m.Attachments.Count() == 1 && m.Attachments[0].FileName == "smime.p7m" && m.GetBodyAsHtmlString() == null) + if (m.IsEncryptedOrSigned) { + Attachment a = m.Attachments[0]; //get attachment bytes var ms = new MemoryStream(); xstFile.SaveAttachment(ms, a); byte[] attachmentBytes = ms.ToArray(); - string messageFromBytes = System.Text.Encoding.Default.GetString(attachmentBytes); - // the message is not encrypted just signed - if (messageFromBytes.Contains("application/x-pkcs7-signature")) - { - MailMessageMimeParser.parseMessage(m, messageFromBytes); - } - else - { - var decryptedMessage = MailMessageMimeParser.decryptMessage(attachmentBytes); - string cleartextMessage = ""; - - //Message is only signed - if (decryptedMessage.Contains("filename=smime.p7m")) - { - cleartextMessage = MailMessageMimeParser.DecodeSignedMessage(decryptedMessage); - } - // message is only encrypted not signed - else - { - cleartextMessage = decryptedMessage; - } - MailMessageMimeParser.parseMessage(m, cleartextMessage); - } - - //remove P7M encrypted file from attachments list - m.Attachments.RemoveAt(0); - // if no attachments left unset the has attachments flag - if (m.Attachments.Count == 0) - { - m.Flags ^= MessageFlags.mfHasAttach; - } + m.ReadSignedOrEncryptedMessage(attachmentBytes); } // Can't bind HTML content, so push it into the control, if the message is HTML if (m.ShowHtml) diff --git a/Message.cs b/Message.cs index 9918f8e..a839b88 100644 --- a/Message.cs +++ b/Message.cs @@ -5,6 +5,8 @@ using System.ComponentModel; using System.IO; using System.Linq; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; using System.Windows; @@ -43,6 +45,7 @@ class Message : INotifyPropertyChanged public List Recipients { get; private set; } = new List(); public List Properties { get; private set; } = new List(); public bool MayHaveInlineAttachment { get { return (Attachments.FirstOrDefault(a => a.HasContentId) != null); } } + public bool IsEncryptedOrSigned { get { return (GetBodyAsHtmlString() == null && Attachments.Count() == 1 && Attachments[0].FileName == "smime.p7m"); } } // The following properties are used in XAML bindings to control the UI public bool HasAttachment { get { return (Flags & MessageFlags.mfHasAttach) == MessageFlags.mfHasAttach; } } @@ -466,6 +469,194 @@ private void OnPropertyChanged(String info) PropertyChanged(this, new PropertyChangedEventArgs(info)); } } + + // Take encrypted or signed bytes and parse into message object + public void ReadSignedOrEncryptedMessage(byte[] attachmentBytes) + { + string messageFromBytes = System.Text.Encoding.Default.GetString(attachmentBytes); + + // the message is not encrypted just signed + if (messageFromBytes.Contains("application/x-pkcs7-signature")) + { + ParseMessage(messageFromBytes); + } + else + { + var decryptedMessage = DecryptMessage(attachmentBytes); + string cleartextMessage; + + //Message is only signed + if (decryptedMessage.Contains("filename=smime.p7m")) + { + cleartextMessage = DecodeSignedMessage(decryptedMessage); + } + // message is only encrypted not signed + else + { + cleartextMessage = decryptedMessage; + } + ParseMessage(cleartextMessage); + } + + //remove P7M encrypted file from attachments list + Attachments.RemoveAt(0); + // if no attachments left unset the has attachments flag + if (Attachments.Count == 0) + { + Flags ^= MessageFlags.mfHasAttach; + } + } + + //parse mime message into a given message object adds alll attachments and inserts inline content to message body + public void ParseMessage(String mimeText) + { + string[] messageParts = GetMimeParts(mimeText); + + foreach (string part in messageParts) + { + Dictionary partHeaders = GetHeaders(new StringReader(part)); + //message body + if (partHeaders.Keys.Contains("content-type") && partHeaders["content-type"].Trim().Contains("text/html;")) + { + BodyHtml = DecodeQuotedPrintable(partHeaders["mimeBody"]); + NativeBody = BodyType.HTML; + } + //real attachments + else if (partHeaders.Keys.Contains("content-disposition") && partHeaders["content-disposition"].Trim().Contains("attachment;")) + { + string filename = Regex.Match(partHeaders["content-disposition"], @"filename=""(.*?)""", RegexOptions.IgnoreCase).Groups[1].Value; + //add base64 content to an attachement on the message. + Attachment a = new Attachment(); + a.LongFileName = filename; + a.AttachMethod = AttachMethods.afByValue; + a.AttachmentBytes = Convert.FromBase64String(partHeaders["mimeBody"]); + a.Size = a.AttachmentBytes.Length; + Attachments.Add(a); + } + //inline images + else if (partHeaders.Keys.Contains("content-id")) + { + Attachment a = new Attachment(); + string contentid = Regex.Match(partHeaders["content-id"], @"<(.*)>", RegexOptions.IgnoreCase).Groups[1].Value; + string name = Regex.Match(partHeaders["content-type"], @".*name=""(.*)""", RegexOptions.IgnoreCase).Groups[1].Value; + a.AttachMethod = AttachMethods.afByValue; + a.ContentId = contentid; + a.LongFileName = name; + a.Flags = AttachFlags.attRenderedInBody; + a.AttachmentBytes = Convert.FromBase64String(partHeaders["mimeBody"]); + a.Size = a.AttachmentBytes.Length; + Attachments.Add(a); + } + } + } + + //decrpts mime message bytes with a valid cert in the user cert store + // returns the decrypted message as a string + public string DecryptMessage(byte[] encryptedMessageBytes) + { + //get cert store and collection of valid certs + X509Store store = new X509Store("MY", StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; + X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); + + //decrypt bytes with EnvelopedCms + EnvelopedCms ec = new EnvelopedCms(); + ec.Decode(encryptedMessageBytes); + ec.Decrypt(fcollection); + byte[] decryptedData = ec.ContentInfo.Content; + + return System.Text.Encoding.ASCII.GetString(decryptedData); + } + + //Signed messages are base64 endcoded and broken up with \r\n + //This extracts the base64 content from signed message that has been wrapped in an encrypted message and decodes it + // returns the decoded message string + public string DecodeSignedMessage(string s) + { + //parse out base64 encoded content in "signed-data" + string base64Message = s.Split(new string[] { "filename=smime.p7m" }, StringSplitOptions.None)[1]; + string data = base64Message.Replace("\r\n", ""); + + // parse out signing data from content + SignedCms sc = new SignedCms(); + sc.Decode(Convert.FromBase64String(data)); + + return System.Text.Encoding.ASCII.GetString(sc.ContentInfo.Content); + } + + //parse out mime headers from a mime section + //returns a dictionary with the header type as the key and its value as the value + private Dictionary GetHeaders(StringReader mimeText) + { + Dictionary Headers = new Dictionary(); + + string line = string.Empty; + string lastHeader = string.Empty; + while ((!string.IsNullOrEmpty(line = mimeText.ReadLine()) && (line.Trim().Length != 0))) + { + + //If the line starts with a whitespace it is a continuation of the previous line + if (Regex.IsMatch(line, @"^\s")) + { + Headers[lastHeader] = Headers[lastHeader] + " " + line.TrimStart('\t', ' '); + } + else + { + string headerkey = line.Substring(0, line.IndexOf(':')).ToLower(); + string value = line.Substring(line.IndexOf(':') + 1).TrimStart(' '); + if (value.Length > 0) + Headers[headerkey] = line.Substring(line.IndexOf(':') + 1).TrimStart(' '); + lastHeader = headerkey; + } + } + + string mimeBody = ""; + while ((line = mimeText.ReadLine()) != null) + { + mimeBody += line + "\r\n"; + } + Headers["mimeBody"] = mimeBody; + return Headers; + } + + // splits a mime message into its individual parts + // returns a string[] with the parts + private string[] GetMimeParts(string mimetext) + { + String partRegex = @"\r\n------=_NextPart_.*\r\n"; + string[] test = Regex.Split(mimetext, partRegex); + + return test; + } + + //decodes quoted printable text into UTF-8 + // returns the decoded text + private string DecodeQuotedPrintable(string input) + { + Regex regex = new Regex(@"(\=[0-9A-F][0-9A-F])+|=\r\n", RegexOptions.IgnoreCase); + string value = regex.Replace(input, new MatchEvaluator(HexDecoderEvaluator)); + return value; + } + + //converts hex endcoded values to UTF-8 + //returns the UTF-8 representation of the hex encoded value + private string HexDecoderEvaluator(Match m) + { + if (m.Groups[1].Success) + { + byte[] bytes = new byte[m.Value.Length / 3]; + + for (int i = 0; i < bytes.Length; i++) + { + string hex = m.Value.Substring(i * 3 + 1, 2); + int iHex = Convert.ToInt32(hex, 16); + bytes[i] = Convert.ToByte(iHex); + } + return System.Text.Encoding.UTF8.GetString(bytes); + } + return ""; + } } } diff --git a/XstFile.cs b/XstFile.cs index 8484d64..8c50757 100644 --- a/XstFile.cs +++ b/XstFile.cs @@ -340,7 +340,6 @@ public Message OpenAttachedMessage(Attachment a) } ReadMessageTables(fs, childSubNodeTree, m, true); - var mine = System.Text.Encoding.Default.GetString(m.RtfCompressed); return m; } diff --git a/XstReader.csproj b/XstReader.csproj index dab6a40..e5c1ad7 100644 --- a/XstReader.csproj +++ b/XstReader.csproj @@ -98,7 +98,6 @@ - From ac8cbc39823edef5ad3b51623d2c1fd14c61d05c Mon Sep 17 00:00:00 2001 From: just1fi3d <37636572+just1fi3d@users.noreply.github.com> Date: Thu, 6 Aug 2020 14:15:17 -0700 Subject: [PATCH 4/5] Added constructors to Attachment class --- Message.cs | 45 +++++++++++++++++---------------------------- View.cs | 28 +++++++++++++++++++++++++--- XstFile.cs | 4 ++-- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/Message.cs b/Message.cs index a839b88..0200c0d 100644 --- a/Message.cs +++ b/Message.cs @@ -471,18 +471,18 @@ private void OnPropertyChanged(String info) } // Take encrypted or signed bytes and parse into message object - public void ReadSignedOrEncryptedMessage(byte[] attachmentBytes) + public void ReadSignedOrEncryptedMessage(byte[] messageBytes) { - string messageFromBytes = System.Text.Encoding.Default.GetString(attachmentBytes); + string messageFromBytes = System.Text.Encoding.ASCII.GetString(messageBytes); // the message is not encrypted just signed if (messageFromBytes.Contains("application/x-pkcs7-signature")) { - ParseMessage(messageFromBytes); + ParseMimeMessage(messageFromBytes); } else { - var decryptedMessage = DecryptMessage(attachmentBytes); + string decryptedMessage = DecryptMessage(messageBytes); string cleartextMessage; //Message is only signed @@ -495,7 +495,7 @@ public void ReadSignedOrEncryptedMessage(byte[] attachmentBytes) { cleartextMessage = decryptedMessage; } - ParseMessage(cleartextMessage); + ParseMimeMessage(cleartextMessage); } //remove P7M encrypted file from attachments list @@ -508,7 +508,7 @@ public void ReadSignedOrEncryptedMessage(byte[] attachmentBytes) } //parse mime message into a given message object adds alll attachments and inserts inline content to message body - public void ParseMessage(String mimeText) + public void ParseMimeMessage(String mimeText) { string[] messageParts = GetMimeParts(mimeText); @@ -525,34 +525,23 @@ public void ParseMessage(String mimeText) else if (partHeaders.Keys.Contains("content-disposition") && partHeaders["content-disposition"].Trim().Contains("attachment;")) { string filename = Regex.Match(partHeaders["content-disposition"], @"filename=""(.*?)""", RegexOptions.IgnoreCase).Groups[1].Value; - //add base64 content to an attachement on the message. - Attachment a = new Attachment(); - a.LongFileName = filename; - a.AttachMethod = AttachMethods.afByValue; - a.AttachmentBytes = Convert.FromBase64String(partHeaders["mimeBody"]); - a.Size = a.AttachmentBytes.Length; - Attachments.Add(a); + byte[] content = Convert.FromBase64String(partHeaders["mimeBody"]); + Attachments.Add(new Attachment(filename, content)); } //inline images else if (partHeaders.Keys.Contains("content-id")) { - Attachment a = new Attachment(); - string contentid = Regex.Match(partHeaders["content-id"], @"<(.*)>", RegexOptions.IgnoreCase).Groups[1].Value; - string name = Regex.Match(partHeaders["content-type"], @".*name=""(.*)""", RegexOptions.IgnoreCase).Groups[1].Value; - a.AttachMethod = AttachMethods.afByValue; - a.ContentId = contentid; - a.LongFileName = name; - a.Flags = AttachFlags.attRenderedInBody; - a.AttachmentBytes = Convert.FromBase64String(partHeaders["mimeBody"]); - a.Size = a.AttachmentBytes.Length; - Attachments.Add(a); + string fileName = Regex.Match(partHeaders["content-type"], @".*name=""(.*)""", RegexOptions.IgnoreCase).Groups[1].Value; + string contentId = Regex.Match(partHeaders["content-id"], @"<(.*)>", RegexOptions.IgnoreCase).Groups[1].Value; + byte[] content = Convert.FromBase64String(partHeaders["mimeBody"]); + Attachments.Add(new Attachment(fileName, contentId, content)); } } } //decrpts mime message bytes with a valid cert in the user cert store // returns the decrypted message as a string - public string DecryptMessage(byte[] encryptedMessageBytes) + private string DecryptMessage(byte[] encryptedMessageBytes) { //get cert store and collection of valid certs X509Store store = new X509Store("MY", StoreLocation.CurrentUser); @@ -571,8 +560,8 @@ public string DecryptMessage(byte[] encryptedMessageBytes) //Signed messages are base64 endcoded and broken up with \r\n //This extracts the base64 content from signed message that has been wrapped in an encrypted message and decodes it - // returns the decoded message string - public string DecodeSignedMessage(string s) + // returns the decoded message as a string + private string DecodeSignedMessage(string s) { //parse out base64 encoded content in "signed-data" string base64Message = s.Split(new string[] { "filename=smime.p7m" }, StringSplitOptions.None)[1]; @@ -630,7 +619,7 @@ private string[] GetMimeParts(string mimetext) return test; } - //decodes quoted printable text into UTF-8 + // decodes quoted printable text // returns the decoded text private string DecodeQuotedPrintable(string input) { @@ -640,7 +629,7 @@ private string DecodeQuotedPrintable(string input) } //converts hex endcoded values to UTF-8 - //returns the UTF-8 representation of the hex encoded value + //returns the string representation of the hex encoded value private string HexDecoderEvaluator(Match m) { if (m.Groups[1].Success) diff --git a/View.cs b/View.cs index 0d09e52..4b9b574 100644 --- a/View.cs +++ b/View.cs @@ -301,11 +301,11 @@ class Attachment public int Size { get; set; } public NID Nid { get; set; } public AttachMethods AttachMethod { get; set; } - public byte[] AttachmentBytes { get; set; } public dynamic Content { get; set; } public bool IsFile { get { return AttachMethod == AttachMethods.afByValue; } } public bool IsEmail { get { return /*AttachMethod == AttachMethods.afStorage ||*/ AttachMethod == AttachMethods.afEmbeddedMessage; } } public bool WasRenderedInline { get; set; } = false; + public bool WasLoadedFromMime { get; set; } = false; public string Type { @@ -356,8 +356,8 @@ public List Properties if (properties == null) { properties = new List(); - if (AttachmentBytes == null) - { + if (!WasLoadedFromMime) + { foreach (var p in XstFile.ReadAttachmentProperties(this)) { properties.Add(p); @@ -367,5 +367,27 @@ public List Properties return properties; } } + + public Attachment() + { + + } + + public Attachment(string fileName, byte[] content) + { + LongFileName = fileName; + AttachMethod = AttachMethods.afByValue; + Size = content.Length; + this.Content = content; + WasLoadedFromMime = true; + } + + public Attachment(string fileName, string contentId, Byte[] content) + : this(fileName, content) + { + ContentId = contentId; + Flags = AttachFlags.attRenderedInBody; + } + } } diff --git a/XstFile.cs b/XstFile.cs index 8c50757..c994ab2 100644 --- a/XstFile.cs +++ b/XstFile.cs @@ -276,9 +276,9 @@ public void SaveAttachment(string fullFileName, DateTime? creationTime, Attachme public void SaveAttachment(Stream s, Attachment a) { - if (a.AttachmentBytes != null) + if (a.WasLoadedFromMime) { - s.Write(a.AttachmentBytes, 0, a.AttachmentBytes.Length); + s.Write(a.Content, 0, a.Content.Length); } else { From 3af68566c81209b87d4f27ea3c1d1e15db91e33a Mon Sep 17 00:00:00 2001 From: just1fi3d <37636572+just1fi3d@users.noreply.github.com> Date: Tue, 11 Aug 2020 13:59:35 -0700 Subject: [PATCH 5/5] Added Error handling for Emails that cannot be decrypted --- MainWindow.xaml.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 8a9505a..39a6af5 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -563,20 +563,29 @@ private void ShowMessage(Message m) { try { + //clear any existing status + ShowStatus(null); + if (m != null) { //email is signed and/or encrypted and no body was included if (m.IsEncryptedOrSigned) { - - Attachment a = m.Attachments[0]; + try + { + Attachment a = m.Attachments[0]; - //get attachment bytes - var ms = new MemoryStream(); - xstFile.SaveAttachment(ms, a); - byte[] attachmentBytes = ms.ToArray(); + //get attachment bytes + var ms = new MemoryStream(); + xstFile.SaveAttachment(ms, a); + byte[] attachmentBytes = ms.ToArray(); - m.ReadSignedOrEncryptedMessage(attachmentBytes); + m.ReadSignedOrEncryptedMessage(attachmentBytes); + } + catch + { + ShowStatus("Message Failed to Decrypt"); + } } // Can't bind HTML content, so push it into the control, if the message is HTML if (m.ShowHtml)