10

I'm trying to decrypt a EnvelopedCms that was encrypted using a non-default AlgorithmIdentifier like this:

ContentInfo contentInfo = new ContentInfo(data);
EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo, new AlgorithmIdentifier(new System.Security.Cryptography.Oid("2.16.840.1.101.3.4.1.42")));
CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, certificates);
envelopedCms.Encrypt(recipients);
byte[] encryptedData = envelopedCms.Encode();

The encryption works as expected. Now when I try to decrypt the envelopedCms using something like this:

EnvelopedCms envelopedCms = new EnvelopedCms();
envelopedCms.Decode(encryptedData );
envelopedCms.Decrypt(certificates);
byte[] decryptedData = envelopedCms.ContentInfo.Content;

I notice that a.) the access to the certificate takes quite long (longer then when using the default AlgorithmIdentifier) and b.) I get this error message:

System.Security.Cryptography.CryptographicException: Access was denied because of a security violation.

Which, looking at the source where this fails, is probably not the issue. Can anyone get the decrypt code above working [with a smartcard]?

//EDIT1 Please note that this issue only occures if the certificate used is placed on a smartcard AND if a AlgorithmIdentifier other then the default one (3DES) was specified, as in the example code. Everything works fine if either the default AlgorithmIdentifier is used or the certificate is NOT placed on a smartcard. It doesn't seem like a SC issue per se, since it's working with the default AlgorithmIdentifier. It's rather the combination of a SC and the AES AlgorithmIdentifier used that's causing the issue but I was unable to find a working solution.

//EDIT2 A complete example demonstrating the issue, read comments for details:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Security.Cryptography.Pkcs;

namespace ConsoleApp
{

    class Program
    {
        static void Main(string[] args)
        {
            // Select the (smartcard) certificate to use it for encryption
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
            X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
            X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
            X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Certificate Select", "Select your smartcard certificate", X509SelectionFlag.MultiSelection);

            // Output which certificate will be used
            Console.WriteLine("Using Certificate:");
            int i = 0;
            foreach (X509Certificate2 x509 in scollection)
            {
                byte[] rawdata = x509.RawData;
                Console.WriteLine("---------------------------------------------------------------------");
                Console.WriteLine("1.\tFull DN: {0}", x509.Subject);
                Console.WriteLine("\tThumbprint: {0}", x509.Thumbprint);
                Console.WriteLine("---------------------------------------------------------------------");
                i++;
            }
            store.Close();

            // Wait
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey(true);

            // Create data for encryption
            string message = "THIS IS OUR SECRET MESSAGE";
            byte[] data = System.Text.Encoding.ASCII.GetBytes(message);

            // Encrypt
            Console.WriteLine("Encrypting message...");

            // ContentInfo contentInfo = new ContentInfo(data); // will use default ContentInfo Oid, which is "DATA"
            // Explicitly use ContentInfo Oid 1.2.840.113549.1.7.1, "DATA", which is the default.
            ContentInfo contentInfo = new ContentInfo(new System.Security.Cryptography.Oid("1.2.840.113549.1.7.1"), data);

            // If using OID 1.2.840.113549.3.7 (the default one used if empty constructor is used) or 1.2.840.113549.1.9.16.3.6  everything works
            // If using OID 2.16.840.1.101.3.4.1.42 (AES CBC) it breaks
            AlgorithmIdentifier encryptionAlgorithm = new AlgorithmIdentifier(new System.Security.Cryptography.Oid("1.2.840.113549.3.7"));
            // EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo); // this will use default encryption algorithm (3DES)
            EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo, encryptionAlgorithm);
            Console.WriteLine("Encyption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.FriendlyName);
            Console.WriteLine("Encyption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.Value);
            CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, scollection);
            /*Console.WriteLine("Receipientinfo count: " + encryptionEnvelopedCms.RecipientInfos.Count.ToString());
            foreach (var i in encryptionEnvelopedCms.RecipientInfos)
            {
                Console.Write("RecipientInfo Encryption Oid: " + i.KeyEncryptionAlgorithm.Oid);
            }
            */
            envelopedCms.Encrypt(recipients);
            byte[] encryptedData = envelopedCms.Encode();
            Console.WriteLine("Message encrypted!");

            // Decrypt
            envelopedCms.Decode(encryptedData);
            Console.WriteLine("Decryption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.FriendlyName);
            Console.WriteLine("Decryption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.Value);
            // Next line will fail if both conditions are true: 
            // 1. A non-default AlgorithmIdentifier was used for encryption, in our case AES
            // 2. The private key required for decryption is placed on a smartcard that requires a manual action, such as entering a PIN code, before releasing the private key
            // Note that everything works just fine when the default AlgorithmIdentifier is used (3DES) or the private key is available in the X509Store
            envelopedCms.Decrypt(scollection);
            byte[] decryptedData = envelopedCms.ContentInfo.Content;
            Console.WriteLine("Message decrypted!");
            Console.WriteLine("Decrypted message: " + System.Text.Encoding.ASCII.GetString(decryptedData));
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey(true);
        }
    }
}
omni
  • 4,104
  • 8
  • 48
  • 67
  • Well we have two types of encryption here: 1. a symetric cipher to encrypt the data and 2. a asymetric cipher using a X.509 certificate to encrypt the key used for the symetric cipher. We use the second encryption (actually I believe that EnvelopedCms is nothing more then S/MIME) so that we can have multiple reciepients. Every single one of them has a different private key for the 2. encryption in order to get to the secret key for the 1st encryption and finally decrypt the data. Note that the default (symetric) cipher that EnvelopedCms will use is DES. I just want to change that to AES. – omni Jun 27 '17 at 17:22
  • I've found some useful info [here](https://stackoverflow.com/a/34205184/589259). Do you maybe use a cryptography provider that doesn't handle OAEP decryption for this specific private key? – Maarten Bodewes Jun 27 '17 at 21:36
  • I'm not sure if that's the issue but I'll give BouncyCastle a try and let you know if it worked or when I solved the issue another way. Maybe in the meantime someone has another idea why this is happening and how to avoid it... Thanks! – omni Jun 28 '17 at 09:04
  • After the call to `Decode(encryptedData)`, `envelopedCms.ContentEncryptionAlgorithm` should contain the original algorithm you used.Can you check that? If this is ok, you have another problem. – Simon Mourier Dec 29 '17 at 16:53
  • You don't need to specify the alg for decryption as it is tagged as ASN.1 tag inside the PKCS (CMS). How do you retrieve your `certificates`? What OS are you running on? And are you using smart cards or HSMs? – zaitsman Jan 02 '18 at 01:44
  • Can't access source right now. OS is Windows (10) and certs are on a SC. – omni Jan 02 '18 at 06:15
  • I imagine if the certs are on a smart card, then you need to ensure you are using correct provider to accessing them and have permissions to the private keys correctly. Also, note that for most `Bouncy Castle` operations your SC will fail because BC needs the keys to be marked `Exportable` – zaitsman Jan 02 '18 at 23:17
  • Everthing works with th SC when using the default alg. So the provider and key access is working. Also even with the specified Alg. the SC is delivering the key, I can tell so from the debug logs. Only the decrypt method doesn't work. – omni Jan 03 '18 at 07:42
  • Okay so this seems to be related to Smartcards only. When testing the code with a local certificate everything works fine. However if you use a certificate that has it's private key on a SC you'll get the Exception "Access was denied because of a security violation.", which is a SC Exception, see https://msdn.microsoft.com/en-us/library/ms936965.aspx Now the interesting question is why this only happens with a non-default AlgorithmIdentifier but works just fine when using the default one. – omni Jan 05 '18 at 14:46
  • I believe that the issue is because of using a CNG OID within the CryptoAPI based EnvelopedCms Class, which seems to be limited in regards to OIDs that work with SC. I yet have not found a working OID for the EnvelopedCms Class that would work with SC but I guess I should switch to CNG anyways. However I was not yet able to find an example how to implement S/MIME with CNG. Maybe someone has an hint on any of both workarounds? – omni Jan 05 '18 at 16:00
  • @zaitsman I believe you're right about that the correct provider has to be used but can you give an example on how to find the correct provider for the AES oid or how to use CNG methods to do something similar to EnvelopedCms (S/MIME)? – omni Jan 05 '18 at 20:18
  • @SimonMourier check and indeed it does return the algorithm I used. So that information is corretly contained in the envelope. – omni Jan 06 '18 at 07:11
  • 1
    For what it's worth, the source of EnvelopedCms is available here: https://referencesource.microsoft.com/#System.Security/system/security/cryptography/pkcs/envelopedpkcs7.cs With this, I have managed to recompile System.Security.dll, the project is available for (a short time) here: https://1drv.ms/u/s!AsmmEDGvydk2i1KmHH_uLbD7I7JB, I can't reproduce the issue, but you can try to replace the reference by this custom one at least to check where it fails and try to understand what the problem is. – Simon Mourier Jan 06 '18 at 09:26
  • @MaartenBodewes I tested the OAEP padding issue you mentioned but it seems to be unrelated to the problem I'm facing. [This](https://stackoverflow.com/questions/35588744/envelopedcms-with-aes-and-rsaencryption-pkcs1-v1-5-padding-instead-of-v2-oaep) post shows how to work around the OAEP problems with EnvelopedCms and I tried both suggested workarounds. Yet still I run into the same issue. Starting to believe this is a issue very specific to the smartcard I'm using (Yubikey 4). SO currently I believe this is PKCS#11 related and not PKCS#7. – omni Jan 07 '18 at 16:17

2 Answers2

1

Although my answer may lead to some incomplete tangents, I believe that it will get you the same assertion that I have come to. The fact is that I use a X509Store allows me to locate the certificates that my machine has. I then pass the collection into the CmsReceipientCollection with a X509Certificate2Collection that is found from my store.Certificates. This method takes 128ms to execute. HTH!

 [TestMethod]
    public void TestEnvelopedCMS()
    {
        X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

        X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
        X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);

        byte[] data = new byte[256];
        //lets change data before we encrypt
        data[2] = 1;

        ContentInfo contentInfo = new ContentInfo(data);
        EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo, new AlgorithmIdentifier(new System.Security.Cryptography.Oid("2.16.840.1.101.3.4.1.42")));
        CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, fcollection);
        envelopedCms.Encrypt(recipients);
        byte[] encryptedData = envelopedCms.Encode();

        //lets decrypt now
        envelopedCms.Decode(encryptedData);
        envelopedCms.Decrypt(fcollection);
        byte[] decryptedData = envelopedCms.ContentInfo.Content;

         //grab index from byte[]
        var item = decryptedData.Skip(2).Take(1).FirstOrDefault();
        var item2 = data.Skip(2).Take(1).FirstOrDefault();

        Assert.IsTrue(item == item2);
    }
  • My mainboard on my workstation broke and I'm still waiting for a replacement, so I can't access my code right now. However I compared your code with my initial question. Can you run your test again but this time after your `//lets decrypt now` comment instead of using the old instance of envelopedCms create a new instance using `new EnvelopedCms()` ? I believe that's part of the issue I'm facing. You example code works because your `EnvelopedCms` instance still holds the `contentInfo`. For a real world app you will encrypt, exit the app, then then, later and maybe on another PC, decrypt. – omni Jan 05 '18 at 09:36
  • I remember that if I tried it just the way you did, using the same instance which is still there in memory, it worked for me too. That's why my example code in my question is explicitly creating a new instance for decryption. – omni Jan 05 '18 at 09:44
  • @masi I will try this. Would it be a more complete example if I picked up a file and serialized it and then decrypted it? I really enjoy encryption/decryption. –  Jan 05 '18 at 14:36
  • Working on it, but this seems to be compleatly Smartcard related. I only get the error if the certificate is placed on a Smartcard but ONLY when using a non-default AlgorithmIdentifier. Everything works fine with the default one. – omni Jan 05 '18 at 14:47
  • Done. Added a full example – omni Jan 05 '18 at 14:56
  • @masi Is this heading in the correct direction in relation to smartcards? https://msdn.microsoft.com/en-us/library/bb742531.aspx is a link that say the user has to get the CA from the enrollment station. Possibly? I am still learning. –  Jan 05 '18 at 14:58
  • Thats more like a generic example on how to get a certificate for your smartcard when using a ActiveDirectory based enterprise CertificateAuthority (CA). It doesn't really matter how you get the certificate on your smartcard as long as you add the issuing CA to your trusted CAs on Windows. The issue in my example code seems to be related to how .Net is accessing the SC. In case the default AlgorithmIdentifier (3DES) is used this seems to work just fine. But when using something else it breaks. FYI: I'm using Yubikey 4 Smartcard (PIV module) – omni Jan 05 '18 at 15:03
  • I just improved my example code so it now includes all details. However I believe you'll need a smartcard in order to test the "bad case" – omni Jan 05 '18 at 20:17
  • @masi I would imagine it is possible to setup a virtual smart card to check things, but I am ready to give it a shot. –  Jan 07 '18 at 18:42
0

Okay so finally I found the reason for why this is not working. It's really dependent on the SC I'm using (Yubikey 4). In my case I created my RSA keys using openssl and then transfered them to the SC using the official Yubico PIV Manager / PIV Tool. This seems to be not supported yet with the official SC driver from Yubico (YubiKey Smart Card Minidriver (YKMD)). The official driver seems however to be the only one that supports all the advanced features of the Yubikey and currently it seems to be required if you want to use AES as encryption algorithm. I was using the OpenSC driver before that will work just fine for 3DES but will fail for more advanced functions. Thus, if someone runs into this issue with the Yubikey:

  1. make sure you're using the official driver (YubiKey Smart Card Minidriver (YKMD)) instead of the Windows base driver or the OpenSC driver
  2. For the official driver to work you have to import your certificates using certutil on Windows, like shown in this article.
  3. If you get a error along the line "NTE_BAD_KEYSET" while trying to import using certutil this is probably because you initialized the PIV function using the Yubico tools (PIV tool and/or PIV manager). This is not supported as well in this case, thus, you'll have to reset your Yubikey PIV config first (basically enter the wrong PIN x times, then the wrong PUK x times and then you can reset the PIV config -all this is done using the PIV tool from Yubico as shown here at the bottom of the page)
  4. Now you can set your custom PIN, PUK, Management-Key and so on using the Yubico tools. It seems as "only" the init of the PIV config is not allowed to be done with this tools. Also note that you'll find more details like "how to set the touch policy" (turned off by default, which kinda su***) in the SC deployment guide from Yubico.
omni
  • 4,104
  • 8
  • 48
  • 67