GpgOL: Unable to save an encrypted message over the microsoft outlook interface
Testing, NormalPublic

Description

When an add-in wants to save an e-mail by calling the Microsoft.Office.Interop.Outlook.MailItem.SaveAs Method on an e-mail that is encrypted by using GpgOL an exception is thrown.

The SaveAs method works fine for normal and encrypted (S-MIME direcly by Outlook- not by the GpgOL) e-mails.

In my test add-in I simply handled the selection changed event for saving the currently selected e-mails to disk (temporarily). The issue occured on Windows10 x64 and Office 2013, 2016 and 2019. Everything with latest updates.

Here is the exception:

System.Runtime.InteropServices.COMException (0x80004004): Vorgang abgebrochen (Ausnahme von HRESULT: 0x80004004 (E_ABORT))
   bei Microsoft.Office.Interop.Outlook._MailItem.SaveAs(String Path, Object Type)
   bei OutlookAddIn1.ThisAddIn.SelectionChanged() in C:\Users\msc\Documents\Visual Studio\Projects\OutlookAddIn1\OutlookAddIn1\ThisAddIn.cs:Zeile 32.

You can find the simple example Outlook Add-In attached.

Can you confirm that?

msc created this task.Nov 5 2018, 10:53 AM
aheinecke triaged this task as Normal priority.Nov 5 2018, 3:46 PM
aheinecke claimed this task.
aheinecke added a subscriber: aheinecke.

Yes that is expected, we block the save event as long as we have replaced the Body / Attachments of the mail with decrypted contents are shown. In our debug log you would see the message "Canceling write event".

I wonder if it would be possible for you to close the mail / inspector of the mail with DiscardChanges before doing a save as? Or do you want to save the mail in "decrypted" state?

Alternatively I could offer some interprocess communication e.g. that you could send us a window message with a unique identifier of a mail you would wish to save so that we can put it back into the "encrypted" state.

msc added a comment.Nov 5 2018, 5:11 PM

Yes that is expected, we block the save event as long as we have replaced the Body / Attachments of the mail with decrypted contents are shown. In our debug log you would see the message "Canceling write event".

Yes, I can confirm that log entry.

I wonder if it would be possible for you to close the mail / inspector of the mail with DiscardChanges before doing a save as?

Discarding changes with the Close(OlDiscard) method has no effect on the issue.

Or do you want to save the mail in "decrypted" state?

No, we want to save the e-mail as it is (encrypted) because it is imported to our document management system so that only the expected user(s) can read it when the e-mail is opened in Outlook again.

Alternatively I could offer some interprocess communication e.g. that you could send us a window message with a unique identifier of a mail you would wish to save so that we can put it back into the "encrypted" state.

That sounds nice to me. Is there a way to determine whether the current MailItem is an (GpgOL-)encrypted e-mail, so that this message doesn't need to be sent by default?

aheinecke added a comment.EditedNov 6 2018, 8:16 AM
In T4241#119944, @msc wrote:

I wonder if it would be possible for you to close the mail / inspector of the mail with DiscardChanges before doing a save as?

Discarding changes with the Close(OlDiscard) method has no effect on the issue.

Oh, that is surprising. What we do is that we call (in pseudocode) both:

inspector = Mail.GetInspector;
if (inspector)
{
    inspector.close (OlDiscard);
}
Mail.close(OlDiscard);

So maybe closing the inspector, too is necessary here so that the real close / unload on the mail is triggered. But it might just be that Outlook immediately reopens the mail in your case. Then it won't help but I think you should try that, too because any Interprocess communication will be more effort.

Or do you want to save the mail in "decrypted" state?

No, we want to save the e-mail as it is (encrypted) because it is imported to our document management system so that only the expected user(s) can read it when the e-mail is opened in Outlook again.

Ok.

Alternatively I could offer some interprocess communication e.g. that you could send us a window message with a unique identifier of a mail you would wish to save so that we can put it back into the "encrypted" state.

That sounds nice to me.

Is it possible to access our internal UID MAPI property? That would be the easiest way for us to identify a mail you wish to save.

http://schemas.microsoft.com/mapi/string/{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL UID/0x0000001F

You should be able to get it through:

"Mail.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/string/{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL UID/0x0000001F2");

Is there a way to determine whether the current MailItem is an (GpgOL-)encrypted e-mail, so that this message doesn't need to be sent by default?

Yes when we first see an encrypted or signed mail in a BeforeRead event we change the message class to a class starting with: "IPM.Note.GpgOL." e.g. IPM.Note.GpgOL.MultipartEncrypted. So you can check for message class starting with "IPM.Note.GpgOL"

msc added a comment.Nov 6 2018, 11:23 AM

So maybe closing the inspector, too is necessary here so that the real close / unload on the mail is triggered. But it might just be that Outlook immediately reopens the mail in your case. Then it won't help but I think you should try that, too because any Interprocess communication will be more effort.

I updated my test and tested it with Office 2016 and Office 2019 but the issue remains. The inspector gets closed and the mails' content disappears. So I guess that the Discard-Operation ran successfully.

Is it possible to access our internal UID MAPI property? That would be the easiest way for us to identify a mail you wish to save.

http://schemas.microsoft.com/mapi/string/{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL UID/0x0000001F

You should be able to get it through:
"Mail.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/string/{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL UID/0x0000001F2");

Yep, I can access this property.

Yes when we first see an encrypted or signed mail in a BeforeRead event we change the message class to a class starting with: "IPM.Note.GpgOL." e.g. IPM.Note.GpgOL.MultipartEncrypted. So you can check for message class starting with "IPM.Note.GpgOL"

Great! I also discovered that the message class can be "IPM.Note.InfoPathForm.GpgOL.SMIME.MultipartSigned". What is this about?

Yep, I can access this property.

Ok good. I'll add a windowmessage that takes this as an argument to remove us from a mail

Great! I also discovered that the message class can be "IPM.Note.InfoPathForm.GpgOL.SMIME.MultipartSigned". What is this about?

Ah, sorry for not mentioning it. That message class is for sent messages. Both OpenPGP and S/MIME, we use it to trigger the S/MIME Email Object Algorithm. ( https://msdn.microsoft.com/en-us/library/cc433474(v=exchg.80).aspx )

msc added a comment.Nov 7 2018, 9:48 AM

Ok. Thank you for sharing informations!

aheinecke changed the task status from Open to Testing.Nov 8 2018, 3:48 PM

I've added two message handling routines and a small program to test it (run-messenger.cpp) You can use run-messenger.cpp for reference.

The first is a user message to close all mails gpgol knows about. The second one is a more specific call to close a single mail. It's nicer if you would use the second call to avoid that other opened mails in different explorers etc. might be closed.

The close all is: message = WM_USER with wParam = 1302

The close a specific message is: WM_COPYDATA with a COPYDATASTRUCT that points to a GpgOL UID string in lpData.

Please let me know if you have problems with it or if it works for you. A gpgol.dll with this change can be found under: https://files.gpg4win.org/Beta/gpgol/2.3.2-beta33/
see: https://wiki.gnupg.org/TroubleShooting#Manually_update_GpgOL_to_a_beta

msc added a comment.Nov 9 2018, 3:21 PM

I tested these window messages with the provided beta build. Both messages gets processed and the e-mail is encrypted again but I still receive the 0x80004004 (E_ABORT) error when trying to save the message via the outlook api.

I ran this test multiple times on the same email but nothing changed.

Here is what I got from the GpgOl logging when logging mode was set to standard:

Test 1 - Close all:

13:17:02/9992/windowmessages.cpp:gpgol_window_proc: Closing all mails.
13:17:02/9992/mapihelp.cpp:mapi_change_message_class: have override message class
13:17:02/9992/mapihelp.cpp:mapi_change_message_class: checking message class `IPM.Note.GpgOL.MultipartEncrypted'
13:17:02/9992/mapihelp.cpp:mapi_change_message_class: saving old message class
13:17:02/9992/mapihelp.cpp:mapi_change_message_class: setting message class to `IPM.Note.GpgOL.MultipartEncrypted'
13:17:02/9992/mapihelp.cpp:mapi_create_attach_table: message has 1 attachments
13:17:02/9992/mapihelp.cpp:mapi_create_attach_table: attachment info:
13:17:02/9992/	81925 mt=3 fname=`gpgol_string_1' ct=`multipart/signed' ct_parms=`(null)'
13:17:09/9992/mailitem-events.cpp:Invoke: Canceling write event.

Test 2 - Close (single):

13:22:45/8584/windowmessages.cpp:gpgol_window_proc Received copydata message.
13:22:45/8584/windowmessages.cpp:gpgol_window_proc CopyData with external close. Payload: 45fe7ea2-8229-4feb-bcb0-dd4e7ee2446b
13:22:45/8584/mapihelp.cpp:mapi_change_message_class: have override message class
13:22:45/8584/mapihelp.cpp:mapi_change_message_class: checking message class `IPM.Note.GpgOL.MultipartEncrypted'
13:22:45/8584/mapihelp.cpp:mapi_change_message_class: saving old message class
13:22:45/8584/mapihelp.cpp:mapi_change_message_class: setting message class to `IPM.Note.GpgOL.MultipartEncrypted'
13:22:45/8584/mapihelp.cpp:mapi_create_attach_table: message has 1 attachments
13:22:45/8584/mapihelp.cpp:mapi_create_attach_table: attachment info:
13:22:45/8584/	81925 mt=3 fname=`gpgol_string_1' ct=`multipart/signed' ct_parms=`(null)'
13:22:45/8584/windowmessages.cpp:gpgol_window_proc: Close for 0000016c00039960 uid: 45fe7ea2-8229-4feb-bcb0-dd4e7ee2446b finished.
13:22:45/8584/mailitem-events.cpp:Invoke: Canceling write event.

The problem is probably that you are also holding a reference to the mail in question. For me the close triggers an unload so that GpgOL completely detaches from the mail in question.
I've now added a more explicit tracking of when it should be allowed to write namely after our close with discard changes.

Please try again with -beta38 and check if the mails are still encrypted after a successful write. I'm 99% sure that this will be the case as it is basically the same what we use for moving mails.

msc added a comment.EditedNov 9 2018, 4:54 PM

Thank you! The beta38 is working for me. Guess the mail->setPassWrite (true); line from the last commit did the trick. I did not need to reload the mail object again.

... check if the mails are still encrypted after a successful write...

Yes, they are.

Opening these saved emails again also works fine and the emails are getting decrypted in the inspector. But these emails remain encrypted in outlook (white body, no attachments, header infos are ok) until I restart outlook. Switching the selected mail or folder do not change this. Is there a way to re-decrypt the current message?

Right. While switching the Mail works for me if there are no other references to the mail open (e.g. If I have the mail opened in Outlook Spy switching does not work as the mail is not unloaded). It is better to make it explicit. The code is all there I just have to add a window message handler for it. I'll do that.

It worked as I expected. I've tested it with the run-messenger test and I can close and later "re-decrypt" again. The only surprising thing might be for your users that they have to unlock their secret key again if it is not already unlocked.

The message is the same as for the single close: WM_COPYDATA this time with cbData = 1303 and a UID as payload.

beta39 contains this.

msc added a comment.Mon, Nov 12, 11:28 AM

Ok, I will reload the mail item then!

The beta39 works but I received some random error message boxes when sending the decrypt message:

//(I translated it manually)
---------------------------
GpgOL Warning
---------------------------
Not all attachments are encrypted.
The unencrypted attachments are:

Unbenannte Anlage 00007.gpg
Unbenannte Anlage 00010.dat

Hint: The attachments could be encrtypted or signed itself but GpgOL cannot show their crypto status.
---------------------------
OK   
---------------------------

Another issue is, when re-decrypting a mail item within the sent folder of outlook, I always get the previously mentioned message box. Then outlook freezes and crashes. I attached the GpgOL log with logging mode "+code tracking". There were only the GpgOL and my Test add-in activated to exclude other error sources.

While implementing the decrypt message, I was wondering if it would be possible that the GpgOL add-in would not abort the
Outlook.MailItem.SaveAs(..) call for decrypted messages but doing the encrypt-save-decrypt stuff internally.

I'll look into it.

In T4241#120127, @msc wrote:

While implementing the decrypt message, I was wondering if it would be possible that the GpgOL add-in would not abort the
Outlook.MailItem.SaveAs(..) call for decrypted messages but doing the encrypt-save-decrypt stuff internally.

Sadly that is not possible. As we change the body and attachments outlook will then think the mail was completely changed and resync it, breaking the format to put it in ms specific formats. As the write event comes quite often after we change the body and attachments (autosave) the resync and encrypt-save-decrypt would also happen very often.

msc added a comment.Thu, Nov 22, 9:29 AM

I'll look into it.

Sorry for the bump but did you find time to look into it?

not yet, I try to get to it this week.