Подписание CMS in.NET с цепочкой сертификатов не в локальном доверенном хранилище сертификатов
У меня есть сертификаты X509, которые хранятся в сети. Я могу прочитать цепочку из удаленного хранилища сертификатов windows. Мне нужно подписать некоторые данные и включить цепочку в подпись, чтобы можно было проверить ее позже.
проблема в том, что я не могу найти способ поместить цепочку сертификатов в CsmSigner. Я прочитал, что он берет сертификат из параметра конструктора и пытается построить цепочку с X509Chain.Строить. Он игнорирует значения списка сертификатов и терпит неудачу (очевидно), потому что сертификат не может быть найден в локальном хранилище сертификатов Windows.
пожалуйста, найдите ниже мой тестовый код (который работает только в том случае, если сертификаты были сохранены локально в хранилище сертификатов windows)
protected byte[] SignWithSystem(byte[] data, X509Certificate2 cert, X509Certificate[] chain)
{
ContentInfo contentInfo = new ContentInfo(data);
SignedCms signedCms = new SignedCms(contentInfo, true);
CmsSigner cmsSigner = new CmsSigner(cert);
cmsSigner.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1"); //sha256
cmsSigner.IncludeOption = X509IncludeOption.WholeChain;
if (chain != null)
{
//adding cert chain to signer
cmsSigner.Certificates.AddRange(chain);
signedCms.Certificates.AddRange(chain);
}
signedCms.ComputeSignature(cmsSigner); //fails here with System.Security.Cryptography.CryptographicException : A certificate chain could not be built to a trusted root authority.
byte[] signedPkcs = signedCms.Encode();
return signedPkcs;
}
есть ли способ заставить его работать без загрузки сертификатов в локальное хранилище? Должен ли я использовать альтернативный signer?
Я могу попытаться загрузить сертификаты в магазин, но проблемы в том, что
Я должен добавить и удалить сертификаты (разрешения должны быть предоставлены)
существует несколько процессов, которые применяют подпись, поэтому необходимо добавить межпроцессную синхронизацию.
Это не то, что я хочу сделать.
3 ответов
пример подписания CMS с BouncyCastle для .NET
можно использовать после установки BouncyCastle crypto library для .NET, которая содержит свой собственный сертификат X509 и подписывающее оборудование CMS. Многие примеры и документация в интернете предназначены для Java, так как BouncyCastle была библиотекой Java в первую очередь. Я использовал ответ на этот вопрос Stackoverflow в качестве отправной точки для загрузки сертификата и ключа, а также добавлена подпись CMS. Вы можете настройка параметров для получения результатов, которые вы хотите для вашего случая использования.
я сделал функцию подписи похожей на вашу, но обратите внимание, что закрытый ключ теперь является отдельным параметром.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Org.BouncyCastle.Cms;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.X509.Store;
class Program
{
protected static byte[] SignWithSystem(byte[] data, AsymmetricKeyParameter key, X509Certificate cert, X509Certificate[] chain)
{
var generator = new CmsSignedDataGenerator();
// Add signing key
generator.AddSigner(
key,
cert,
"2.16.840.1.101.3.4.2.1"); // SHA256 digest ID
var storeCerts = new List<X509Certificate>();
storeCerts.Add(cert); // NOTE: Adding end certificate too
storeCerts.AddRange(chain); // I'm assuming the chain collection doesn't contain the end certificate already
// Construct a store from the collection of certificates and add to generator
var storeParams = new X509CollectionStoreParameters(storeCerts);
var certStore = X509StoreFactory.Create("CERTIFICATE/COLLECTION", storeParams);
generator.AddCertificates(certStore);
// Generate the signature
var signedData = generator.Generate(
new CmsProcessableByteArray(data),
false); // encapsulate = false for detached signature
return signedData.GetEncoded();
}
static void Main(string[] args)
{
try
{
// Load end certificate and signing key
AsymmetricKeyParameter key;
var signerCert = ReadCertFromFile(@"C:\Temp\David.p12", "pin", out key);
// Read CA cert
var caCert = ReadCertFromFile(@"C:\Temp\CA.cer");
var certChain = new X509Certificate[] { caCert };
var result = SignWithSystem(
Guid.NewGuid().ToByteArray(), // Any old data for sake of example
key,
signerCert,
certChain);
File.WriteAllBytes(@"C:\Temp\Signature.data", result);
}
catch (Exception ex)
{
Console.WriteLine("Failed : " + ex.ToString());
Console.ReadKey();
}
}
public static X509Certificate ReadCertFromFile(string strCertificatePath)
{
// Create file stream object to read certificate
using (var keyStream = new FileStream(strCertificatePath, FileMode.Open, FileAccess.Read))
{
var parser = new X509CertificateParser();
return parser.ReadCertificate(keyStream);
}
}
// This reads a certificate from a file.
// Thanks to: http://blog.softwarecodehelp.com/2009/06/23/CodeForRetrievePublicKeyFromCertificateAndEncryptUsingCertificatePublicKeyForBothJavaC.aspx
public static X509Certificate ReadCertFromFile(string strCertificatePath, string strCertificatePassword, out AsymmetricKeyParameter key)
{
key = null;
// Create file stream object to read certificate
using (var keyStream = new FileStream(strCertificatePath, FileMode.Open, FileAccess.Read))
{
// Read certificate using BouncyCastle component
var inputKeyStore = new Pkcs12Store();
inputKeyStore.Load(keyStream, strCertificatePassword.ToCharArray());
var keyAlias = inputKeyStore.Aliases.Cast<string>().FirstOrDefault(n => inputKeyStore.IsKeyEntry(n));
// Read Key from Aliases
if (keyAlias == null)
throw new NotImplementedException("Alias");
key = inputKeyStore.GetKey(keyAlias).Key;
//Read certificate into 509 format
return (X509Certificate)inputKeyStore.GetCertificate(keyAlias).Certificate;
}
}
}
.NET CMS (быстрое исправление с остальной частью цепочки, опущенной из подписи)
я могу воспроизвести вашу проблему с сертификатом, корень которого не находится в доверенном хранилище сертификатов, и подтвердить, что добавление цепочки сертификатов в cmsSigner
/signedCms
Certificates
коллекция не избегает A certificate chain could not be built to a trusted root authority
ошибка.
вы можете успешно подписать, установив cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;
однако, если вы это сделаете, вы не получите остальную часть цепочки в подписи. Возможно, это не то, чего ты хочешь.
кроме того, в вашем примере вы используете X509Certificate
для массива сертификатов в цепочке, но передача их в X509Certificate2Collection
(обратите внимание на "2" нет). X509Certificate2
происходит от X509Certificate
, но если это не на самом деле X509Certificate2
что вы поместите в одну из этих коллекций, вы получите ошибку приведения, если что-то повторяется над коллекцией (вы не получите ошибку при добавлении сертификата неправильного типа, К сожалению, потому что X509Certificate2Collection
происходит от X509CertificateCollection
и наследует его методы add).
добавление примера кода, который создает отдельную подпись PKCS7 с помощью BouncyCastle (благодаря softwariness) без хранилища сертификатов.
он использует экземпляры .net X509Certificate2 в качестве входного параметра. Первый сертификат в коллекции должен быть связан с закрытым ключом для подписи данных.
также я хотел бы отметить, что невозможно прочитать закрытый ключ, связанный с сертификатом из удаленного хранилища сертификатов Windows, используя .net X509Certificate2.Собственность с закрытым ключом. От закрытый ключ по умолчанию не загружается с сертификатом с помощью X509Store (@"\remotemachine\MY", StoreLocation.LocalMachine) и когда X509Certificate2.Свойство PrivateKey доступно на локальном компьютере, он терпит неудачу с ошибкой "Keyset не существует".
public void SignWithBouncyCastle(Collection<X509Certificate2> netCertificates)
{
// first cert have to be linked with private key
var signCert = netCertificates[0];
var Cert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(signCert);
var data = Encoding.ASCII.GetBytes(Cert.SubjectDN.ToString());
var bcCertificates = netCertificates.Select(_ => Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(_)).ToList();
var x509Certs = X509StoreFactory.Create("Certificate/Collection", new X509CollectionStoreParameters(bcCertificates));
var msg = new CmsProcessableByteArray(data);
var gen = new CmsSignedDataGenerator();
var privateKey = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signCert.PrivateKey).Private;
gen.AddSigner(privateKey, Cert, CmsSignedDataGenerator.DigestSha256);
gen.AddCertificates(x509Certs);
var signature = gen.Generate(msg, false).GetEncoded();
Trace.TraceInformation("signed");
CheckSignature(data, signature);
Trace.TraceInformation("checked");
try
{
CheckSignature(new byte[100], signature);
}
catch (CryptographicException cex)
{
Trace.TraceInformation("signature was checked for modified data '{0}'", cex.Message);
}
}
void CheckSignature(byte[] data, byte[] signature)
{
var ci = new ContentInfo(data);
SignedCms signedCms = new SignedCms(ci, true);
signedCms.Decode(signature);
foreach (X509Certificate cert in signedCms.Certificates)
Trace.TraceInformation("certificate found {0}", cert.Subject);
signedCms.CheckSignature(true);
}
чтобы быть ясным, я нет эксперт по безопасности или криптографии.. но, насколько мне известно, для того, чтобы получатель мог проверить подпись, корневой сертификат в цепочке сертификатов, которую вы использовали для подписи, должен уже быть Доверенные корневые для приемника.
Если получатель не имеет корневого сертификата уже в своем хранилище и отмечен как доверенный корень... тогда не имеет значения, как вы подписываете данные.. он не пройдет проверку на конец приемника. И это по замыслу.
на доверия
следовательно, единственное реальное решение вашей проблемы, которое я вижу, - это убедиться, что корневой сертификат подготовлен как доверенный корень с обоих концов... Обычно выполняется Сертификат.
сценарий корпоративного приложения - обычно на предприятии какая-то группа в ИТ-отделе (у кого есть доступ ко всем машинам в доменном домене администраторы) включили бы этот сценарий, гарантируя, что каждый компьютер в домене имеет корневой сертификат, принадлежащий этой группе, присутствующий на каждом компьютере как доверенный корень, и разработчик приложений на предприятии обычно запрашивает новый сертификат для использования с их приложением, которое имеет цепочку доверия, возвращающуюся к корневому сертификату, уже распространенному на все машины в домене.
выяснил Контактное лицо для этой группы в вашей компании, и они выдают сертификат можно использовать для подписи.
сценарий интернет-приложения - есть установленные центры сертификации, которые владеют своими корневыми сертификатами и работают с поставщиками ОС, чтобы гарантировать, что их корневые сертификаты находятся в доверенном хранилище, поскольку поставщик ОС отправляет ОС своим клиентам. (Одна из причин, почему использование пиратской ОС может быть вредным. Это не только вирусы и вредоносные программы..). И именно поэтому, когда вы используете сертификат, выданный VeriSign для подписания данных, подпись может быть проверена большинством других машин в мире.