Sign and Encrypt on MimeKit

5.5k views Asked by At

I was required to send signed and encrypted mails to our customers, however, this is my first time with the fight of sign and encrypt (I want to highlight this point).

I have tried with OpaqueMail, and MimeKit. Because I really don't understand deeply OpaqueMail and I have my own clients to retrieve emails, I found far better to understand and implement MimeKit.

I know it is a rudimentary implementation what I did in the following lines, but it is just the first contact with it and just a test. I can send signed emails with encrypted body, the problem come with the attachments (we just sent empty bodies with the attachment file, that comes from a DB).

try
    {
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly);

            X509Certificate2Collection collection = store.Certificates.Find(X509FindType.FindBySubjectName, "[email protected]", false); //TODO Change to true after test
            X509Certificate2 senderCertificate = collection[0];

            store = new X509Store(StoreName.AddressBook, StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly);

            collection = store.Certificates.Find(X509FindType.FindBySubjectName, "[email protected]", false); //TODO Change to true after test
            X509Certificate2 recipientCertificate = collection[0];

            MimeMessage mimeMessage = new MimeMessage
            {
                Date = DateTime.Now,
            };

            mimeMessage.From.Add(
                new SecureMailboxAddress(
                    "[email protected]",
                    "[email protected]",
                    senderCertificate.Thumbprint));

            mimeMessage.To.Add(
                new SecureMailboxAddress(
                    "[email protected]",
                    "[email protected]",
                    recipientCertificate.Thumbprint));

            mimeMessage.Subject = "S/MIME Test";

            using (Stream stream = "TestAttachmentFile".ToStream())
            {
                //Attachment
                MimePart attachment = new MimePart(new ContentType("text", "plain"))
                                      {
                                          ContentTransferEncoding =
                                              ContentEncoding.Base64,
                                          ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                                          FileName = "TestAttachmentFileName.txt",
                                          ContentObject = new ContentObject(stream)
                                      };

                Multipart multipart = new Multipart("mixed") { attachment};

                mimeMessage.Body = multipart;

                //Sign / Encryption
                CmsSigner signer = new CmsSigner(senderCertificate);

                CmsRecipientCollection colle = new CmsRecipientCollection();
                X509Certificate bountyRecipientCertificate = DotNetUtilities.FromX509Certificate (recipientCertificate)

                CmsRecipient recipient = new CmsRecipient(bountyRecipientCertificate);
                colle.Add(recipient);

                using (var ctx = new MySecureMimeContext ()) 
                {
                      var signed = MultipartSigned.Create (ctx, signer, mimeMessage.Body);        
                      var encrypted = ApplicationPkcs7Mime.Encrypt (ctx, colle, signed);           
                      mimeMessage.Body = MultipartSigned.Create (ctx, signer, encrypted);
                }

                //Sending
                using (SmtpClient smtpClient = InitSmtpClient(
                    "mail.smtp.com",
                    465,
                    "[email protected]",
                    "Pwd",
                    true))
                {
                    smtpClient.Send(mimeMessage);
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

Well here the questions:

Sign and body encrypt works. But when I try to add an attachment I can open it but always appear the BOM () I can see the attachment however thunderbird doesn't tell me it is an attachment, it's just like the body of the email. I don't know if it is a problem from the ToStream() I have implemented. Also Thunderbird was not able to show correct german characters (ÜüÖöÄäß) neither for spanish ñ

EDIT MimeKit.Decryption methods works pretty well too, and also I get the correct encoding of the message without BOM and the attachment is there. It could be a problem for Thunderbird clients.


Related to SecureMimeContext, we are using HanaDB and we want to store the certificates there, retrieve and use them, but I was not able to find the proper conversion for IX509CertificateDatabase, so, using WindowsStore for the moment.

EDIT, I solve the problem with the DB creating a WindowsSecureMimeContext and overriding the import to get the certificates from the DB. Quick and dirty.

EDIT 2, This was hard to implement, because our implementation with DAO templates, I've made subclass from SecureMimeContext, I look on the WindowsSecureMimeContext to understand what the methods exactly does and just change the code to fit with our DAO stuff.


How can I convert from X509Certificate2 to X509Certificate(BouncyCastle) as is the parameter CmsRecipient?

EDIT, DotNetUtilities.FromX509Certificate did the job.


Is it possible to make a "Tripple Wrap"? Sign, Encrypt, Sign again.

EDIT, Yes

using (var ctx = new MySecureMimeContext ()) {
    var signed = MultipartSigned.Create (ctx, signer, mimeMessage.Body);
    var encrypted = ApplicationPkcs7Mime.Encrypt (ctx, colle, signed);
    mimeMessage.Body = MultipartSigned.Create (ctx, signer, encrypted);
}
1

There are 1 answers

5
jstedfast On BEST ANSWER

It sounds like you're most of the way there now that you figured out to use DotNetUtilities.FromX509Certificate().

Looks like your last remaining question is about how to "triple-wrap".

What I would recommend is this:

using (var ctx = new MySecureMimeContext ()) {
    var encrypted = ApplicationPkcs7Mime.SignAndEncrypt(ctx, signer, colle, mimeMessage.Body);
    mimeMessage.Body = ApplicationPkcs7Mime.Sign (ctx, signer, encrypted);
}

or:

using (var ctx = new MySecureMimeContext ()) {
    var encrypted = ApplicationPkcs7Mime.SignAndEncrypt(ctx, signer, colle, mimeMessage.Body);
    mimeMessage.Body = MultipartSigned.Sign (ctx, signer, encrypted);
}

or:

using (var ctx = new MySecureMimeContext ()) {
    var signed = MultipartSigned.Create (ctx, signer, mimeMessage.Body);
    var encrypted = ApplicationPkcs7Mime.Encrypt (ctx, colle, signed);
    mimeMessage.Body = MultipartSigned.Sign (ctx, signer, encrypted);
}

You might want to play around with all 3 of those options to see which one works best with the mail clients your customers use (Outlook, Thunderbird, Apple Mail?).

ApplicationPkcs7Mime.SignAndEncrypt() uses the application/pkcs7-mime; smime-type=signed-data format and then encrypts that which is different from encrypting a multipart/signed and different clients may handle those to various degrees of success.

A good reason for using multipart/signed is that the email will still be readable even if the user's client cannot decode S/MIME because it uses a detached signature which means that the original text of the message is not encapsulated within binary signature data. But... since you are encrypting as well, it might not make a difference.

Related to SecureMimeContext, we are using HanaDB and we want to store the certificates there, retrieve and use them, but I was not able to find the proper conversion for IX509CertificateDatabase, so, using WindowsStore for the moment.

I would recommend taking a look at DefaultSecureMimeContext and implementing your own IX509CertificateDatabase.

If HanaDB is SQL-based, you could probably subclass SqlCertificateDatabase which is an abstract SQL-based certificate database implementation. You can take a look at SqliteCertificateDatabase.cs or NpgsqlCertificateDatabase.cs (PostgreSQL) to get a feel for how to do it.

Or you could take a look at X509CertificateDatabase.cs to see how to implement a generic version.

I would honestly avoid WindowsSecureMimeContext unless you are actually storing the certificates in the Windows certificate stores.