Skip to content

Commit

Permalink
Merge pull request #31 from Dijji/SecureEmail
Browse files Browse the repository at this point in the history
Secure email
  • Loading branch information
Dijji authored Aug 22, 2020
2 parents 3ecb463 + 81e6333 commit daf7583
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
/.vs/XstReader/v15
/Releases
/documents
/.vs/PstFileAttachmentExporter
/PstFileAttachmentExporter/bin/Debug/netcoreapp3.1
/PstFileAttachmentExporter/obj
Binary file added .vs/XstReader/v16/.suo
Binary file not shown.
23 changes: 22 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 @@ -564,8 +563,30 @@ 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)
{
try
{
Attachment a = m.Attachments[0];

//get attachment bytes
var ms = new MemoryStream();
xstFile.SaveAttachment(ms, a);
byte[] attachmentBytes = ms.ToArray();

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)
{
Expand Down
190 changes: 190 additions & 0 deletions Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -43,6 +45,7 @@ class Message : INotifyPropertyChanged
public List<Recipient> Recipients { get; private set; } = new List<Recipient>();
public List<Property> Properties { get; private set; } = new List<Property>();
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; } }
Expand Down Expand Up @@ -137,6 +140,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 +217,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 Expand Up @@ -456,6 +469,183 @@ private void OnPropertyChanged(String info)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

// Take encrypted or signed bytes and parse into message object
public void ReadSignedOrEncryptedMessage(byte[] messageBytes)
{
string messageFromBytes = System.Text.Encoding.ASCII.GetString(messageBytes);

// the message is not encrypted just signed
if (messageFromBytes.Contains("application/x-pkcs7-signature"))
{
ParseMimeMessage(messageFromBytes);
}
else
{
string decryptedMessage = DecryptMessage(messageBytes);
string cleartextMessage;

//Message is only signed
if (decryptedMessage.Contains("filename=smime.p7m"))
{
cleartextMessage = DecodeSignedMessage(decryptedMessage);
}
// message is only encrypted not signed
else
{
cleartextMessage = decryptedMessage;
}
ParseMimeMessage(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 ParseMimeMessage(String mimeText)
{
string[] messageParts = GetMimeParts(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;"))
{
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;
byte[] content = Convert.FromBase64String(partHeaders["mimeBody"]);
Attachments.Add(new Attachment(filename, content));
}
//inline images
else if (partHeaders.Keys.Contains("content-id"))
{
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
private 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 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];
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<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 string[] GetMimeParts(string mimetext)
{
String partRegex = @"\r\n------=_NextPart_.*\r\n";
string[] test = Regex.Split(mimetext, partRegex);

return test;
}

// decodes quoted printable text
// 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 string 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 "";
}
}

}
34 changes: 31 additions & 3 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 @@ -303,6 +305,7 @@ class Attachment
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
{
Expand Down Expand Up @@ -351,15 +354,40 @@ 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))
if (!WasLoadedFromMime)
{
properties.Add(p);
foreach (var p in XstFile.ReadAttachmentProperties(this))
{
properties.Add(p);
}
}
}
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;
}

}
}
Loading

0 comments on commit daf7583

Please sign in to comment.