Skip to content

Commit

Permalink
Merge pull request #28 from just1fi3d/master
Browse files Browse the repository at this point in the history
Encrypted/Signed Email  changes for Issue #15
  • Loading branch information
Dijji authored Aug 2, 2020
2 parents c21c9a8 + db5eb5a commit 09467ff
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 26 deletions.
172 changes: 172 additions & 0 deletions MailMessageMimeParser.cs
Original file line number Diff line number Diff line change
@@ -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<string, string> 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<string, string> 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<string, string> getHeaders(StringReader mimeText)
{
Dictionary<string, string> Headers = new Dictionary<string, string>();

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 "";
}
}
}
43 changes: 42 additions & 1 deletion MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down
10 changes: 10 additions & 0 deletions Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 10 additions & 4 deletions View.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public void SelectedAttachmentsChanged(IEnumerable<Attachment> selection)

public void SetMessage(Message m)
{
if (CurrentMessage != null)
CurrentMessage.ClearContents();
stackMessage.Clear();
UpdateCurrentMessage(m);
}
Expand Down Expand Up @@ -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; } }
Expand Down Expand Up @@ -351,11 +354,14 @@ public List<Property> Properties
{
// We read the full set of attachment property values only on demand
if (properties == null)
{
{
properties = new List<Property>();
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;
Expand Down
55 changes: 34 additions & 21 deletions XstFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -272,39 +276,47 @@ 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<Node> subNodeTreeMessage = a.subNodeTreeProperties;
s.Write(a.AttachmentBytes, 0, a.AttachmentBytes.Length);
}
else
{
using (FileStream fs = ndb.GetReadStream())
{
BTree<Node> 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<Attachment>(fs, subNodeTreeMessage, a.Nid, pgAttachmentContent, a);
var subNodeTreeAttachment = ltp.ReadProperties<Attachment>(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);
}
}
}
}
}

public Message OpenAttachedMessage(Attachment a)
{

using (FileStream fs = ndb.GetReadStream())
{
BTree<Node> subNodeTreeMessage = a.subNodeTreeProperties;
Expand All @@ -314,7 +326,6 @@ public Message OpenAttachedMessage(Attachment a)
ndb.LookupNodeAndReadItsSubNodeBtree(fs, a.Parent.Nid, out subNodeTreeMessage);

var subNodeTreeAttachment = ltp.ReadProperties<Attachment>(fs, subNodeTreeMessage, a.Nid, pgAttachmentContent, a);

if (a.Content.GetType() == typeof(PtypObjectValue))
{
Message m = new Message { Nid = new NID(((PtypObjectValue)a.Content).Nid) };
Expand All @@ -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;
}
Expand All @@ -337,6 +349,7 @@ public Message OpenAttachedMessage(Attachment a)
}
}


private struct LineProp
{
public int line;
Expand Down
Loading

0 comments on commit 09467ff

Please sign in to comment.