如何在 Web 服务中获取来自客户端发送的 X509Certificate?

11

显然,我在之前的帖子中提出了错误的问题。我有一个受X.509证书保护的Web服务,作为安全的网站运行(https://..)。我想使用由公司根CA签发的客户端机器证书(也是X.509)来验证客户端机器是否被授权使用服务。为此,我需要检查该证书并查找某些标识特征,将其与存储在数据库中的值进行匹配(可能是Thumbprint吗?)。

以下是我用于从本地证书存储中获取证书的代码(直接从http://msdn.microsoft.com/en-us/magazine/cc163454.aspx中引用):

 public static class SecurityCertificate
{
    private static X509Certificate2 _certificate = null;

    public static X509Certificate2 Certificate
    {
        get { return _certificate; }
    }

    public static bool LoadCertificate()
    {
        // get thumbprint from app.config
        string thumbPrint = Properties.Settings.Default.Thumbprint;
        if ( string.IsNullOrEmpty( thumbPrint ) )
        {
            // if no thumbprint on file, user must select certificate to use
            _certificate = PickCertificate( StoreLocation.LocalMachine, StoreName.My );
            if ( null != _certificate )
            {
                // show certificate details dialog
                X509Certificate2UI.DisplayCertificate( _certificate );
                Properties.Settings.Default.Thumbprint = _certificate.Thumbprint;
                Properties.Settings.Default.Save();
            }
        }
        else
        {
            _certificate = FindCertificate( StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, thumbPrint );
        }

        if ( null == _certificate )
        {
            MessageBox.Show( "You must have a valid machine certificate to use STS." );
            return false;
        }

        return true;
    }

    private static X509Certificate2 PickCertificate( StoreLocation location, StoreName name )
    {
        X509Store store = new X509Store( name, location );
        try
        {
            // create and open store for read-only access
            store.Open( OpenFlags.ReadOnly );

            X509Certificate2Collection coll = store.Certificates.Find( X509FindType.FindByIssuerName, STSClientConstants.NBCCA, true );
            if ( 0 == coll.Count )
            {
                MessageBox.Show( "No valid machine certificate found - please contact tech support." );
                return null;
            }

            // pick a certificate from the store
            coll = null;
            while ( null == coll || 0 == coll.Count )
            {
                coll = X509Certificate2UI.SelectFromCollection(
                        store.Certificates, "Local Machine Certificates",
                        "Select one", X509SelectionFlag.SingleSelection );
            }

            // return first certificate found
            return coll[ 0 ];
        }
        // always close the store
        finally { store.Close(); }
    }

    private static X509Certificate2 FindCertificate( StoreLocation location, StoreName name, X509FindType findType, string findValue )
    {
        X509Store store = new X509Store( name, location );
        try
        {
            // create and open store for read-only access
            store.Open( OpenFlags.ReadOnly );

            // search store
            X509Certificate2Collection col = store.Certificates.Find( findType, findValue, true );

            // return first certificate found
            return col[ 0 ];
        }
        // always close the store
        finally { store.Close(); }
    }

然后,我这样将证书附加到出站流中:

public static class ServiceDataAccess
{    
    private static STSWebService _stsWebService = new STSWebService();

    public static DataSet GetData(Dictionary<string,string> DictParam, string action)
    {
        // add the machine certificate here, the first web service call made by the program (only called once)
        _stsWebService.ClientCertificates.Add( SecurityCertificate.Certificate );
        // rest of web service call here...
    }
}

我的问题是 -- 在web服务代码中,我该如何"获取"证书?大多数我遇到的涵盖自定义验证的示例代码段都包含一个GetCertificate()调用,似乎假定这一部分非常容易,每个人都应该知道如何做?

我的主类继承自WebService,因此我可以使用Context.Request.ClientCertificate来获取证书,但那是一个HttpClientCertificate,而不是X509Certificate2。HttpContext给我同样的结果。其他方法都使用Web配置代码来调用预定义的验证代码,没有任何线索可以调用自定义的C#方法进行验证。


如果您没有HttpContext,请查看https://dev59.com/2Wsz5IYBdhLWcg3w-89v?lq=1的答案。 - Michael Freidgeim
2个回答

14

我记得之前做过类似的事情,虽然已经有一段时间了,但是你是否尝试在你的 Web 服务中使用这个:

X509Certificate2 cert = new X509Certificate2(Context.Request.ClientCertificate.Certificate);

好的!现在我终于可以着手解决问题的核心——找出可以与数据库中存储的值匹配的价值是什么了... - DaveN59
1
如果您将数据库视为可信的权威机构,那么使用Thumbprint是可以的。我曾经参与过采用这种方法的应用程序开发。另一方面,如果您需要一个分布式信任模型(使用CA证书颁发机构),那么您需要进行额外的检查(例如验证证书链)。 - dso
我想我可能行动过早了。尝试访问Thumbprint时会抛出异常'System.Security.Cryptography.CryptographicException'--所以,即使http证书现在是X509Certificate2类型,它是否真的是从客户端发送的相同证书,还是我只是创建了一个看起来有点像它的新兽? - DaveN59
1
好的,深入挖掘后我发现异常信息是“m_safeCertContext是无效句柄”。查找该消息时,可能是由于尝试在“薄型”VS Web服务器(调试模式)中运行而不是IIS引起的。回到起点!嘿,如果这很容易,他们就不会付钱让我们做了,对吧? - DaveN59
我认为你不能使用Visual Studio Web服务器调试SSL(除非自从我上次在VS 2005中使用它以来有所改变)。你真的应该设置自己使用IIS进行调试。 - dso

2
关于如何将证书与用户关联起来,假设已经验证了证书的真实性(将其追溯到可信任的根证书并且未被吊销),那么您需要将证书声明的身份与用户绑定。您可以使用主题DN的LDAP字符串形式并查找(cn=用户名,ou=部门...)以确定本地ID。这在用户重新生成其密钥/证书(例如由于卡片丢失或证书自然过期)时是有弹性的。这依赖于两个CA不会向两个不同的人发放具有相同主题名称的两个证书的事实。
如果证书是由MS CA颁发的,则可能在其中有一个UPN,它实际上是域登录名。
另外,如果您想将用户的身份与实际证书绑定,则通常的方法是存储颁发者名称和证书序列号。CA必须为每个证书发行唯一的序列号。请注意,根据CA,序列号可能很大。但请注意,使用此方法意味着必须每次重新发行用户证书时更新数据库中的证书详细信息。

1
非常棒的信息!如果我仍在这个项目上工作,这正是我需要修复第一次实现中出现问题所需的信息。 然而,由于我现在已经转到了另一家公司,承担着不同的职责,我将把这留给其他人来练习...感谢反馈! - DaveN59

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接