7

我已经使用 OpenSSL 生成了一个 CA 和多个证书(由 CA 签名),并且我有一个 .NET/C# 客户端和服务器,它们都使用SslStream它们各自有自己的证书/密钥,启用了相互身份验证并禁用了撤销。

我正在使用RemoteCertificateValidationCallbackforSslStream来验证远程服务器的证书,我希望我可以在程序中加载 CA 的公共证书(作为文件)并使用它来验证远程证书,而不是在 Windows 证书存储中实际安装 CA . 问题是X509Chain除非我将 CA 安装到商店中,否则不会显示任何其他内容,当我打开其中一个证书的 PEM 版本时,Windows CryptoAPI shell 也不会显示。

我的问题是,我如何仅通过使用 CA 的公共证书文件而不使用 Windows 证书存储或 WCF来验证证书是否已由我的特定 CA 签名RemoteCertificateValidationCallbackX509Certificate并且X509Chain似乎没有给我任何可使用的东西?

4

2 回答 2

5

Because the CA certificate is NOT in the root certificate store, you will have within the RemoteCertificateValidationCallback() an error flag of SslPolicyErrors.RemoteCertificateChainErrors ; a possibility is to validate explicitely the certificate chain against your own X509Certificate2Collection, since you are not using the local store.

if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors)
{
    X509Chain chain0 = new X509Chain();
    chain0.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
    // add all your extra certificate chain
    chain0.ChainPolicy.ExtraStore.Add(new X509Certificate2(PublicResource.my_ca));
    chain0.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
    isValid = chain0.Build((X509Certificate2)certificate);
}

You can also re-use the chain passed in the callback, add your extra certificate(s) in the ExtraStore collection, and validate with the AllowUnknownCertificateAuthority flag which is needed since you add untrusted certificate(s) to the chain.

You could also prevent the original error by adding programmatically the CA certificate in the trusted root store (of course it opens a popup, for it is a major security problem to globally add a new trusted CA root) :

var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
X509Certificate2 ca_cert = new X509Certificate2(PublicResource.my_ca);
store.Add(ca_cert);
store.Close();

EDIT: For those who want to clearly test the chain with your CA :

Another possibility is to use the library BouncyCastle to build the certificate chain and validate the trust. The options are clear and errors are easy to understand. In cas of success it will build the chain, otherwise an exception is returned. Sample below :

// rootCerts : collection of CA
// currentCertificate : the one you want to test
var builderParams = new PkixBuilderParameters(rootCerts, 
                        new X509CertStoreSelector { Certificate = currentCertificate });
// crls : The certificate revocation list
builderParams.IsRevocationEnabled = crls.Count != 0;
// validationDate : probably "now"
builderParams.Date = new DateTimeObject(validationDate);

// The indermediate certs are items necessary to create the certificate chain
builderParams.AddStore(X509StoreFactory.Create("Certificate/Collection", new X509CollectionStoreParameters(intermediateCerts)));
builderParams.AddStore(X509StoreFactory.Create("CRL/Collection", new X509CollectionStoreParameters(crls)));

try
{
    PkixCertPathBuilderResult result = builder.Build(builderParams);
    return result.CertPath.Certificates.Cast<X509Certificate>();
    ...
于 2012-03-16T08:28:07.907 回答
1

当 RemoteCertificateValidationCallback、X509Certificate 和 X509Chain 似乎没有给我任何帮助时,我如何仅使用 CA 的公共证书文件而不使用 Windows 证书存储或 WCF 来验证证书是否已由我的特定 CA 签名?

以下代码将避免 Windows 证书存储并验证链。它与 JB 的代码有点不同,尤其是在标志的使用方面。下面的代码不需要AllowUnknownCertificateAuthority(但它确实使用X509RevocationMode.NoCheck,因为我没有 CRL)。

函数的名称无关紧要。下面,VerifyServerCertificate是与课堂上相同的RemoteCertificateValidationCallback回调SslStream。您也可以将它用于ServerCertificateValidationCallbackin ServicePointManager

static bool VerifyServerCertificate(object sender, X509Certificate certificate,
    X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    try
    {
        String CA_FILE = "ca-cert.der";
        X509Certificate2 ca = new X509Certificate2(CA_FILE);

        X509Chain chain2 = new X509Chain();
        chain2.ChainPolicy.ExtraStore.Add(ca);

        // Check all properties
        chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

        // This setup does not have revocation information
        chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;

        // Build the chain
        chain2.Build(new X509Certificate2(certificate));

        // Are there any failures from building the chain?
        if (chain2.ChainStatus.Length == 0)
            return true;

        // If there is a status, verify the status is NoError
        bool result = chain2.ChainStatus[0].Status == X509ChainStatusFlags.NoError;
        Debug.Assert(result == true);

        return result;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }

    return false;
}

我还没有弄清楚如何chain2默认使用这个链(下面),这样就不需要回调了。也就是说,将它安装在 ssl 套接字上,连接将“正常工作”。而且我还没有弄清楚如何安装它以便将其传递给回调。也就是说,我必须为回调的每次调用构建链。我认为这些是 .Net 中的架构缺陷,但我可能会遗漏一些明显的东西。

于 2014-03-28T02:39:03.503 回答