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

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

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

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

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

    [DllImport ("CRYPT32", EntryPoint = "CertOpenStore", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr CertOpenStore (int storeProvider, int encodingType, int hcryptProv, int flags, строка pvPara);

    IntPtr storeHandle = CertOpenStore (CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, string.Format (@ "\ {0} {1}", имя_сервера, имя));

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

    [DllImport ("CRYPT32", EntryPoint = "CertEnumCertificatesInStore", CharSet = CharSet.Unicode, SetLastError = true)] public static extern 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)] внутренний внешний статический bool CryptAcquireCertificatePrivateKey (IntPtr pCert, uint dwFlags, IntPtr pvReserved, ref IntPtr phCryptProv, ref int pdwKeySpec, ref bool pfCallerFreeProv);

    // сертификат является сертификатом X5092

    CryptAcquireCertificatePrivateKey (cert.Handle, 0, IntPtr.Zero, ref hProvider, ref _keyNumber, ref freeProvider);

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

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

    IntPtr pData = IntPtr.Zero; CryptGetProvParam (hProvider, PP_UNIQUE_CONTAINER, pData, ref cbBytes, 0));

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

w2k3: \ Documents and Settings \ Все пользователи \ Данные приложения \ Microsoft \ Crypto \ RSA \ MachineKeys

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

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

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

Обновлено

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

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

Structs and 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);
}

1 Ответ

5 голосов
/ 13 марта 2012

Используя CertGetCertificateContextProperty выше, я смог решить этот вопрос. Таким образом, можно определить имя файла закрытого ключа сертификата на удаленном компьютере, используя шаги, упомянутые в обновлении.

...