C# 如何根据证书主题验证规范名称(common name,cn)?

C# 如何根据证书主题验证规范名称(common name,cn)?,c#,validation,ssl,x509certificate,C#,Validation,Ssl,X509certificate,在C中,我创建了一个到服务器的SSL连接,如下所示: var hostname = "www.example.com"; var client = new TcpClient(hostname, 443); var sslStream = new SslStream(client.GetStream()); sslStream.AuthenticateAsClient(hostname); 在上述操作完成且未引发异常后,我知道服务器证书已经过验证,主机名与主题充分匹配。可以通过属性sslStr

在C中,我创建了一个到服务器的SSL连接,如下所示:

var hostname = "www.example.com";
var client = new TcpClient(hostname, 443);
var sslStream = new SslStream(client.GetStream());
sslStream.AuthenticateAsClient(hostname);
在上述操作完成且未引发异常后,我知道服务器证书已经过验证,主机名与主题充分匹配。可以通过属性sslStream.RemoteCertificate.subject访问主题,该属性是DN格式的字符串,类似

CN=www.example.com, O=Example Inc, L=New York, S=New York, C=US


出于深奥的原因,我想根据同一证书主题验证另一个字符串和另一个主机名。如何正确验证特定主机名是否与证书的主题匹配?

如果要模拟SSL主题名验证,必须进行一些工作,因为此过程并不简单。以下是在证书中实施受试者姓名验证的指南:

评估X509Certificate2对象的主题备选名称扩展名。如果此扩展不存在,则: 1.1。使用以下正则表达式模式从证书中提取CN属性:“CN=[^,]+”并用所需的名称验证它。由于CN属性可能包含通配符,所以应该使用正则表达式对其进行验证。下面是一个简单通配符类的示例

class Wildcard : Regex {
    public Wildcard(String pattern) : base(WildcardToRegex(pattern)) { }
    public Wildcard(String pattern, RegexOptions options) : base(WildcardToRegex(pattern), options) { }
    public static String WildcardToRegex(String pattern) {
        return "^" + Escape(pattern).Replace("\\*", ".*") + "$";
    }
}
如果提供SAN扩展,请忽略主题字段验证,并以相同方式对DNSName和IPAddress备选名称集合使用地址验证

如果步骤1或2成功,请检查完整的证书链直至根证书,以了解名称约束扩展结束验证:

3.1。如果名称约束定义了排除节,请验证其他名称是否与列表中的任何条目不匹配。否则,此证书不允许使用该名称。不执行步骤3.2

3.2。如果名称约束定义了包含节,请验证其他名称是否与此节中的任何条目匹配。如果Include部分的任何条目中没有其他名称,则此证书不允许使用此名称

遗憾的是,.NET没有表示SAN扩展的本机类,因此您必须编写自己的解码器或使用我自己的.NET扩展库PKI.Core.dll


下面的类表示SAN扩展:

如果您想模拟SSL主题名称验证,您必须做一些工作,因为这个过程不是很简单。以下是在证书中实施受试者姓名验证的指南:

评估X509Certificate2对象的主题备选名称扩展名。如果此扩展不存在,则: 1.1。使用以下正则表达式模式从证书中提取CN属性:“CN=[^,]+”并用所需的名称验证它。由于CN属性可能包含通配符,所以应该使用正则表达式对其进行验证。下面是一个简单通配符类的示例

class Wildcard : Regex {
    public Wildcard(String pattern) : base(WildcardToRegex(pattern)) { }
    public Wildcard(String pattern, RegexOptions options) : base(WildcardToRegex(pattern), options) { }
    public static String WildcardToRegex(String pattern) {
        return "^" + Escape(pattern).Replace("\\*", ".*") + "$";
    }
}
如果提供SAN扩展,请忽略主题字段验证,并以相同方式对DNSName和IPAddress备选名称集合使用地址验证

如果步骤1或2成功,请检查完整的证书链直至根证书,以了解名称约束扩展结束验证:

3.1。如果名称约束定义了排除节,请验证其他名称是否与列表中的任何条目不匹配。否则,此证书不允许使用该名称。不执行步骤3.2

3.2。如果名称约束定义了包含节,请验证其他名称是否与此节中的任何条目匹配。如果Include部分的任何条目中没有其他名称,则此证书不允许使用此名称

遗憾的是,.NET没有表示SAN扩展的本机类,因此您必须编写自己的解码器或使用我自己的.NET扩展库PKI.Core.dll


以下类表示SAN扩展:

以下是相关标准:

特别重要的是:问题是如何解析主题字符串,正确的答案是:一般来说,您不应该这样做。至少从2000年编写RFC2818时开始,解析主题字符串就被弃用,取而代之的是subjectAltName.dNSName

以下是mono实现:在以下文件中搜索checkServerIdentity


我之前将此信息发布为对问题的编辑,并说它不算作答案,因为代码不完整-代码中标记为TODO的部分已弃用。但是现在我更仔细地检查了代码和RFC,发现RFC标准要求使用不推荐的注释,而且TODO注释实际上已经完成了-只是有人忘记删除TODO注释。

以下是相关标准:

特别重要的是:问题是如何解析主题字符串,正确的答案是:一般来说,您不应该这样做。至少从2000年编写RFC2818时开始,解析主题字符串就被弃用,取而代之的是subjectAltName.dNSName

这是mono实现 选项:在以下文件中搜索checkServerIdentity


我之前将此信息发布为对问题的编辑,并说它不算作答案,因为代码不完整-代码中标记为TODO的部分已弃用。但是现在我更仔细地检查了代码和RFC,发现RFC标准要求使用不推荐的注释,而且TODO注释实际上已经完成了-只是很明显有人忘记删除TODO注释。

谢谢,事实上,这个答案可能是意外正确的,但可能有一个30页长的标准在某处正式定义了主题的确切格式。在幕后,我相信他们已经阅读了该标准并进行了相应的编码。我没有访问幕后的源代码,可能会找到这方面的mono源代码,但在搜索几分钟后还没有找到。其他项目-openssl、bouncycastle等,肯定有可读的源代码。。。我不想在这里发明新的解决方案。我想确保我做对了。这不是很常见的任务。我不知道有任何API或工具会检查抽象URL主机名是否与连接上下文之外的主题字符串匹配。因此,您必须像以前一样使用活动连接,或者实现自己的URL->主题验证程序。我知道。如果将SslStream.client使用的方法公开以供一般使用会很酷,但它没有公开,所以我希望我必须重新创建它。这很容易做到这一点,只需编写一些正则表达式代码,以看起来有意义的方式解析字符串。但我想更确定一点,这是正确的。这意味着要么找到一个遵循MS源代码、mono源代码等的示例,要么找到某个地方编写的标准,以便遵循。例如,我只提到了特定主机名和通配符的SSL证书。还存在多个主机名的SSL证书。这些将如何格式化?我不知道。但我知道上面的正则表达式不能处理它们。。。还有其他的可能性吗?我不知道…也就是说,主题字段可能是空的或包含一些条目。Web浏览器只查找SAN扩展以查找适用的名称,名称匹配的执行方式与使用通配符支持的主题字段相同。但是,请注意,“*”字符允许在同一域级别使用多个名称。也就是说,DNSName=*.domain.com对于www.server2.domain.com主机名无效。如果您可以执行证书链,那么您可以跳过名称约束扩展处理。谢谢,事实上,这个答案可能是偶然正确的,但可能有一个30页长的标准在某处正式定义了主题的确切格式。在幕后,我相信他们已经阅读了该标准并进行了相应的编码。我没有访问幕后的源代码,可能会找到这方面的mono源代码,但在搜索几分钟后还没有找到。其他项目-openssl、bouncycastle等,肯定有可读的源代码。。。我不想在这里发明新的解决方案。我想确保我做对了。这不是很常见的任务。我不知道有任何API或工具会检查抽象URL主机名是否与连接上下文之外的主题字符串匹配。因此,您必须像以前一样使用活动连接,或者实现自己的URL->主题验证程序。我知道。如果将SslStream.client使用的方法公开以供一般使用会很酷,但它没有公开,所以我希望我必须重新创建它。这很容易做到这一点,只需编写一些正则表达式代码,以看起来有意义的方式解析字符串。但我想更确定一点,这是正确的。这意味着要么找到一个遵循MS源代码、mono源代码等的示例,要么找到某个地方编写的标准,以便遵循。例如,我只提到了特定主机名和通配符的SSL证书。还存在多个主机名的SSL证书。这些将如何格式化?我不知道。但我知道上面的正则表达式不能处理它们。。。还有其他的可能性吗?我不知道…也就是说,主题字段可能是空的或包含一些条目。Web浏览器只查找SAN扩展以查找适用的名称,名称匹配的执行方式与使用通配符支持的主题字段相同。但是,请注意,“*”字符允许在同一域级别使用多个名称。也就是说,DNSName=*.domain.com对于www.server2.domain.com主机名无效。如果您可以执行证书链,那么您可以跳过名称约束扩展处理。您的另一条评论建议您可能不知道-我无法确定您希望在哪个类中找到.NET CLR实现,但如果它存在,
应该在那里有。@TomW谢谢你!不,我不知道。。。重要的是要注意许可条款的不同——mono中的特定文件是MIT许可的,而MS参考源是MS-RSL,这一限制更为严格。此外,显然,您只能单步浏览某些程序集的.Net引用源,其中不包括System.Net.Security,我无法找到MS引用代码的相关部分。>至少在2000年编写RFC2818时,解析主题字符串就被弃用了,我不同意subjectAltName.dNSName,因为SAN不是强制性的,所以您仍然需要解析Subject字段。没有什么不同意的。它写在标准上。引用自RFC2818:如果存在dNSName类型的subjectAltName扩展名,则必须将其用作标识。否则,必须使用证书的Subject字段中最具体的Common Name字段。虽然通用名称的使用是现有的做法,但不推荐使用,并鼓励认证机构改用dNSName。不推荐并不意味着你不做;这意味着您首先要做一些其他的事情(如果可用),并且只有在没有其他选择的情况下才使用不推荐的方法。RFC2818是2000年编写的,因此,长期以来,假设它已被完全采用是安全的,并且在实践中,您永远不会遇到缺少dNSName的证书,除非您自己以某种方式导致它发生。因此,正如标准所说,首先检查dNSName,然后返回到解析公共名称。虽然这在实践中永远不会发生,但它仍然是您需要做的,以符合标准。您的另一条评论建议您可能不知道-我真的不知道您希望在哪个类中找到.NET CLR实现,但如果它存在,应该在那里提供。@TomW谢谢您!不,我不知道。。。重要的是要注意许可条款的不同——mono中的特定文件是MIT许可的,而MS参考源是MS-RSL,这一限制更为严格。此外,显然,您只能单步浏览某些程序集的.Net引用源,其中不包括System.Net.Security,我无法找到MS引用代码的相关部分。>至少在2000年编写RFC2818时,解析主题字符串就被弃用了,我不同意subjectAltName.dNSName,因为SAN不是强制性的,所以您仍然需要解析Subject字段。没有什么不同意的。它写在标准上。引用自RFC2818:如果存在dNSName类型的subjectAltName扩展名,则必须将其用作标识。否则,必须使用证书的Subject字段中最具体的Common Name字段。虽然通用名称的使用是现有的做法,但不推荐使用,并鼓励认证机构改用dNSName。不推荐并不意味着你不做;这意味着您首先要做一些其他的事情(如果可用),并且只有在没有其他选择的情况下才使用不推荐的方法。RFC2818是2000年编写的,因此,长期以来,假设它已被完全采用是安全的,并且在实践中,您永远不会遇到缺少dNSName的证书,除非您自己以某种方式导致它发生。因此,正如标准所说,首先检查dNSName,然后返回到解析公共名称。虽然这在实践中永远不会发生,但它仍然是您需要做的事情,以符合标准。