OUT-541: Add Reply-To tunneling for Key Import 1.0.204 Patch Release
authorThomas
Mon, 04 Mar 2019 11:56:21 +0100
branch1.0.204 Patch Release
changeset 2589 4196bf6148c2
parent 2428 e7fd0f58bbca
child 2590 6f029d803429
OUT-541: Add Reply-To tunneling for Key Import
PEPMessage.cs
ThisAddIn.cs
UI/KeySyncWizard.xaml.cs
--- a/PEPMessage.cs	Mon Oct 29 10:28:07 2018 +0100
+++ b/PEPMessage.cs	Mon Mar 04 11:56:21 2019 +0100
@@ -46,6 +46,8 @@
         public const string PEP_HIDDEN_SYNC_MESSAGE_BODY                 = "This message is part of p≡p's concept to synchronize. \n\nYou can safely ignore it. It will be deleted automatically.";
         public const string PEP_HIDDEN_SYNC_MESSAGE_SUBJECT              = "p≡p synchronization message - please ignore";
 
+        public const string PEPTUNNEL_MAIL_ADDRESS         = "@peptunnel.com";
+
         private static object                     mutexMirror           = new object();
         private static Dictionary<string, string> mirrorCache           = new Dictionary<string, string>();
 
@@ -73,6 +75,7 @@
         private string              _PEPProtocolVersion;
         private pEpRating           _Rating;
         private DateTime?           _ReceivedOn;
+        private List<PEPIdentity>   _ReplyTo;
         private DateTime?           _SentOn;
         private string              _ShortMsg;
         private List<PEPIdentity>   _To;
@@ -462,67 +465,6 @@
         }
 
         /// <summary>
-        /// Gets the outgoing rating of this message.
-        /// <param name="ignoreOptions">Ignore user settings like ForceProtection or
-        /// ForceUnencrypted and return always calculated engine rating.</param>
-        /// </summary>
-        public pEpRating GetOutgoingRating(bool ignoreOptions = false,
-                                           bool previewOnly = false)
-        {
-            pEpRating rating = pEpRating.pEpRatingUndefined;
-
-#if READER_RELEASE_MODE
-            // If reader mode, always unencrypted
-            rating = pEpRating.pEpRatingUnencrypted;
-#else
-            // If message has BCC recipients, always return unencrypted
-            if (this.Bcc?.Count > 0)
-            {
-                return pEpRating.pEpRatingUnencrypted;
-            }
-
-            if (ignoreOptions == false)
-            {
-                /* If message is forcefully unencrypted, return Unencrypted.
-                 * If message is forcefully encrypted, return Reliable.
-                 */
-                if (this.ForceUnencrypted)
-                {
-                    return pEpRating.pEpRatingUnencrypted;
-                }
-                else if (string.IsNullOrEmpty(this.ForceProtectionId) == false)
-                {
-                    return pEpRating.pEpRatingReliable;
-                }
-            }
-
-            // If we have no rating at this point, calculate it
-            PEPMessage workingMessage = this.Copy(true);
-            workingMessage.FlattenAllRecipientIdentities();
-            workingMessage.Direction = pEpMsgDirection.pEpDirOutgoing;
-
-            try
-            {
-                if (previewOnly)
-                {
-                    rating = ThisAddIn.PEPEngine.OutgoingMessageRatingPreview(workingMessage.ToCOMType());
-                }
-                else
-                {
-                    rating = ThisAddIn.PEPEngine.OutgoingMessageRating(workingMessage.ToCOMType());
-                }
-            }
-            catch (Exception ex)
-            {
-                rating = pEpRating.pEpRatingUndefined;
-                Log.Error("GetOutgoingRating: Error getting outgoing rating from engine. " + ex.ToString());
-            }
-
-#endif
-            return rating;
-        }
-
-        /// <summary>
         /// Gets or sets the pEp protocol (or 'format') version of this message.
         /// This is not to be confused with pEp engine version which can be different.
         /// This is commonly set after decryption.
@@ -578,6 +520,16 @@
         }
 
         /// <summary>
+        /// Gets the list of identities in the Reply-To field.
+        ///          MailItem : Corresponds to property 'ReplyRecipients' 
+        ///       TextMessage : Corresponds to property 'ReplyTo'
+        /// </summary>
+        public List<PEPIdentity> ReplyTo
+        {
+            get { return (this._ReplyTo); }
+        }
+
+        /// <summary>
         /// Gets or sets the date and time when the message was sent.
         ///          MailItem : Corresponds to property 'SentOn'
         /// CryptableMailItem : not supported
@@ -676,7 +628,67 @@
         private void RaisePropertyChangedEvent(string propertyName)
         {
             this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
-            return;
+        }
+
+        /// <summary>
+        /// Gets the outgoing rating of this message.
+        /// <param name="ignoreOptions">Ignore user settings like ForceProtection or
+        /// ForceUnencrypted and return always calculated engine rating.</param>
+        /// </summary>
+        public pEpRating GetOutgoingRating(bool ignoreOptions = false,
+                                           bool previewOnly = false)
+        {
+            pEpRating rating = pEpRating.pEpRatingUndefined;
+
+#if READER_RELEASE_MODE
+            // If reader mode, always unencrypted
+            rating = pEpRating.pEpRatingUnencrypted;
+#else
+            // If message has BCC recipients, always return unencrypted
+            if (this.Bcc?.Count > 0)
+            {
+                return pEpRating.pEpRatingUnencrypted;
+            }
+
+            if (ignoreOptions == false)
+            {
+                /* If message is forcefully unencrypted, return Unencrypted.
+                 * If message is forcefully encrypted, return Reliable.
+                 */
+                if (this.ForceUnencrypted)
+                {
+                    return pEpRating.pEpRatingUnencrypted;
+                }
+                else if (string.IsNullOrEmpty(this.ForceProtectionId) == false)
+                {
+                    return pEpRating.pEpRatingReliable;
+                }
+            }
+
+            // If we have no rating at this point, calculate it
+            PEPMessage workingMessage = this.Copy(true);
+            workingMessage.FlattenAllRecipientIdentities();
+            workingMessage.Direction = pEpMsgDirection.pEpDirOutgoing;
+
+            try
+            {
+                if (previewOnly)
+                {
+                    rating = ThisAddIn.PEPEngine.OutgoingMessageRatingPreview(workingMessage.ToCOMType());
+                }
+                else
+                {
+                    rating = ThisAddIn.PEPEngine.OutgoingMessageRating(workingMessage.ToCOMType());
+                }
+            }
+            catch (Exception ex)
+            {
+                rating = pEpRating.pEpRatingUndefined;
+                Log.Error("GetOutgoingRating: Error getting outgoing rating from engine. " + ex.ToString());
+            }
+
+#endif
+            return rating;
         }
 
         /// <summary>
@@ -697,6 +709,7 @@
             List<pEpIdentity> bcc = new List<pEpIdentity>();
             List<pEpIdentity> cc = new List<pEpIdentity>();
             List<pEpIdentity> to = new List<pEpIdentity>();
+            List<pEpIdentity> replyTo = new List<pEpIdentity>();
             List<StringPair> optionalFields = new List<StringPair>();
             StringPair field;
             TextMessage result = new TextMessage();
@@ -813,6 +826,12 @@
                 recvSec = (long)span.TotalSeconds;
             }
 
+            // Convert ReplyTo
+            for (int i = 0; i < this._ReplyTo.Count; i++)
+            {
+                replyTo.Add(this.ReplyTo[i].ToCOMType());
+            }
+
             // SentOn
             sentSec = -1;
             if (this._SentOn != null)
@@ -860,6 +879,7 @@
             result.LongMsgFormatted = this._LongMsgFormattedHtml;
             result.OptFields = optionalFields.ToArray();
             result.Recv = ((recvSec >= 0) ? recvSec : result.Recv);
+            result.ReplyTo = replyTo.ToArray();
             result.Sent = ((sentSec >= 0) ? sentSec : result.Sent);
             result.ShortMsg = this._ShortMsg;
             result.To = to.ToArray();
@@ -989,6 +1009,24 @@
                 message.MessageId = pEpMessage.Id;
             }
 
+            // ReplyTo
+            for (int i = 0; i < pEpMessage.ReplyTo?.Count; i++)
+            {
+                try
+                {
+                    if (InternetAddress.TryParse(pEpMessage.ReplyTo[i].Address, out InternetAddress address))
+                    {
+                        address.Name = pEpMessage.ReplyTo[i].UserName;
+                        message.ReplyTo.Add(address);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    status = Globals.ReturnStatus.Failure;
+                    Log.Error("ToMIMEMessage: Error parsing ReplyTo identity. " + ex.ToString());
+                }
+            }
+
             // Subject
             if (string.IsNullOrEmpty(pEpMessage.ShortMsg) == false)
             {
@@ -1184,6 +1222,7 @@
                 Comparisons.Equals(this.PEPProtocolVersion, obj.PEPProtocolVersion) &&
                 Comparisons.Equals(this.Rating, obj.Rating) &&
                 Comparisons.Equals(this.ReceivedOn, obj.ReceivedOn) &&
+                Comparisons.Equals(this.ReplyTo, obj.ReplyTo) &&
                 Comparisons.Equals(this.SentOn, obj.SentOn) &&
                 Comparisons.Equals(this.ShortMsg, obj.ShortMsg) &&
                 Comparisons.Equals(this.To, obj.To))
@@ -1320,6 +1359,13 @@
                 copy.ReceivedOn = null;
             }
 
+            // ReplyTo
+            copy.ReplyTo.Clear();
+            for (int i = 0; i < this._ReplyTo.Count; i++)
+            {
+                copy.ReplyTo.Add(this._ReplyTo[i].Copy());
+            }
+
             // SentOn
             if (this._SentOn != null)
             {
@@ -1371,6 +1417,7 @@
             this._PEPProtocolVersion = null;
             this._Rating = pEpRating.pEpRatingUndefined;
             this._ReceivedOn = null;
+            this._ReplyTo = new List<PEPIdentity>();
             this._SentOn = null;
             this._ShortMsg = null;
             this._To = new List<PEPIdentity>();
@@ -1398,6 +1445,7 @@
             this.RaisePropertyChangedEvent(nameof(this.PEPProtocolVersion));
             this.RaisePropertyChangedEvent(nameof(this.Rating));
             this.RaisePropertyChangedEvent(nameof(this.ReceivedOn));
+            this.RaisePropertyChangedEvent(nameof(this.ReplyTo));
             this.RaisePropertyChangedEvent(nameof(this.SentOn));
             this.RaisePropertyChangedEvent(nameof(this.ShortMsg));
             this.RaisePropertyChangedEvent(nameof(this.To));
@@ -1844,6 +1892,29 @@
                     catch { }
                 }
 
+                // ReplyTo recipients
+                recipients = omi.ReplyRecipients;
+                while (recipients?.Count > 0)
+                {
+                    recipients.Remove(1);
+                }
+
+                for (int i = 0; i < this._ReplyTo.Count; i++)
+                {
+                    if (string.IsNullOrWhiteSpace(this._ReplyTo[i].Address) == false)
+                    {
+                        // Add by address
+                        newRecipient = recipients.Add(this._ReplyTo[i].Address);
+                        newRecipient = null;
+                    }
+                    else if (string.IsNullOrWhiteSpace(this._ReplyTo[i].UserName) == false)
+                    {
+                        // Add by user name (required for distribution lists)
+                        newRecipient = recipients.Add(this._ReplyTo[i].UserName);
+                        newRecipient = null;
+                    }
+                }
+
                 /* Set the encoding to UTF-8
                  * See: https://msdn.microsoft.com/en-us/library/office/ff860730.aspx
                  * All PEPMessages should be UTF especially those coming from the engine.
@@ -2023,7 +2094,6 @@
                 {
                     // Note: ignore return status
                     omi.SetPEPProperty(MailItemExtensions.PEPProperty.KeyList, this._KeyList);
-                    omi.SetPEPProperty(MailItemExtensions.PEPProperty.KeyImport, this._KeyImport);
 
                     // Only store rating once and never change it
                     object storedRating = null;
@@ -2037,6 +2107,7 @@
                 // Note: ignore return status
                 omi.SetPEPProperty(MailItemExtensions.PEPProperty.AutoConsume, this._AutoConsume);
                 omi.SetPEPProperty(MailItemExtensions.PEPProperty.ForceProtection, this._ForceProtectionId);
+                omi.SetPEPProperty(MailItemExtensions.PEPProperty.KeyImport, this._KeyImport);
                 omi.SetPEPProperty(MailItemExtensions.PEPProperty.NeverUnsecure, this._NeverUnsecure);
                 omi.SetPEPProperty(MailItemExtensions.PEPProperty.PEPProtocolVersion, this._PEPProtocolVersion);
 
@@ -2471,6 +2542,16 @@
                     newMessage.ReceivedOn = null;
                 }
 
+                // ReplyTo
+                newMessage.ReplyTo.Clear();
+                if (msg.ReplyTo != null)
+                {
+                    for (int i = 0; i < msg.ReplyTo.Length; i++)
+                    {
+                        newMessage.ReplyTo.Add(new PEPIdentity((pEpIdentity)msg.ReplyTo.GetValue(i)));
+                    }
+                }
+
                 // SentOn
                 if (msg.Sent > 0)
                 {
@@ -2632,6 +2713,27 @@
                     currRecipient = null;
                 }
 
+                recipients = omi.ReplyRecipients;
+                for (int i = 1; i <= recipients.Count; i++)
+                {
+                    currRecipient = recipients[i];
+                    sts = PEPIdentity.Create(currRecipient, out ident);
+                    if (sts == Globals.ReturnStatus.Success)
+                    {
+                        newMessage.ReplyTo.Add(ident);
+                    }
+                    else
+                    {
+                        // Update the status, only the first failure type is recorded
+                        if (status == Globals.ReturnStatus.Success)
+                        {
+                            status = sts;
+                        }
+
+                        Log.Error("PEPMessage.Create: Failure creating 'ReplyTo' identity.");
+                    }
+                }
+
                 Log.Verbose("PEPMessage.Create: Recipients calculated, calculating main properties.");
 
                 if (onlyRecipientsAndDirection == false)
@@ -2782,6 +2884,22 @@
                     Log.Verbose("PEPMessage.Create: Transport headers retrieved.");
                     newMessage.SetPEPProperties(omi, headers);
 
+                    // Check backup solution with ReplyTo tunnel if needed
+                    if (string.IsNullOrEmpty(newMessage.KeyImport) &&
+                        (newMessage.ReplyTo?.Count > 0))
+                    {
+                        for (int i = 0; i < newMessage.ReplyTo.Count; i++)
+                        {
+                            string address = newMessage.ReplyTo[i].Address?.Trim();
+                            if (address?.EndsWith(PEPMessage.PEPTUNNEL_MAIL_ADDRESS) == true)
+                            {
+                                string keyImportFpr = address.Split('@')?.GetValue(0) as string;
+                                newMessage.KeyImport = keyImportFpr.ToUpperInvariant();
+                                break;
+                            }
+                        }
+                    }
+
                     // Add additional headers
                     string autocryptHeaderKey = "Autocrypt";
                     try
--- a/ThisAddIn.cs	Mon Oct 29 10:28:07 2018 +0100
+++ b/ThisAddIn.cs	Mon Mar 04 11:56:21 2019 +0100
@@ -595,12 +595,10 @@
         /// <param name="validateSendingAccount">Validates that the SendingAccount matches the From identity of the given message.
         /// This can catch situations (and throw exceptions) where the default account would be used instead.</param>
         /// <param name="processMessage">Whether or not to process this message through the pEp engine.</param>
-        /// <param name="setKeyImportHeader">Whether or not to process this message through the pEp engine.</param>
         internal void CreateAndSendMessage(PEPMessage message,
                                            bool deleteAfterSend,
                                            bool validateSendingAccount,
-                                           bool processMessage = false,
-                                           bool setKeyImportHeader = false)
+                                           bool processMessage = false)
         {
             Outlook.MailItem newItem;
             Globals.ReturnStatus sts;
@@ -632,32 +630,11 @@
                     // Do not allow TNEF/RTF format with 'winmail.dat' attachment
                     MapiHelper.SetProperty(newItem, MapiProperty.PidLidUseTnef, false);
 
-                    // If ForceUnencrypted property is set, add it to mail item
-                    bool save = false;
-                    if (message.ForceUnencrypted)
+                    // If ForceUnencrypted property is set, add it to mail item (only if message is to be processed)
+                    if (processMessage && 
+                        message.ForceUnencrypted)
                     {
                         newItem.SetPEPProperty(MailItemExtensions.PEPProperty.ForceUnencrypted, true);
-                        save = true;
-                    }
-
-                    // Set KeyImport header if necessary
-                    if (setKeyImportHeader)
-                    {
-                        newItem.SetPEPProperty(MailItemExtensions.PEPProperty.KeyImport, message.KeyImport);
-                        save = true;
-                    }
-
-                    // Save if necessary
-                    if (save)
-                    {
-                        try
-                        {
-                            newItem.Save();
-                        }
-                        catch (Exception ex)
-                        {
-                            Log.Error("CreateAndSendMessage: Error saving new item. " + ex.ToString());
-                        }
                     }
 
                     /* Send
--- a/UI/KeySyncWizard.xaml.cs	Mon Oct 29 10:28:07 2018 +0100
+++ b/UI/KeySyncWizard.xaml.cs	Mon Mar 04 11:56:21 2019 +0100
@@ -1558,7 +1558,6 @@
                         }
 
                         // Create basic message
-                        bool processMessage = true;
                         PEPMessage message = new PEPMessage
                         {
                             From = this.Myself,
@@ -1571,6 +1570,10 @@
                         };
                         message.To.Add(this.Partner);
 
+                        // Tunnel the fingerprint also through the Reply-To field in the following format: <fingerprint>@peptunnel.com
+                        string keyImportAddress = string.Concat(message.KeyImport, PEPMessage.PEPTUNNEL_MAIL_ADDRESS);
+                        message.ReplyTo.Add(new PEPIdentity(keyImportAddress));
+
                         Log.Verbose("SendSyncMessage: Basic message successfully created.");
 
                         // If message type isn't InitialMessage, encrypt it accordingly
@@ -1580,7 +1583,16 @@
                         {
                             case MessageTypes.InitialMessage:
                                 {
-                                    Log.Verbose("SendSyncMessage: Initial message. Won't encrypt.");
+                                    if (msgProcessor.Encrypt(message, out msg, null, pEpEncryptFlags.pEpEncryptFlagDefault, pEpEncFormat.pEpEncNone))
+                                    {
+                                        Log.Verbose("SendSyncMessage: Initial message successfully created.");
+                                        message = msg;
+                                    }
+                                    else
+                                    {
+                                        Log.Error("SendSyncMessage: Error creating initial message.");
+                                        message = null;
+                                    }
                                 }
                                 break;
                             case MessageTypes.PublicKeyMessage:
@@ -1590,7 +1602,6 @@
                                     {
                                         Log.Verbose("SendSyncMessage: Public key message successfully created.");
                                         message = msg;
-                                        processMessage = false;
                                     }
                                     else
                                     {
@@ -1606,7 +1617,6 @@
                                     {
                                         Log.Verbose("SendSyncMessage: Private key message successfully created.");
                                         message = msg;
-                                        processMessage = false;
                                     }
                                     else
                                     {
@@ -1645,8 +1655,8 @@
                                 }
                             }
 
-                            // Send message
-                            Globals.ThisAddIn.CreateAndSendMessage(message, true, true, processMessage, true);
+                            // Send message (do not process as it's already processed at this point)
+                            Globals.ThisAddIn.CreateAndSendMessage(message, true, true, false);
 
                             // Log that message has been sent
                             this.sentMessages?.Add(messageType);