Decrypting EnvelopedCms with non-default Algorithm

2020-08-21 04:17发布

问题:

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);
        }
    }
}

回答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);
    }


回答2:

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.