8

リモートのWindows(2k3 / 2k8)マシンに保存されている証明書の秘密鍵のファイル名を特定しようとしていますが、問題が発生しています。また、私はMicrosoftのCryptAPIにあまり詳しくないので、あなたが提供できる助けを探しています。

この演習の目的は、特定の基準を満たす秘密鍵がリモートコンピューターにインストールされている証明書を見つけ、それらの秘密鍵ファイルに正しい権限が割り当てられていることを確認することです。フォルダレベルで権限を割り当てることはできますが、(明らかな理由で)必要な場合にのみ秘密鍵ファイルレベルで権限を割り当てることをお勧めします。

シナリオは次のとおりです。管理者のような権限を持つサービスアカウントが証明書ストアにアクセスしていると仮定します。

  1. p / invokeを使用してC#から次の呼び出しを使用して、リモート証明書ストアを取得します。

    [DllImport( "CRYPT32"、EntryPoint = "CertOpenStore"、CharSet = CharSet.Unicode、SetLastError = true)] public static 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、string.Format(@ "\ {0} {1}"、serverName、name));

  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. 証明書が私の基準に一致する場合、次のようにCertEnumCertificatesInStore呼び出しから返されたIntPtrからX509Certificate2インスタンスを作成します。

    X509Certificate2 current = new X509Certificate2(certCtx);

  4. 興味のある証明書のX509Certificate2インスタンスを取得したら、 CryptAcquireCertificatePrivateKeyを呼び出して、秘密鍵プロバイダーを取得します。

    [DllImport( "crypt32"、CharSet = CharSet.Unicode、SetLastError = true)] internal extern static bool CryptAcquireCertificatePrivateKey(IntPtr pCert、uint dwFlags、IntPtr pvReserved、ref IntPtr phCryptProv、ref int pdwKeySpec、

    //certはX509Certificate2で​​す

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

  5. 秘密鍵ファイル名を取得するために、hProviderから次のようなpDataとして一意のコンテナ名を要求しようとします。

    [DllImport( "advapi32"、CharSet = CharSet.Unicode、SetLastError = true)] internal extern static 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));

これまでのところ、上記のすべての手順はローカルでうまく機能します(servername ==ローカルマシン名)。ただし、リモートコンピューターのローカルマシンの証明書ストアに格納されている証明書に対して返される一意のコンテナー名(秘密鍵ファイル名)は、次の場所に表示されている実際の秘密鍵ファイル名としては表示されません。

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

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

たとえば、上記の手順をリモートマシンで直接実行すると、秘密鍵ファイル名AAAAAAA-111111が取得されますが、リモートで実行すると、秘密鍵BBBBBBBB-2222222が取得されます。また、リモート証明書をローカルにインストールし、ローカルマシンに対して手順を実行すると、同じ秘密鍵名BBBBBBBB-2222222を取得します。

CryptAcquireCertificatePrivateKeyを呼び出すステップ4で、警告が欠落している可能性があります。この呼び出しは、ローカルマシンのIDに依存して、秘密鍵BLOBを格納するために使用される一意のコンテナの名前を生成している可能性があります。

更新しました

さらに調査した結果、秘密鍵コンテナのファイル名がここでどのように作成されるかを正確に詳しく説明しているブログを見つけました。

CryptAcquireCertificatePrivateKeyを使用する代わりに、CertGetCertificateContextPropertyによって取得されたコンテナーの名前を取得したら、そのブログで説明されているメソッドを使用して、任意のマシンで秘密鍵コンテナー名を取得できます。ここのコードは、秘密鍵コンテナ名を取得して秘密鍵ファイル名を生成する方法を示しています。*免責事項-これは変更される可能性があり、完全ではない可能性もあると確信していますが、将来誰かに役立つ場合に備えて投稿しています*

構造体と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);
}
4

1 に答える 1

6

上記のCertGetCertificateContextPropertyを使用して、この質問を解決することができました。したがって、更新で説明されている手順を使用して、リモートコンピューター上の証明書の秘密鍵のファイル名を判別することができます。

于 2012-03-12T22:45:11.817 に答える