EnvelopedCms, как правильно идентифицировать сертификат для расшифровки и не запрашивать пароль?

Я расшифровываю вложения SMIME.P7M в электронных письмах. В настоящее время у меня есть следующее

                EnvelopedCms envDate = new EnvelopedCms(new ContentInfo(data));
                envDate.Decode(data);
                RecipientInfoCollection recips = envDate.RecipientInfos;
                RecipientInfo recipin = recips[0];
                X509Certificate2 x509_2 = LoadCertificate2(StoreLocation.CurrentUser, (SubjectIdentifier)recipin.RecipientIdentifier);

И сертификаты загрузки выглядят так

public static X509Certificate2 LoadCertificate2(StoreLocation storeLocation, SubjectIdentifier identifier)
        {
            X509Store store = new X509Store(storeLocation);
            store.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection certCollection = store.Certificates;
            X509Certificate2 x509 = null;
            X509IssuerSerial issuerSerial;

            if (identifier.Type == SubjectIdentifierType.IssuerAndSerialNumber)
            {
                issuerSerial = (X509IssuerSerial)identifier.Value;
            }

            foreach (X509Certificate2 c in certCollection)
            {
                Console.WriteLine("{0}Valid Date: {1}{0}", Environment.NewLine, c.NotBefore);
                if (c.SerialNumber == issuerSerial.SerialNumber && c.Issuer == issuerSerial.IssuerName)
                {
                    x509 = c;
                    break;
                }
            }
            if (x509 == null)
                Console.WriteLine("A x509 certificate for  was not found");
            store.Close();
            return x509;
        }

Приведенный выше код получает только первого получателя RecipientInfo recipin = recips[0]; однако является ли наиболее эффективным методом получения соответствующего сертификата для прохождения каждого получателя и проверки хранилища на наличие SubjectIdentifier?

После получения правильного сертификата я использую это

                X509Certificate2Collection col = new X509Certificate2Collection(x509_2);
                envDate.Decrypt(col);
               decData = envDate.ContentInfo.Content;

Это запрашивает PIN-код, связанный с закрытым ключом сертификата. Как я могу добавить PIN-код перед вызовом расшифровки, чтобы не было запроса?


person darbid    schedule 21.02.2019    source источник
comment
Можем ли мы предположить, что закрытый ключ находится на устройстве pkcs#11 (смарт-карта, eID, ..)?   -  person gusto2    schedule 21.02.2019
comment
Правильно X509Certificate2 имеет прикрепленный закрытый ключ.   -  person darbid    schedule 21.02.2019
comment
Одно небольшое исправление: у сертификата есть закрытый ключ, но он может находиться на смарт-карте или храниться локально. В любом случае они появятся в магазине, а для доступа к закрытому ключу требуется пароль.   -  person darbid    schedule 21.02.2019
comment
К вашему сведению: если вы ищете совпадения в магазине CurrentUser\My или LocalMachine\My store, это уже сделано с помощью метода Decrypt в дополнение к дополнительным сертификатам, предоставленным в коллекции.   -  person bartonjs    schedule 21.02.2019
comment
Ага понял. Но затем, когда сертификат найден и закрытый ключ считывается автоматически, отображается запрос на ввод пароля. Я хочу найти правильный сертификат, дать пароль для его ключа, продолжить расширение без приглашения системы/Windows.   -  person darbid    schedule 21.02.2019


Ответы (1)


Класс EnvelopedCms в .NET Framework не позволяет легко программно применять PIN-код (или другой механизм разблокировки); в частности, если сертификат существует в хранилищах CurrentUser\My или LocalMachine\My (поскольку они ищутся перед любыми сертификатами в коллекции extraStore).

В .NET Framework 4.7+ вы можете сделать это очень окольным путем для ключей, доступных для CNG, при условии, что сертификат не находится также в хранилищах CurrentUser\My или LocalMachine\My:

CngKey key = ExerciseLeftToTheReader();
key.SetProperty(new CngProperty("SmartCardPin", pin, CngPropertyOptions.None));
X509Certificate2 cert = DifferentExerciseLeftToTheReader();

// You need to use tmpCert because this won't do good things if the certificate
// already knows about/how-to-find its associated private key

using (key)
using (X509Certificate2 tmpCert = new X509Certificate2(cert.RawData))
{
   // Need to NOT read the HasPrivateKey property until after the property set.  Debugger beware.
   NativeMethods.CertSetCertificateContextProperty(
       tmpCert.Handle,
       CERT_NCRYPT_KEY_HANDLE_PROP_ID,
       CERT_SET_PROPERTY_INHIBIT_PERSIST_FLAG,
       key.Handle);

   envelopedCms.Decrypt(new X509Certificate2Collection(tmpCert));
}

(И, конечно, вам нужно определить P/Invoke для CertSetCertificateContextProperty)

В .NET Core 3.0 это стало проще (предварительная версия 2 в настоящее время доступна с этой функцией)... хотя это оставляет вам бремя определения, какой RecipientInfo является вами, и какой ключ идет с ним:

RecipientInfo recipientInfo = FigureOutWhichOneYouCanMatch();
CngKey key = ExerciseLeftToTheReader();
key.SetProperty(new CngProperty("SmartCardPin", pin, CngPropertyOptions.None));

using (key)
using (RSA rsa = new RSACng(key))
{
    envelopedCms.Decrypt(recipientInfo, rsa);
}
person bartonjs    schedule 21.02.2019
comment
Спасибо, это то, что я могу проверить. Я нашел ваш ответ здесь stackoverflow.com/questions/42626742/, и этот код работает для сертификата на смарт-карте, но не работает для сертификатов, хранящихся на ПК. Есть предположения, почему? - person darbid; 21.02.2019
comment
@darbid Предположительно, поставщик программного обеспечения просто не проверяет это свойство (возможно, он подключается к CAPI и не переводит значение в CAPI-ese). Ключи CAPI с предварительно заданными PIN-кодами можно успешно использовать с помощью подхода .NET Core, но без установки свойства в данных сертификата. - person bartonjs; 21.02.2019
comment
Итак, подход .Net Framework работает с сертификатами на смарт-карте, но для сертификатов в CurrentUser/Personal я получаю исключение с неправильным паролем, когда вызываю setProperty. Для этих сертификатов установлен флажок Включить надежную защиту закрытого ключа. Я думаю, что это означает, что для сертификатов, хранящихся в Current/Personal, нет НИКАКОЙ СПОСОБНОСТИ добавить пароль, мы застряли в уродливом сером диалоговом окне 90-х годов. - person darbid; 22.02.2019