Стандартизированный подход к цифровой подписи файлов через .NET - PullRequest
5 голосов
/ 30 января 2009

Я создаю систему для распространения пакетов (архивов .zip), созданных различными организациями. Я хотел бы проверить, действительно ли издатель пакета является тем, кем он себя считает, и что файл не был подделан.

Для проверки издателя требуется система, аналогичная той, которая используется веб-браузерами - например, мое приложение связывается с корневыми центрами сертификации, которые проверяют подлинность. Другими словами, «зеленая полоса» :)

Я предполагаю, что создание пакета будет работать так:

  1. Автор создает zip-пакет
  2. Автор хеширует пакет и подписывает хеш
  3. Переупаковывается с:
    • Заголовок, содержащий подписанный хэш и общедоступный сертификат
    • Тело, содержащее содержимое zip-файла

Открытие пакета будет работать так:

  1. Возьмите тело данных
  2. Хеш, используя тот же алгоритм
  3. Расшифровать хеш пакета, используя открытый ключ из сертификата
  4. Сравните два хеша - теперь у нас есть целостность
  5. Свяжитесь с корневыми центрами сертификации, чтобы проверить личность

Таким образом, я проверил личность, а также проверил содержимое (само содержимое не должно быть зашифровано - цель - проверка, а не конфиденциальность).

Итак, мои вопросы:

  1. Является ли вышеуказанный правильный способ приблизиться к нему?
  2. Какой алгоритм хеширования обычно используют люди? Я предполагаю, что это должно быть односторонним. Вы бы просто выбрали один (MD5, SHA1, SHA2?) Или это более нормально - поддерживать разновидность и позволить автору пакета сообщить вам, какой из них он использовал (например, заголовок документа содержит имя функции хеширования).
  3. Как вы работаете с корневыми ЦС? Это работа класса X509Store или есть дополнительные шаги?
  4. Какие сертификаты здесь задействованы? Такие же сертификаты используются для подписи сборок .NET? (Сертификаты для подписи кодов?)

Наконец, если организация не имеет оплаченного сертификата и вместо этого решает использовать самостоятельно выданный сертификат, я предполагаю, что я все еще могу проверить хэши (ради целостности данных) без необходимости устанавливать компоненты в хранилища сертификатов компьютера или что-то подобное (в этих случаях я просто отображал: «Опубликовано XYZ Co. (Неподтверждено)». Это правильно?

Я нашел множество ссылок о том, как использовать X509 и RSACryptoServiceProvider, так что я, вероятно, могу понять код, я думаю, что меня больше интересует процесс и я знаю, что использую правильные методы.

Ответы [ 2 ]

17 голосов
/ 30 января 2009

Я попробовал aku предложение System.IO.Packaging и мне очень понравилось, хотя заставить работать подписи было довольно сложно и не интуитивно понятно (во всяком случае, на мой крохотный взгляд). Вот шаги, которые я предпринял, на случай, если кому-то еще это понадобится

Основная проблема заключается в том, что в документации просто говорится "сертификаты", но мне пришлось создать защищенный паролем PFX, чтобы он работал. Две полезные ссылки:

  1. MSDN: Платформа цифровой подписи для соглашений об открытой упаковке
  2. Создание самоподписанных файлов 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 : полезная заметка. Чтобы посмотреть и поиграть с сертификатами на вашем компьютере, сделайте следующее:

  1. Start-> Run и введите «mmc»
  2. В окне MMC выберите Файл-> Добавить / Удалить SnapIn ...
  3. Нажмите Сертификаты, Добавить ->
  4. Выберите один из аккаунтов
  5. Хит ОК

В моем случае, когда я дважды щелкнул вышеупомянутый файл .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

Теперь вы можете дважды щелкнуть сертификат и установить его, и он будет считаться доверенным сертификатом.

15 голосов
/ 30 января 2009

Существует стандартный API для создания подписанных пакетов ZIP.

System.IO.Packaging Пространство имен содержит необходимые классы для создания OPS (открытых упаковочных спецификаций) ZIP-пакетов с цифровыми подписями .

...