Подпись сертификата производит другую подпись, когда на сервере - PullRequest
0 голосов
/ 15 ноября 2018

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

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

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace TestSignature
{
    class Program
    {
        static void Main(string[] args)
        {

            var key = SigningKeyFromCertificate(StoreName.My, StoreLocation.LocalMachine, X509FindType.FindByThumbprint, "thumbprint");
            var alg = CryptoConfig.MapNameToOID("SHA256");
            var data = Encoding.UTF8.GetBytes("test");
            var sig = key.SignData(data, alg);

            Console.WriteLine(Convert.ToBase64String(sig));

        }

        private static RSACryptoServiceProvider SigningKeyFromCertificate(StoreName storeName, StoreLocation storeLocation, X509FindType findType, string findValue)
        {
            X509Store store = new X509Store(storeName, storeLocation);
            store.Open(OpenFlags.ReadOnly);

            var certs = store.Certificates.Find(findType, findValue, false);
            if (certs?.Count > 0)
            {
                var cert = certs[0];
                if (cert.HasPrivateKey)
                {
                    // Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
                    var key = cert.PrivateKey as RSACryptoServiceProvider;
                    var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;
                    var parameters = new CspParameters(enhanced.ProviderType, enhanced.ProviderName, key.CspKeyContainerInfo.UniqueKeyContainerName);
                    return new RSACryptoServiceProvider(parameters);
                }
                else
                {
                    throw new Exception($"No private key access to cert '{findValue}.'");
                }
            }
            else
            {
                throw new Exception($"Cert '{findValue}' not found!");
            }
        }
    }
}

Локально я получаю следующую подпись:

YUjspKhLl7v3u5VQkh1PfHytMTpEtbAftxOA5v4lmph3B4ssVlZp7KedO5NW9K5L222Kz9Ik9/55NirS0cNCz/cDhEFRtD4daJ9qLRuM8oD5hCj6Jt9Vc6WeS2he+Cqfoylnv4V9plfi1xw8y7EyAf4C77BGkXOdyP5wyz2Xubo=

На сервере я получаю такую:

u1RUDwbBlUpOgNNkAjXhYEWfVLGpMOa0vEfm6PUkB4y9PYBk1lDmCAp+488ta+ipbTdSDLM9btRqsQfZ7JlIn/dIBw9t5K63Y7dcDcc7gDLE1+umLJ7EincMcdwUv3YQ0zCvzc9RrP0jKJManV1ptQNnODpMktGYAq1KmJb9aTY=

Есть идеи, что может быть другим?Я думаю, что с тем же сертификатом, тем же кодом и теми же данными подпись должна быть одинаковой.

(Пример написан на C # 4.5.2.)

1 Ответ

0 голосов
/ 16 ноября 2018

У вас есть код для повторного открытия дескриптора клавиши CAPI в PROV_RSA_AES:

// Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
var key = cert.PrivateKey as RSACryptoServiceProvider;
var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;

var parameters = new CspParameters(
    enhanced.ProviderType,
    enhanced.ProviderName,
    key.CspKeyContainerInfo.UniqueKeyContainerName);

return new RSACryptoServiceProvider(parameters);

Но key.CspKeyContainerInfo.UniqueKeyContainerName - это не имя ключа (это имя файла на диске, где хранится ключ), поэтому вы открываете новый ключ (вы также генерируете новый эфемерный ключ просто спросить, какой поставщик по умолчанию). Поскольку он является именованным ключом, он сохраняется, и последующие выполнения приложений разрешаются на один и тот же ключ - но разные «одинаковые» ключи на каждом компьютере.

Более стабильный способ повторного открытия ключа -

var cspParameters = new CspParameters
{
    KeyContainerName = foo.CspKeyContainerInfo.KeyContainerName,
    Flags = CspProviderFlags.UseExistingKey,
};

(поскольку тип и имя провайдера не указаны, они будут использовать значения по умолчанию, и, сказав UseExistingKey, вы получите исключение, если ссылаетесь на несуществующий ключ).


Тем не менее, самое простое решение - перестать использовать RSACryptoServiceProvider. В .NET Framework 4.6 (и .NET Core 1.0) есть метод (n extension) для X509Certificate2, GetRSAPrivateKey(), он возвращает RSA (что вам следует избегать приведения), обычно это RSACng (в Windows) , но может быть RSACryptoServiceProvider, если только CAPI имел драйвер, необходимый для HSM, и может быть некоторым другим RSA в будущем. Так как RSACng обрабатывает SHA-2 лучше, почти никогда не возникает необходимости «вновь открывать» возвращаемый объект (даже если он RSACryptoServiceProvider, и даже если тип не PROV_RSA_AES (24), это не означает, что HSM не сможет выполнить SHA-2).

...