Можно ли определить имя файла закрытого ключа сертификата на удаленном сервере Windows?

Я пытаюсь определить имя файла закрытого ключа сертификата, хранящегося на удаленной машине с Windows (2k3/2k8), и у меня возникают некоторые трудности. Я также не очень хорошо знаком с Microsoft CryptAPI, поэтому мне нужна любая помощь, которую вы можете предоставить.

Цель этого упражнения — найти сертификаты с закрытыми ключами, установленными на удаленном компьютере, которые соответствуют определенным критериям, и убедиться, что их файлам закрытых ключей назначены правильные права. Хотя я мог бы назначать права на уровне папки, я бы предпочел назначать права только на уровне файла закрытого ключа, где это необходимо (по очевидным причинам).

Вот сценарий, предположим, что учетная запись службы с административными разрешениями обращается к хранилищу сертификатов:

  1. Я получаю удаленное хранилище сертификатов, используя следующий вызов из C# с использованием p/invoke:

    [DllImport("CRYPT32", EntryPoint = "CertOpenStore", CharSet = CharSet.Unicode, SetLastError = true)] открытый статический extern IntPtr CertOpenStore (int storeProvider, int encodingType, int hcryptProv, int flags, string pvPara);

    IntPtr storeHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, нить.Формат(@"\{0}{1}", serverName, имя));

  2. Затем я использую CertEnumCertificatesInStore для получения сертификатов, которые хочу оценить.

    [DllImport("CRYPT32", EntryPoint = "CertEnumCertificatesInStore", CharSet = CharSet.Unicode, SetLastError = true)] открытый статический внешний IntPtr CertEnumCertificatesInStore (IntPtr storeProvider, IntPtr prevCertContext); IntPtr certCtx = IntPtr.Zero; certCtx = CertEnumCertificatesInStore(storeHandle, certCtx);

  3. Если сертификат соответствует моим критериям, я создаю экземпляр X509Certificate2 из IntPtr, возвращаемого вызовом CertEnumCertificatesInStore, например:

    X509Certificate2 current = new X509Certificate2(certCtx);

  4. Когда у меня есть экземпляры X509Certificate2 для интересующих меня сертификатов, я вызываю CryptAcquireCertificatePrivateKey, чтобы получить поставщика закрытого ключа:

    [DllImport("crypt32", CharSet = CharSet.Unicode, SetLastError = true)] внутренний внешний статический логический CryptAcquireCertificatePrivateKey(IntPtr pCert, uint dwFlags, IntPtr pvReserved, ref IntPtr phCryptProv, ref int pdwKeySpec, ref bool pfCallerFreeProv);

    //сертификат X509Certificate2

    CryptAcquireCertificatePrivateKey(cert.Handle, 0, IntPtr.Zero, ссылка hProvider, ссылка _keyNumber, исх бесплатный провайдер);

  5. Чтобы получить имя файла закрытого ключа, я пытаюсь запросить уникальное имя контейнера у hProvider как pData, например:

    [DllImport("advapi32", CharSet = CharSet.Юникод, SetLastError = true)] внутренний внешний статический логический CryptGetProvParam (IntPtr hCryptProv, CryptGetProvParamType dwParam, IntPtr pvData, ref int pcbData, uint dwFlags);

    IntPtr pData = IntPtr.Zero; CryptGetProvParam(hProvider, PP_UNIQUE_CONTAINER, pData, ссылка cbBytes, 0));

До сих пор все описанные выше шаги отлично работали локально (имя_сервера == имя локальной машины); однако уникальное имя контейнера (имя файла закрытого ключа), которое возвращается для сертификата, хранящегося в хранилище сертификатов локального компьютера удаленного компьютера, не отображается как фактическое имя файла закрытого ключа, которое я вижу в разделе:

w2k3: \Documents and Settings \All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys

ws08: \ProgramData\Microsoft\Crypto\RSA\MachineKeys

Например, если я выполняю вышеуказанные шаги непосредственно на удаленном компьютере, я получаю имя файла закрытого ключа AAAAAAA-111111, но если я запускаю их удаленно, я получаю закрытый ключ BBBBBBBB-2222222. Кроме того, если я установлю удаленный сертификат локально и выполню шаги на своем локальном компьютере, я получу то же имя закрытого ключа BBBBBBBB-2222222.

Скорее всего, мне кажется, что я упустил предостережение на шаге 4, вызывая CryptAcquireCertificatePrivateKey. Возможно, этот вызов использует идентификатор локального компьютера для создания имени уникального контейнера, который будет использоваться для хранения большого двоичного объекта закрытого ключа.

Обновлено

После некоторых дополнительных исследований я нашел блог, в котором подробно описано, как именно создаются имена файлов для контейнеров закрытых ключей здесь.

Вместо использования CryptAcquireCertificatePrivateKey вы можете использовать методы, описанные в этом блоге, для получения имени контейнера закрытого ключа на любом компьютере после получения имени контейнера с помощью CertGetCertificateContextProperty. Код здесь показывает, как получить имя контейнера закрытого ключа, чтобы вы могли сгенерировать имя файла закрытого ключа. * отказ от ответственности - я уверен, что это может быть изменено и может быть даже неполным,но я публикую его на случай, если это поможет кому-то еще в будущем *

Structs и P/Invoke:

[StructLayout(LayoutKind.Sequential)]
public struct CryptKeyProviderInfo
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public String pwszContainerName;
    [MarshalAs(UnmanagedType.LPWStr)]
    public String pwszProvName;
    public uint dwProvType;
    public uint dwFlags;
    public uint cProvParam;
    public IntPtr rgProvParam;
    public uint dwKeySpec;
}

public const uint CERT_KEY_PROV_INFO_PROP_ID = 0x00000002;

[DllImport("crypt32.dll", SetLastError = true)]
internal extern static bool CertGetCertificateContextProperty(IntPtr pCertContext, uint dwPropId, IntPtr pvData, ref uint pcbData);

IntPtr providerInfo = IntPtr.Zero;
string containerName = string.Empty;
try
{

    //Win32 call w/IntPtr.Zero will get the size of our Cert_Key_Prov_Info_Prop_ID struct
    uint pcbProviderInfo = 0;
    if (!Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref pcbProviderInfo))
    {
        //if we can't get the certificate context, return string.empty
        return string.Empty;
    }

    //Allocate heap for Cert_Key_Prov_Info_Prop_ID struct
    providerInfo = Marshal.AllocHGlobal((int)pcbProviderInfo);

    //Request actual Cert_Key_Prov_Info_Prop_ID struct with populated data using our allocated heap
    if (Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, providerInfo, ref pcbProviderInfo))
    {
        //Cast returned pointer into managed structure so we can refer to it by it's structure layout
        Win32.CryptKeyProviderInfo keyInfo = (Win32.CryptKeyProviderInfo)Marshal.PtrToStructure(providerInfo, typeof(Win32.CryptKeyProviderInfo));

        //Get the container name
        containerName = keyInfo.pwszContainerName;
    }

    //Do clean-up immediately if possible
    if (providerInfo != IntPtr.Zero)
    {
        Marshal.FreeHGlobal(providerInfo);
        providerInfo = IntPtr.Zero;
    }
}
finally
{
    //Do clean-up on finalizer if an exception cause early terminiation of try - after alloc, before cleanup
    if (providerInfo != IntPtr.Zero)
        Marshal.FreeHGlobal(providerInfo);
}

6
задан mrdc 12 March 2012 в 21:49
поделиться