【问题标题】:Using client certificate not in certificate store使用不在证书存储中的客户端证书
【发布时间】:2010-12-11 05:55:51
【问题描述】:

我正在尝试使用我的客户端证书对 WebService 进行身份验证,但由于某些原因(我解释过),我不想从商店加载证书,而是从光盘中读取它。

以下内容:

// gw is teh WebService client
X509Certificate cert = new X509Certificate(PathToCertificate);
_gw.ClientCertificates.Add(ClientCertificate());
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
_gw.DoSomeCall();

总是返回 403 - 服务没有授权我。但是,当我将该证书保存到 CertStore 时,它​​就可以工作了。 (如 MSDN 中所述。)

是否可以使用不在商店的证书?

(原因是我得到了 windows 服务(客户端)有时调用 web 服务(服务器),并且在未指定的时间后,服务“忘记”了我的证书并且没有对服务器进行授权,没有明显的原因)

【问题讨论】:

    标签: c# web-services certificate


    【解决方案1】:

    PathToCertificate 是什么类型的文件?如果它只是一个 .cer 文件,它将不包含证书的私钥,并且尝试将该证书用于 SSL/TLS 将失败。

    但是,如果您有一个包含证书的公钥和私钥的 PKCS7 或 PKCS12 文件,您的代码将可以工作(如果私钥有密码,您可能需要使用需要密码的重载)。

    为了测试这一点,我去了http://www.mono-project.com/UsingClientCertificatesWithXSP 并按照这些说明创建了我的 client.p12 文件。我还使用 HttpListener 创建了一个简单的 HTTPS 服务器进行测试。

    然后我将以下程序编译成'client.exe'并运行如下:

     client.exe https://<MYSSLSERVER>/ client.p12 password
    

    其中 client.p12 是之前生成的 PKCS12 文件,“password”是我为证书的私钥设置的密码。

    using System;
    using System.IO;
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    
    public class HttpWebRequestClientCertificateTest : ICertificatePolicy {
    
        public bool CheckValidationResult (ServicePoint sp, X509Certificate certificate,
                WebRequest request, int error)
        {
                return true; // server certificate's CA is not known to windows.
        }
    
        static void Main (string[] args)
        {
                string host = "https://localhost:1234/";
                if (args.Length > 0)
                        host = args[0];
    
                X509Certificate2 certificate = null;
                if (args.Length > 1) {
                        string password = null;
                        if (args.Length > 2)
                                password = args [2];
                        certificate = new X509Certificate2 (args[1], password);
                }
    
                ServicePointManager.CertificatePolicy = new HttpWebRequestClientCertificateTest ();
    
                HttpWebRequest req = (HttpWebRequest) WebRequest.Create (host);
                if (certificate != null)
                        req.ClientCertificates.Add (certificate);
    
                WebResponse resp = req.GetResponse ();
                Stream stream = resp.GetResponseStream ();
                StreamReader sr = new StreamReader (stream, Encoding.UTF8);
                Console.WriteLine (sr.ReadToEnd ());
        }
    }
    

    如果您希望我上传测试双方使用的服务器代码和证书,请告诉我。

    【讨论】:

    • 您知道如何在 Windows .NET 上进行同样的工作吗?出于某种原因,如果不在 x509 商店中注册证书,我就无法使其正常运行
    【解决方案2】:

    潜在的问题可能是 SSL 会话的缓存(Schannel 缓存)。只有第一个请求会协商 SSL 握手。后续请求将使用相同的会话 ID,并希望服务器接受它。如果服务器清除 SessionId,请求将失败并返回 403 错误。要禁用本地 ssl 会话缓存(并为每个请求强制进行 SSL 协商),您必须打开 Windows 注册表文件夹:

    [HKEY_LOCAL_MACHINE][System][CurrentControlSet][Control][SecurityProviders][SCHANNEL]

    并添加名为 ClientCacheTime (DWORD) 且值为 0 的键。

    这里讨论了这个问题:

    http://support.microsoft.com/?id=247658

    【讨论】:

    • 这是相当大的。如果您不知道这一点,您可能会花很多时间认为您正在将“试错”解决方案缩减为最简单的形式,而实际上却毫无防备地破坏了一些东西。
    【解决方案3】:

    你至少有两个问题的可能性......

    首先...

    除非使用密码访问,否则您的客户端证书文件不能包含私钥。您应该使用带有密码的 PKCS #12 (*.pfx) 证书,以便您的客户端可以访问私钥。正如其他人已经发布的那样,您的客户端代码在打开证书时必须提供密码。有几种创建方法,最简单的方法是使用以下命令行首先生成证书,然后使用 MMC 证书管理器导出证书私钥:

    Process p = Process.Start(
        "makecert.exe",
        String.Join(" ", new string[] {
            "-r",//                     Create a self signed certificate
            "-pe",//                    Mark generated private key as exportable
            "-n", "CN=" + myHostName,// Certificate subject X500 name (eg: CN=Fred Dews)
            "-b", "01/01/2000",//       Start of the validity period; default to now.
            "-e", "01/01/2036",//       End of validity period; defaults to 2039
            "-eku",//                   Comma separated enhanced key usage OIDs
            "1.3.6.1.5.5.7.3.1," +//    Server Authentication (1.3.6.1.5.5.7.3.1)
            "1.3.6.1.5.5.7.3.2", //     Client Authentication (1.3.6.1.5.5.7.3.2)
            "-ss", "my",//              Subject's certificate store name that stores the output certificate
            "-sr", "LocalMachine",//    Subject's certificate store location.
            "-sky", "exchange",//       Subject key type <signature|exchange|<integer>>.
            "-sp",//                    Subject's CryptoAPI provider's name
            "Microsoft RSA SChannel Cryptographic Provider",
            "-sy", "12",//              Subject's CryptoAPI provider's type
            myHostName + ".cer"//       [outputCertificateFile]
        })
    );
    

    第二...

    您的下一个问题将是服务器端。服务器必须允许此证书。你有正确的逻辑,但是在错误的一端,将这条线移动到处理请求的 Web 服务器。如果不能,则必须将上面保存的“.cer”文件带到服务器并将其添加到服务器计算机的信任列表中:

    ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
    

    【讨论】:

    • -1。服务器不需要知道客户端证书的私钥。它只需要信任签署客户端证书的 CA。在协商 SSL3/TLS 时,服务器会发送一个列表,其中包含所有允许的 CA 的 DN。此时客户端应该会看到客户端证书的 CA。在列表中并发送。
    • 不正确?请参阅tools.ietf.org/html/rfc5246#page-53,第 7.4.4 节:certificate_authorities 可接受的 certificate_authorities 的可分辨名称 [X501] 列表,以 DER 编码格式表示。这些专有名称可以为根 CA 或从属 CA 指定所需的专有名称;因此,该消息可用于描述已知的根以及所需的授权空间。如果 certificate_authorities 列表为空,则客户端可以发送任何适当的 ClientCertificateType 的证书,除非
    • 哦,是的。如果按照你说的安装客户端证书并信任服务器,就相当于安装了签署证书的CA。如何删除 -1?
    • 不用担心 -1 的事情...我正在阅读您发布的 rfc now 链接。
    • 啊,服务器可以发送 CA 列表的说法是正确的,我不知道 nego 的那部分...抱歉 ;)
    【解决方案4】:

    您需要证书密码吗?如果是这样,构造函数中有一个字段。

    X509Certificate cert = new X509Certificate(PathToCertificate,YourPassword);
    

    【讨论】:

      猜你喜欢
      • 2013-02-10
      • 1970-01-01
      • 2019-11-08
      • 2012-01-08
      • 1970-01-01
      • 2015-09-06
      • 2016-01-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多