Using MimeKit to cms sign and verify

708 views Asked by At

I am using Mimekit to do rsa pss cms sign, to simulate this openssl command openssl cms -sign -in keys.zip -binary -nodetach -signer selfsigned.crt -inkey keypair.pem -out keys.zip.signed -keyopt rsa_padding_mode:pss

    public byte[] Sign(byte[] data, byte[] signCert, byte[] privateKey, CmsKeyOpt cmsKeyOpt)
            {
                MimeMessage message = new MimeMessage
                {
                    Body = new MimePart()
                    {
                        Content = new MimeContent(new MemoryStream(data)),
                        ContentTransferEncoding = ContentEncoding.Binary,
                    },
                };
    
                // Load private key from byte array
                StreamReader stream = new StreamReader(new MemoryStream(privateKey), Encoding.Default);
                AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)new PemReader(stream).ReadObject();
    
                // load certifacte from byte array
                X509CertificateParser parser = new X509CertificateParser();
                X509Certificate certificate = parser.ReadCertificate(signCert);
    
                // Create RSA PSS CMS signer
                CmsSigner signer = new CmsSigner(certificate, keyPair.Private)
                {
                    RsaSignaturePadding = RsaSignaturePadding.Pss,
                    DigestAlgorithm = DigestAlgorithm.Sha256,
                };
    
                // Create BouncyCastle Secure MimeContext 
                BouncyCastleSecureMimeContext ctx = new TemporarySecureMimeContext();
                ctx.EncapsulatedSign(signer, new MemoryStream(data));
    
                // Get signed message body and override it
                message.Body = MultipartSigned.Create(ctx, signer, message.Body);
    
                byte[] singedData;
                using (var memory = new MemoryStream())
                {
                    message.WriteTo(memory);
                    singedData = memory.ToArray();
                }
                return singedData;
            } 

And everything works fine, my question how I can achieve verify by Mimekit/BouncyCastle, to simulate this openssl command openssl cms -verify -in keys.zip.signed.dec -CAfile selfsigned.crt -out keys_dec_unsigned.zip

I tried this but I got exception System.NotSupportedException: 'SQLite is not available on pkcs7.Verify(out original) line

 public byte[] Verify(byte[] data, byte[] signCert, byte[] privateKey)
        {
            bool valid = false;
            // Load private key from byte array
            StreamReader stream = new StreamReader(new MemoryStream(privateKey), Encoding.Default);
            AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)new PemReader(stream).ReadObject();

            // load certifacte from byte array
            X509CertificateParser parser = new X509CertificateParser();
            X509Certificate certificate = parser.ReadCertificate(signCert);

            MimeMessage message = MimeMessage.Load(new MemoryStream(data));
            // Create BouncyCastle Secure MimeContext 
            MimeEntity original;
            ApplicationPkcs7Mime pkcs7 = message.Body as ApplicationPkcs7Mime;
            if (pkcs7 != null && pkcs7.SecureMimeType == SecureMimeType.SignedData)
            {
                foreach (var signature in pkcs7.Verify(out original))
                {
                    try
                    {
                        valid = signature.Verify();
                    }
                    catch (DigitalSignatureVerifyException)
                    {
                        // There was an error verifying the signature.
                    }
                }
            }


            byte[] res = { 0 };
            return res;
        }

Is there any guide I can follow to verify and return the data to it's origin forum, or an example?

1

There are 1 answers

18
jstedfast On

By default, MimeKit tries to instantiate a S/MIME verify context based on its SQLite backend for certificate storage.

If you don't have SQLite installed and/or don't have your certificates stored in the SQLite database that MimeKit will maintain for you, then you need to either register a different backend for certificate storage/retrieval or instantiate your own S/MIME context (like you did for signing).

MimeKit comes with 2 options:

Once you decide on which of those to use, you will need to import your certificate(s) into the context (which will import them into the appropriate backend storage location).

Then, after that, you can do this:

using (BouncyCastleSecureMimeContext ctx = new TemporarySecureMimeContext()) {
    ctx.Import (...);

    foreach (var signature in pkcs7.Verify(ctx, out original)) {
        // ...
    }
}

Update:

This devolved into misuse of the openssl cms sign command and an inability to verify the signature using MimeKit because MimeKit expects the encapsulated signed data to be in MIME format as it is supposed to be according to the S/MIME specifications.

Here's the deal:

The openssl cms sign command can be used to sign arbitrary data and the corresponding openssl cms verify command can be used to verify such signed output. HOWEVER, it is only valid S/MIME if the original content signed by the openssl cms sign command is/was in MIME format.

MimeKit expects valid S/MIME because it is... surprise, surprise... a MIME library.

As you might have noticed, the Verify() method has an output parameter (out MimeEntity originalEntity). This means that the Verify() method extracts the encapsulated content from within the signed data and parses it into a MIME entity and returns it to you, the caller, in the form of said output parameter.

If the encapsulated content is not in MIME format, then, obviously, the parser will fail to parse it.

Additional background with how CMS signatures work:

When you sign content using the CMS signing routine (e.g. openssl cms sign ...), it produces something that looks a bit like this:

MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
Content-Type: application/pkcs7-mime; smime-type=signed-data; name="smime.p7m"
Content-Transfer-Encoding: base64 

MIIQgAYJKoZIhvcNAQcCoIIQcTCCEG0CAQExDTALBglghkgBZQMEAgEwggkPBgkq...

The base64 content in the above MIME part includes both the original content and the CMS signature data (i.e. a list of signatures).

Therefor, MimeKit's Verify() method needs to separate the signatures from the original content after base64 decoding it. MimeKit then returns to you, the caller, the original content (which it expects to be in MIME format) and the list of signatures that you can then independently verify the authenticity of.

When I kept repeating myself that it mattered what format the file.bin file was in when you signed the content, I was not saying that openssl or MimeKit needed to parse file.bin when verifying the signature, I was saying that file.bin has exactly the same content as what would be extracted from the base64 blob in the S/MIME output produced by openssl cms sign ... and therefore, it needs to be in MIME format.