2

I have to exchange encrypted & signed e-mails with some business partners. Specific algorithms are required, such as :

  • for signature, RSASSA-PSS as the signature algorithm,
  • for encryption, RSAES-OAEP for key encryption & AES-128 CBC for content encryption

I am having troubles setting this up with Mailkit, and actually behind it MailKit & BouncyCastle. Here is where I am so far :

For decryption & signature verification

Decrypting the body is ok, I do it by using a WindowsSecureMimeContext, after setting up my private key in the windows store

Verifying the signature is not ok

case MultipartSigned signedBody:
    try
    {
        using (var ctx = new WindowsSecureMimeContext(StoreLocation.LocalMachine))
        {
            var verifiedData = signedBody.Verify(ctx);
            return verifiedData.All(o => o.Verify());
        }
    }
    catch (Exception e)
    {
        throw new Exception("Error during signature verification.", e);
    }

Certificate of the sender is signed by a common CA, so I'm using again a WindowsSecureMimeContext, but verifiedData.All(o => o.Verify()) throws a DigitalSignatureVerifyException ("Failed to verify digital signature: Unknown error "-1073700864".")

For signature and encryption

Well, that looks tough...

For signature, it seems that I need somewhere a BouncyCastle's PssSigner, which I can get by overriding DkimSigner, and especially the DigestSigner property

class TestSigner : DkimSigner
{
    protected TestSigner(string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256) 
        : base(domain, selector, algorithm)
    {
    }

    public TestSigner(AsymmetricKeyParameter key, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256) 
        : base(key, domain, selector, algorithm)
    {
    }

    public TestSigner(string fileName, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
        : base(fileName, domain, selector, algorithm)
    {
    }

    public TestSigner(Stream stream, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
        : base(stream, domain, selector, algorithm)
    {
    }

    public override ISigner DigestSigner => SignerUtilities.GetSigner(PkcsObjectIdentifiers.IdRsassaPss);
}

However I don't know exactly where to use it. Maybe when using MimeMessage.Sign(), however I am a bit lost with the required parameters in the signature of the method

For encryption, I could find my way up to a RsaesOaepParameters in BouncyCastle's library, by I can't figure out how to use it.

Any help by a mail expert would be much appreciated !

Leonardo Alves Machado
  • 2,747
  • 10
  • 38
  • 53
Adrien
  • 23
  • 4
  • Your verify exception looks like it probably comes from System.Security (were you using the WindowsSecureMimeContext?). If you were, try upgrading to MimeKit/MailKit 2.0 that I just released to nuget.org yesterday. I made a lot of improvements to the WindowsSecureMimeContext for 2.0. I'm not sure if that exception was caused by a bug in MimeKit or is just an error in the System.Security layer). – jstedfast Dec 23 '17 at 03:19
  • No, that did not help. Debug shows that this is triggered by System.Security, while checking SignerInfo's signature [here](https://github.com/jstedfast/MimeKit/blob/e4e0c5b3feb03197f0d0b3d05bacae17db062ad5/MimeKit/Cryptography/WindowsSecureMimeDigitalSignature.cs#L185). I used a BouncyCastleSecureMimeContext instead, which verifies successfully the signature – Adrien Dec 27 '17 at 14:13
  • FWIW, I've just implemented RSAES-OAEP in MimeKit (will be included in v2.5.0 once it is released). RSASSA-PSS is also supported (since MimeKit v2.3.1). – jstedfast Dec 16 '19 at 17:42

1 Answers1

1

A DkimSigner is used for generating DKIM signatures which is not what you want to do. DKIM signatures have nothing to do with S/MIME.

S/MIME Signing using RSASSA-PSS

Currently, the WindowsSecureMimeContext (which uses System.Security as the backend) does NOT support RSASSA-PSS, so you'll need to use the Bouncy Castle backend.

To use the Bouncy Castle backend, you will need to use one of the BouncyCastleSecureMimeContext derivatives (or create your own). As a temporary solution for playing around with things, I might suggest using the TemporarySecureMimeContext, but for long-term use, I would suggest looking at the DefaultSecureMimeContext - although you will still probably want to subclass that to get it working.

Now that you are using a Bouncy Castle S/MIME context, in order to specify that you want to use RSASSA-PSS padding, you'll need to use the APIs that take a CmsSigner parameter such as MultipartSigned.Create() or ApplicationPkcs7Mime.Sign().

Here's an example code snippet:

var signer = new CmsSigner ("certificate.pfx", "password");

// Specify that we want to use RSASSA-PSS
signer.RsaSignaturePaddingScheme = RsaSignaturePaddingScheme.Pss;

// Sign the message body
var signed = MultipartSigned.Create (ctx, signer, message.Body);

// replace the message body with the signed body
message.Body = signed;

S/MIME Encryption Using AES-128 CBC (or any other specific algorithm) with RSAES-OAEP

First, to encrypt using S/MIME, you'll want to use one of the ApplicationPkcs7Mime.Encrypt() methods.[2]

The Encrypt() methods that take a MailboxAddress will automatically create the CmsRecipients and CmsRecipientCollection for you by doing certificate lookups based on the email address provided (or, if any of those mailboxes are actually a SecureMailboxAddress, the Fingerprint is used instead, which is useful if that user has more than 1 certificate in your database or you want to be extra sure that MimeKit picks the right one).

The other thing that MimeKit will do for you when you feed it a list of MailboxAddresses, is that it will look up the supported encryption algorithms that are stored in the database for said user.

For the WindowsSecureMimeContext, this involves looking at the S/MIME Capabilities X509 Certificate Extension attribute and decoding the supported encryption algorithms. In my experience, however, many times this extension is not present on X509 Certificates in the Windows certificate store and so MimeKit will have to assume that only 3DES CBC is supported.

For the DefaultSecureMimeContext, if you have verified any S/MIME signed message by said recipient, then that user's certificate (chain) and advertised encryption algorithms will be stored in MimeKit's custom SQL database (when you sign a message using S/MIME, it's fairly common practice for clients to include the S/MIME Capabilities attribute in the S/MIME signature data).

Now that you understand how that works, if you want to force the use of AES-128 CBC, the way to do that is to manually construct the CmsRecipientCollection yourself.

Naturally, this involves creating a new CmsRecipient for each recipient. To create this class, all you really need is the X509 certificate for that recipient.

var recipient = new CmsRecipient (certificate);

Since you want to force the use of AES-128 CBC, now you just need to override the encryption algorithms that this recipient supports:

recipient.EncryptionAlgorithms = new EncryptionAlgorithm[] {
    EncryptionAlgorithm.Aes128
};

(By default, the EncryptionAlgorithms property will be set to the algorithms listed in the certificate's S/MIME Capabilities Extension attribute (in preferential order), if present, otherwise it'll just contain 3DES CBC).

If you also want to force RSAES-OAEP, you'll need to set:

recipient.RsaEncryptionPadding = RsaEncryptionPadding.OaepSha1;

Add each CmsRecipient to your CmsRecipientCollection and then pass that off to your preferred Encrypt() method and whallah, it will be encrypted using AES-128 CBC.

Notes:

  1. MultipartSigned.Create() will produce a multipart/signed MIME part while ApplicationPkcs7Mime.Sign() will create an application/pkcs7-mime MIME part. Whichever one you want to use is up to you to decide, just keep in mind that your choice may impact compatibility with whatever client your recipients are using (I think most clients support both forms, but you might want to check to make sure).
  2. If you've registered your custom SecureMimeContext class with MimeKit (as briefly described in the README), then you can feel free to use the various Encrypt/Decrypt/Sign/Verify/etc methods that do not take a cryptography context argument as MimeKit will instantiate the default context for you. Otherwise you will need to pass them a context.
jstedfast
  • 35,744
  • 5
  • 97
  • 110
  • Hi jstedfast, thanks for your great answer. If I undersand correctly, I need to use a RSA-PSS private key (OpenSSL will do, I suppose). For encryption, I'll need to force AES-128 indeed, thanks for the trick. However where is RSAES-OAEP set up ? – Adrien Dec 27 '17 at 08:40
  • There’s no way to specify the key encryption algorithm in MimeKit and I have no idea what the default algorithms are for System.Security or BouncyCastle. Nor do I know how to specify them to either of those libraries. I will happily accept patches tho. – jstedfast Dec 27 '17 at 10:40
  • I will have a look, although it won't be in the next days – Adrien Dec 27 '17 at 14:14
  • I've managed to figure out how to do RSAES-OAEP using BouncyCastle, so I've added a new property to `CmsRecipient` to allow setting that padding scheme, but I have not yet made a release with this feature. When it is released, it will have a version of MimeKit 2.5.0. – jstedfast Dec 15 '19 at 19:27