Я попробовал aku предложение System.IO.Packaging и мне очень понравилось, хотя заставить работать подписи было довольно сложно и не интуитивно понятно (во всяком случае, на мой крохотный взгляд). Вот шаги, которые я предпринял, на случай, если кому-то еще это понадобится
Основная проблема заключается в том, что в документации просто говорится "сертификаты", но мне пришлось создать защищенный паролем PFX, чтобы он работал. Две полезные ссылки:
- MSDN: Платформа цифровой подписи для соглашений об открытой упаковке
- Создание самоподписанных файлов PFX
Как видно из второй ссылки, у вас есть два варианта создания PFX. Вы можете создать .cer, установить его и затем экспортировать его с помощью графического интерфейса, или вы можете загрузить pvkimport от Microsoft.
Вот команды, которые я использовал (изменить: вы можете использовать OpenSSL для этого - см. Внизу):
makecert -r -n "CN=Paul Stovell" -b 01/01/2000 -e 01/01/2099 -eku 1.3.6.1.5.5.7.3.3 -sv PaulStovell.pvk PaulStovell.cer
cert2spc PaulStovell.cer PaulStovell.spc
pvkimprt -pfx PaulStovell.spc PaulStovell.pvk
В последней команде появляется мастер. Вас спросят, хотите ли вы экспортировать закрытый ключ, для которого требуется, чтобы PFX был защищен паролем. Выберите «да». Затем на следующей странице есть флажок с вопросом «Экспортировать ли все расширенные свойства», на что я также выбрал «да».
В результате вы получите самозаверяющий PFX-файл, защищенный паролем, который вы можете использовать для подписи документов и пакетов.
Вот код для создания, подписи и сохранения пакета, а затем повторного открытия и проверки его.
private const string _digitalSignatureUri = "/package/services/digital-signature/_rels/origin.psdsor.rels";
static void Main(string[] args)
{
var certificate = new X509Certificate2(@"T:\Sample\Input\PaulStovell.pfx", "password");
using (var package = Package.Open("T:\\Sample\\MyPackage.zip", FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
CreatePart(package, @"/Files/File2.dll", @"T:\Sample\Input\File2.dll");
CreatePart(package, @"/Files/File2.pdb", @"T:\Sample\Input\File2.pdb");
CreatePart(package, @"/Files/File2.xml", @"T:\Sample\Input\File2.xml");
package.PackageProperties.Creator = "Paul Stovell";
package.PackageProperties.Title = "Paul Stovell's Package";
package.PackageProperties.Description = "My First Package";
package.PackageProperties.Identifier = "MyPackage";
package.PackageProperties.Version = "1.0.0.0";
// Sign the package
var toSign = package.GetParts().Select(part => part.Uri).ToList();
var uriPartSignatureOriginRelationship = PackUriHelper.CreatePartUri(new Uri(_digitalSignatureUri, UriKind.Relative));
toSign.Add(uriPartSignatureOriginRelationship);
var dsm = new PackageDigitalSignatureManager(package);
dsm.CertificateOption = CertificateEmbeddingOption.InSignaturePart;
dsm.Sign(toSign, certificate);
package.Close();
}
Console.WriteLine("Package written");
Console.WriteLine("Reading package");
using (var package = Package.Open("T:\\Sample\\MyPackage.zip", FileMode.Open, FileAccess.Read, FileShare.Read))
{
Console.WriteLine(" Package name: {0}", package.PackageProperties.Title);
var dsm = new PackageDigitalSignatureManager(package);
if (dsm.IsSigned)
{
var verificationResult = dsm.VerifySignatures(false);
var signature = dsm.Signatures[0];
Console.WriteLine(" Signed by: {0}", signature.Signer.Subject);
Console.WriteLine(" Issued by: {0}", signature.Signer.Issuer);
Console.WriteLine(" Verification: {0}", verificationResult);
}
else
{
Console.WriteLine(" Not signed.");
}
}
Console.ReadKey();
}
private static void CreatePart(Package package, string relativePath, string file)
{
var packagePartUri = new Uri(relativePath, UriKind.Relative);
var packagePart = package.CreatePart(packagePartUri, "part/" + Path.GetExtension(file));
using (var fileContent = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
CopyStream(fileContent, packagePart.GetStream());
}
}
private static void CopyStream(Stream source, Stream target)
{
// It is .NET 3.5, surely this kind of thing has gotten easier by now?
var bufferSize = 0x1000;
var buf = new byte[bufferSize];
int bytesRead = 0;
while ((bytesRead = source.Read(buf, 0, bufferSize)) > 0)
{
target.Write(buf, 0, bytesRead);
}
}
На моей машине вывод:
Package written
Reading package
Package name: Paul Stovell's Package
Signed by: CN=Paul Stovell
Issued by: CN=Paul Stovell
Verification: Success
Последняя строка интересная. Это вывод PackageDigitalSignatureManager.VerifySignatures (). Это указывает на то, что документ не был подделан. Если я изменяю или удаляю файл после подписи, он больше не возвращает «Успех».
Я скопировал .exe, примеры файлов и .pfx на новый компьютер и получил точно такой же вывод, который, похоже, указывает на то, что «Проверка» проверяет только автора подписи и не запрашивает никакой сертификации власти. Я не установил никаких сертификатов на тестовом компьютере, и он работает в своем собственном домене.
Редактировать : Приведенный выше код проверяет только сам документ, но не проверяет сертификат, используя полномочия корневого ЦС. Для этого используйте следующее:
Этот вызов возвращает false при вызове с моим самозаверяющим сертификатом:
var verified = ((X509Certificate2) dsm.Signatures[0].Signer).Verify();
Затем я дважды щелкаю по .cer, из которого я создал .pfx, и устанавливаю его в папку по умолчанию, чтобы он стал доверенным центром сертификации. Как только это будет сделано, приведенный выше вызов вернет true . Таким образом, это будет правильным способом проверки личности подписавшего.
Редактировать 2 : полезная заметка. Чтобы посмотреть и поиграть с сертификатами на вашем компьютере, сделайте следующее:
- Start-> Run и введите «mmc»
- В окне MMC выберите Файл-> Добавить / Удалить SnapIn ...
- Нажмите Сертификаты, Добавить ->
- Выберите один из аккаунтов
- Хит ОК
В моем случае, когда я дважды щелкнул вышеупомянутый файл .cer, чтобы установить его, он был помещен в хранилище Current User , в папку Trusted Root Certification Authorities . Затем вы можете удалить его, чтобы отменить изменения.
Моя окончательная логика проверки выглядит следующим образом:
if (dsm.IsSigned)
{
var verificationResult = dsm.VerifySignatures(false);
var signature = dsm.Signatures[0];
var trusted = ((X509Certificate2)dsm.Signatures[0].Signer).Verify();
Console.WriteLine(" Signed by: {0}", signature.Signer.Subject);
Console.WriteLine(" Issued by: {0}", signature.Signer.Issuer);
Console.WriteLine(" Verified: {0}", verificationResult == VerifyResult.Success);
Console.WriteLine(" Trusted: {0}", trusted);
}
else
{
Console.WriteLine(" Not signed.");
}
Где это всегда проверяется (если я не вмешиваюсь в содержимое), но доверяю только тогда, когда сертификат находится в хранилище.
Редактировать 3 : Я немного больше читал. Файлы PFX на самом деле являются PKCS 12 файлами, являющимися частью спецификации группы RSA. Более простой способ их создания, чем тот, что я показал выше, - это использование открытого источника OpenSSL , в котором есть двоичные файлы , доступные для Windows .
После установки OpenSSL создайте файл пары открытый / закрытый ключ. Я обнаружил, что мне нужно было запустить вторую команду ниже как администратор, так что она могла бы заплатить за запуск этой команды от имени администратора.
openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout PaulStovell.pem -out PaulStovell.cer
Вам будет задано около 7 дополнительных вопросов о вас и вашей организации для создания сертификата. Затем создайте PKCS 12. Он попросит вас ввести пароль:
openssl pkcs12 -export -out PaulStovell.pfx -in PaulStovell.pem -name "Paul Stovell"
Введите и повторно введите свой пароль, когда будет предложено.
На этом этапе System.IO.Packaging может использовать ваш PFX и проверять подпись пакета, но не будет доверять сертификату. Чтобы создать сертификат .cer и установить его в хранилище доверенных центров сертификации, выполните следующие действия:
openssl x509 -in PaulStovell.pem -out PaulStovell.cer
Теперь вы можете дважды щелкнуть сертификат и установить его, и он будет считаться доверенным сертификатом.