如何在ASP.NET Core中使用证书

目录

介绍

为什么我们需要证书?

创建证书

如何在.NET代码中使用证书

结论

附录


  • 从GitHub下载源代码

介绍

最近,对您的Web资源使用HTTPS协议是所有相对较大的Web项目的强制性要求。该技术基于所谓的证书。以前,您必须付费才能获得Web服务器的证书。但是现在,我们提供了Let's Encrypt等服务,您可以在其中免费获取证书。这就是为什么价格不再是不使用HTTPS的理由。

在最简单的情况下,证书允许您在客户端和服务器之间建立受保护的连接。但这并不是它的全部能力。例如,我在Pluralsight上看到了一个名为Microservices Security的在线课程。那里提到了一件事,称为相互传输层安全性。它不仅允许客户端确保它与正确的服务器交互,还允许服务器对客户端进行身份验证。

这就是为什么开发人员必须知道如何使用证书。正是出于这个原因,我决定写这篇文章。我希望它成为一个可以找到有关证书的基本知识的地方。我不认为专家可以在这里找到有趣的东西,但我希望它对初学者和想要更新知识的人有用。

为什么我们需要证书?

在我们开始使用证书之前,我们需要了解我们为什么需要它们。让我们看几个人。传统上,我们称他们为AliceBob。他们需要相互交流。但这样做的唯一方法是通过公共通信渠道交换消息:

如何在ASP.NET Core中使用证书_第1张图片

所有图标均由Flaticon的Vitaly Gorbachev创建

不幸的是,由于频道是公开的,任何人都可以阅读甚至更改AliceBob发送给彼此的消息:

如何在ASP.NET Core中使用证书_第2张图片

这种情况被称为中间人

AliceBob如何保护自己免受这种危险?加密来拯救。最古老和最广泛的加密系统是具有对称密钥的系统。在这种情况下,AliceBob必须拥有完全相同的密钥(这就是它们被称为对称的原因),而其他任何人都不知道这些密钥。然后,使用任何对称加密系统,他们可以通过公共通信渠道交换消息,而不必担心黑客将能够读取或更改消息。

但是黑客仍然可以重复他之前看到的一条或多条消息。在某些情况下,这可能会造成严重的危险(想象一下,黑客可以重复请求将资金从一个帐户转移到另一个帐户)。但是这个问题在所有现代通信系统中都得到了有效的解决。(例如,可以给每条消息添加一个序号,如果接收端消息中的序号不等于期望的序号,则丢弃这样的消息)。

如何在ASP.NET Core中使用证书_第3张图片

但是让我们回到我们的AliceBob。看来他们的问题已经解决了。但这种情况并非如此。问题是他们如何才能获得相同的加密密钥,以便其他人无法获得它们。毕竟,他们只能通过公共渠道进行交流。通过此通道传递密钥也将简单地将其传递给黑客。在这种情况下,他将能够解密和更改AliceBob的消息。

我们应该做什么?这就是非对称加密或公钥加密的用武之地。其主要思想如下。假设Alice想向Bob发送一条消息。现在Bob生成的不是一个,而是两个密钥——公钥和私钥。公钥不是秘密。Bob可以把它交给任何想与他交谈的人。但他将私钥保密,不向任何人展示,包括Alice。诀窍在于,如果消息使用公钥加密,则只能使用私钥解密。相反,使用私钥加密的消息只能使用公钥解密。

现在很清楚AliceBob应该如何行动了。他们每个人都生成自己的公钥和私钥。然后他们通过通信渠道交换他们的公钥。由于公钥不是秘密,它们可以通过公共渠道传输。但是AliceBob将他们的私钥保密。假设Bob想要将他的消息发送给Alice。他用她的公钥对其进行加密,并通过通道发送加密消息。只有拥有私钥的人才能解密此消息(这意味着只有Alice可以这样做)。黑客无法解密。

如何在ASP.NET Core中使用证书_第4张图片

事实上,一切都有点复杂。你看,公钥加密比对称加密慢得多。因此,以这种方式加密大量数据是不方便的。这就是为什么当Bob想与Alice交谈时,他会执行以下操作。他为对称加密系统生成一个新密钥(通常称为会话密钥)。然后他用Alice的公钥加密这个会话密钥并将其发送给她。现在AliceBob有一个其他人都不知道的对称密钥。从现在开始,他们可以使用快速对称加密算法。

看来我们的问题已经解决了。但这并不是那么简单。控制通讯渠道的黑客有话要告诉我们。问题又出在密钥分配机制上,但现在这些是公钥。让我们看看会发生什么。

假设Alice生成了一对公钥和私钥。现在她想将她的公钥提供给Bob。她通过通信通道发送此密钥。此时,黑客截获了这个密钥,不允许Bob得到它。相反,黑客会生成他自己的一对公钥和私钥。然后他将他的公钥发送给Bob,说这是Alice的公钥。黑客为自己保留Alice的真实公钥:

如何在ASP.NET Core中使用证书_第5张图片

是的,现在我们有许多不同的密钥。让我们看看它是如何工作的。假设Bob想向Alice发送一条消息。他用公钥加密它,在他看来,公钥属于Alice。但实际上,这是黑客的关密钥。黑客拦截了这条消息,不允许Alice接收。由于消息是用黑客的公钥加密的,他可以用他的私钥解密它,阅读它并在他认为合适的时候更改它。之后,他用Alice的真实公钥对其进行加密(请记住,黑客将她的公钥随身携带)并将其发送给她。Alice用她的私钥解密它,没有任何问题。所以Alice收到Bob的消息,并不知道它已被黑客阅读并可能被修改过。

我们能做些什么来避免这种情况呢?在这里,我们接近证书。想象一下,Alice不仅通过公共渠道分发她的公钥,而且还通过一个带有标签的密钥分发该密钥属于Alice。此标签还包含AliceBob信任的一些受人尊敬的人的签名:

如何在ASP.NET Core中使用证书_第6张图片

假设密钥和标签是一。标签不能从一把密钥上取下并放在另一把密钥上。在这种情况下,如果黑客无法伪造签名,他也无法伪造密钥。如果Bob收到一个带有标签的密钥,上面写着这是Alice的密钥,并且有一个受信任的人的签名,那么他可以确定这是Alice的密钥,而不是其他人的。

您可以假设证书是带有此类标签的密钥。但它在数字世界中如何运作?

在数字世界中,一切都可以表示为一系列位(零和一)。这同样适用于密钥。我们应该怎么做才能为这样的位序列创建数字签名?此签名必须具有以下属性:

  • 它应该很短。假设您想为电影文件创建数字签名。这样的文件可能会占用磁盘上数十GB的空间。如果我们的签名大小相同,则很难将其与文件一起传输。
  • 伪造它应该是不可能的(或在实践中非常困难)。否则,黑客仍然可以强迫Bob接受他自己的密钥而不是Alice的密钥。

我们如何创建这样的签名?我们可以这样做。首先,我们将为我们的位序列计算所谓的散列。您将位序列发送到某个函数的输入(称为散列函数),该函数返回另一个位序列,但已经很短了。这个输出序列称为散列。所有现代哈希函数都具有以下属性:

  • 对于任意长度的输入序列,它们会生成相同长度的散列。通常,这个长度不超过几十个字节。请记住,我们的签名必须简短。散列的这个属性使得在签名中使用起来很方便。
  • 如果您只知道哈希,您将无法获得为其创建此哈希的输入序列。这意味着您无法从哈希中恢复输入序列。
  • 如果您有某个位序列的散列,则不能指定具有相同散列的另一个位序列。事实上,有很多不同的文件长度为1GB。但是对于其中任何一个,您都可以计算出32个字节的哈希值。长度为32字节的不同序列远少于长度为1GB的不同文件。这意味着必须有两个长度为1GB且具有相同哈希的不同文件。然而,如果您知道其中一个文件及其哈希值,您将无法指定另一个给出相同哈希值的文件。

但是关于哈希就足够了。不幸的是,散列本身并不适合作为签名的角色。是的,它很短。但任何人都可以计算出来。黑客可以为他的公钥计算一个哈希值,没有什么能阻止他这样做。我们怎样才能使哈希值抗伪造?在这里,公钥加密再次发挥作用。

请记住,我说过AliceBob应该信任密钥标签上的签名。假设AliceBob信任非常重要人物的签名。非常重要的人如何签署密钥?为此,他生成了自己的一对公钥和私钥。他将他的公钥传递给AliceBob,并将私钥保密。当他需要对Alice的公钥进行签名时,他会这样做。首先,他计算Alice的密钥的哈希值,然后用他的私钥对其进行加密。使用非常重要的人(通常称为证书颁发机构)的私钥加密的哈希是签名。由于没有人知道非常重要人物的私钥,所以没有人可以伪造他的签名。

现在我们了解了如何创建签名。但我们还需要知道如何验证它,如何确保签名不是伪造的。假设Bob有一些密钥。标签说这是Alice的公钥。此外,还有非常重要的人的签名。但是如何检查呢?首先,Bob计算接收到的公钥的哈希值。请记住,每个人都可以做到。然后Bob使用非常重要的人的公钥解密签名。正如我之前所说,签名只是一个加密的哈希。之后,Bob比较两个哈希值:一个是他计算出来的,另一个是他从解密的签名中收到的。如果它们相等,那么一切都很好,Bob可以确定这是Alice的密钥。但是如果哈希值不同,那么密钥就不能被信任。由于黑客无法创建正确的签名,所以他不能强迫Bob信任错误的密钥。

因此,证书只是它的密钥和标签。但是,在实践中,证书中添加了许多附加信息:

  • 谁拥有密钥。在我们的例子中,这是Alice
  • 密钥从什么日期到什么日期有效。
  • 谁签署了密钥。在我们的例子中,这是非常重要的人。此信息是必要的,因为实际上,不同的证书颁发机构可以对密钥进行签名。
  • 使用什么算法来计算哈希并创建签名。
  • ...以及任何其他信息。

为所有这些数据创建哈希和签名,因此黑客无法伪造任何数据。

但我们严格的方案仍有差距。我希望你已经明白我的意思了。AliceBob如何获得非常重要的人的公钥?如果黑客可以用自己的密钥替换这个密钥,我们的整个系统就会被破坏。

好吧,当然,非常重要的人的公钥是随证书分发的,但现在由非常重要的人签名。嗯……但是非常非常重要的人的公钥是怎么分配的呢?当然有证书。嗯,你知道……一路下来都有证书

但笑话放在一边。确实,Alice的证书可以用非常重要的人的证书来签名。他的证书可以用非常非常重要的人的证书签名。这称为信任链。但这个链条并不是无穷无尽的。它通常以根证书结尾。这个证书没有被任何人签名,更准确地说,它是由自己签名的(自签名证书)。通常,根证书属于非常可靠的公司,他们的工作是用他们的根证书签署其他证书。

以前,公司拿钱来签署证书。但是现在我们有像错误!超链接引用无效。这样的服务,它是免费的。我想很多大公司已经意识到,与其拥有很多保护不善的网站,每个网站都可以作为攻击这些大公司的平台,不如免费提供证书,让互联网成为一个更安全的空间。类似的事情发生在防病毒软件上。二十年前,我们不得不为他们买单。现在人们可以很容易地找到一个免费的高质量防病毒软件来安装在个人计算机上。

但是,让我们回到我们的证书。我们还有最后一个问题。为什么我们信任根证书?是什么阻止了黑客替换它们?原因是他们如何到达AliceBob的计算机。你看,它们不是通过开放的通信渠道交付的,而是与操作系统一起交付的。最近,一些浏览器开始安装自己的一组可信证书。

就这样。这就是我想说的关于证书的全部内容。有很多有趣的事情与它们相关,例如证书的弃用和撤销机制,但我们不会在这里讨论。让我们继续讨论实际的事情。

创建证书

我希望我设法让你相信证书是一件重要且必要的事情。作为开发人员,您决定是时候学习如何使用它们了。如果您从Visual Studio创建ASP.NET Core项目,您只需选中Configure for HTTPS复选框,所有必要的基础设施都会为您准备好:

但我想向您展示如何创建自己的证书来测试您的应用程序。首先,我将创建一个自签名证书,一个自己签名的证书。接下来,我将向您展示如何在您的系统中安装此证书,以便它开始信任该证书。

让我们开始吧。我们需要的一切都已经在.NET Core中了。让我们创建一个控制台应用程序并使用一些有用的命名空间:

using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

现在我们需要创建一对公钥和私钥。公钥的安全分发是证书的工作:

// Generate private-public key pair
var rsaKey = RSA.Create(2048);

然后我们需要创建一个证书请求:

// Describe certificate
string subject = "CN=localhost";

// Create certificate request
var certificateRequest = new CertificateRequest(
    subject,
    rsaKey,
    HashAlgorithmName.SHA256,
    RSASignaturePadding.Pkcs1
);

证书请求包含有关为谁颁发此证书的信息(subject变量)。如果我们希望证书被www.example.com上的Web服务器使用,那么变量subject应该等于CN=www.example.com。在我们的例子中,我们想在localhost上测试我们的Web服务器。这就是subject变量的值等于CN=localhost的原因。

接下来,我们将我们的密钥对传递给证书请求,并指定应该用于计算哈希和签名的算法。

现在我们需要提供一些关于我们需要哪个证书的附加信息。让我们表明我们不想用这个签署其他证书:

certificateRequest.CertificateExtensions.Add(
    new X509BasicConstraintsExtension(
        certificateAuthority: false,
        hasPathLengthConstraint: false,
        pathLengthConstraint: 0,
        critical: true
    )
);

然后有一些有趣的事情。你看,证书只是一个加密密钥存储。这些密钥可用于各种目的。我们已经看到它们可以用于数字签名和会话密钥加密。但它还有其他用途。现在我们必须指定如何使用我们的证书:

certificateRequest.CertificateExtensions.Add(
    new X509KeyUsageExtension(
        keyUsages:
            X509KeyUsageFlags.DigitalSignature
            | X509KeyUsageFlags.KeyEncipherment,
        critical: false
    )
);

您可以自己查看X509KeyUsageFlags枚举,其中列出了证书的各个使用领域。

接下来,我们提供一个公钥用于识别:

certificateRequest.CertificateExtensions.Add(
    new X509SubjectKeyIdentifierExtension(
        key: certificateRequest.PublicKey,
        critical: false
    )
);

这里有一点黑魔法。正如我已经告诉你的,如果你想使用证书来保护www.example.com站点,它的subject字段必须包含CN=www.example.com。但这对于Chrome浏览器来说还不够。他们要求Subject Alternative Name字段必须包DNS Name=www.example.com。在我们的例子中,它必须包含DNS Name=localhost。否则Chrome将不信任这样的证书。不幸的是,我还没有找到一种方便的方法来为我们的证书设置主题备用名称字段的值。但是以下代码将其设置为DNS Name=localhost

certificateRequest.CertificateExtensions.Add(
    new X509Extension(
        new AsnEncodedData(
            "Subject Alternative Name",
            new byte[] { 48, 11, 130, 9, 108, 111, 99, 97, 108, 104, 111, 115, 116 }
        ),
        false
    )
);

就是这样。我们的证书请求已准备就绪。现在我们可以创建证书本身:

var expireAt = DateTimeOffset.Now.AddYears(5);
var certificate = certificateRequest.CreateSelfSigned(DateTimeOffset.Now, expireAt);

在这里,我们说证书从现在起有效期为五年。

现在我们有了证书。但它目前只存在于计算机的内存中。为了能够在我们的系统中安装它,我们需要将它写入PFX格式的文件中。但这里有一个障碍。我们要获取的文件必须同时包含公钥和私钥,因为服务器必须同时执行加密和解密。但出于安全原因,我们的证书不能用于导出私钥。我们可以创建一个准备导出的证书,如下所示:

// Export certificate with private key
var exportableCertificate = new X509Certificate2(
    certificate.Export(X509ContentType.Cert),
    (string)null,
    X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet
).CopyWithPrivateKey(rsaKey);

为方便起见,我们可以添加描述:

exportableCertificate.FriendlyName = 
    "Ivan Yakimov Test-only Certificate For Client Authorization";

现在我们可以将证书导出到文件中。由于此文件还包含私钥,因此使用密码保护它是合理的。在这种情况下,即使文件被盗,犯罪分子也无法使用:

// Create password for certificate protection
var passwordForCertificateProtection = new SecureString();
foreach (var @char in "p@ssw0rd")
{
    passwordForCertificateProtection.AppendChar(@char);
}

// Export certificate to a file.
File.WriteAllBytes(
    "certificateForServerAuthorization.pfx",
    exportableCertificate.Export(
        X509ContentType.Pfx,
        passwordForCertificateProtection
    )
);

所以,我们有一个证书文件可以用来保护Web服务器。但您也可以创建证书来验证此服务器的客户端。创建过程与服务器证书几乎相同,但主题字段可以包含任何内容,我们不再需要主题备用名称字段:

// Generate private-public key pair
var rsaKey = RSA.Create(2048);

// Describe certificate
string subject = "CN=Ivan Yakimov";

// Create certificate request
var certificateRequest = new CertificateRequest(
    subject,
    rsaKey,
    HashAlgorithmName.SHA256,
    RSASignaturePadding.Pkcs1
);

certificateRequest.CertificateExtensions.Add(
    new X509BasicConstraintsExtension(
        certificateAuthority: false,
        hasPathLengthConstraint: false,
        pathLengthConstraint: 0,
        critical: true
    )
);

certificateRequest.CertificateExtensions.Add(
    new X509KeyUsageExtension(
        keyUsages:
            X509KeyUsageFlags.DigitalSignature
            | X509KeyUsageFlags.KeyEncipherment,
        critical: false
    )
);

certificateRequest.CertificateExtensions.Add(
    new X509SubjectKeyIdentifierExtension(
        key: certificateRequest.PublicKey,
        critical: false
    )
);

var expireAt = DateTimeOffset.Now.AddYears(5);

var certificate = certificateRequest.CreateSelfSigned(DateTimeOffset.Now, expireAt);

// Export certificate with private key
var exportableCertificate = new X509Certificate2(
    certificate.Export(X509ContentType.Cert),
    (string)null,
    X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet
).CopyWithPrivateKey(rsaKey);

exportableCertificate.FriendlyName = 
    "Ivan Yakimov Test-only Certificate For Client Authorization";

// Create password for certificate protection
var passwordForCertificateProtection = new SecureString();
foreach (var @char in "p@ssw0rd")
{
    passwordForCertificateProtection.AppendChar(@char);
}

// Export certificate to a file.
File.WriteAllBytes(
    "certificateForClientAuthorization.pfx",
    exportableCertificate.Export(
        X509ContentType.Pfx,
        passwordForCertificateProtection
    )
);

现在我们可以将我们创建的证书安装到系统中。要在Windows中执行此操作,请双击PFX证书文件。向导窗口打开。指定您只想为当前用户安装证书,而不是为整个机器安装证书:

如何在ASP.NET Core中使用证书_第7张图片

在下一个截图上上,您可以指定证书文件的路径。保持原样:

如何在ASP.NET Core中使用证书_第8张图片

在下一个截图上,输入用于保护证书文件的密码:

如何在ASP.NET Core中使用证书_第9张图片

然后指定您要在受信任的根证书颁发机构中安装您的证书:

如何在ASP.NET Core中使用证书_第10张图片

还记得我们之前是如何讨论证书信任链的吗?此受信任的根证书颁发机构存储库存储系统信任的这些最终(根)证书,无需额外检查。

证书导入配置到此结束。然后您只能单击下一步完成确定

现在我们的证书存在于Trusted Root Certification Authorities存储中。您可以通过单击控制面板中的管理用户证书链接来打开它:

如何在ASP.NET Core中使用证书_第11张图片

这是我们的证书的样子:

如何在ASP.NET Core中使用证书_第12张图片

客户端认证的证书也可以同样的方式安装。

在继续在.NET代码中使用这些证书之前,我想向您展示另一种创建自签名证书的方法。如果您不想编写证书创建程序,但您有PowerShell,则可以使用它创建证书。

这是生成证书以保护服务器的代码:

$certificate = New-SelfSignedCertificate `
    -Subject localhost `
    -DnsName localhost `
    -KeyAlgorithm RSA `
    -KeyLength 2048 `
    -NotBefore (Get-Date) `
    -NotAfter (Get-Date).AddYears(5) `
    -FriendlyName "Ivan Yakimov Test-only Certificate For Server Authorization" `
    -HashAlgorithm SHA256 `
    -KeyUsage DigitalSignature, KeyEncipherment, DataEncipherment `
    -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1")

$pfxPassword = ConvertTo-SecureString `
    -String "p@ssw0rd" `
    -Force `
    -AsPlainText

Export-PfxCertificate `
    -Cert $certificate `
    -FilePath "certificateForServerAuthorization.pfx" `
    -Password $pfxPassword

New-SelfSignedCertificateExport-PfxCertificate命令来自pki模块。我希望到现在为止,你已经可以理解这里各个参数的含义了。

这是为客户端身份验证创建证书的代码:

$certificate = New-SelfSignedCertificate `
      -Type Custom `
      -Subject "Ivan Yakimov" `
      -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2") `
      -FriendlyName "Ivan Yakimov Test-only Certificate For Client Authorization" `
      -KeyUsage DigitalSignature `
      -KeyAlgorithm RSA `
      -KeyLength 2048

$pfxPassword = ConvertTo-SecureString `
    -String "p@ssw0rd" `
    -Force `
    -AsPlainText

Export-PfxCertificate `
    -Cert $certificate `
    -FilePath "certificateForClientAuthorization.pfx" `
    -Password $pfxPassword

现在让我们看看如何使用这些证书。

如何在.NET代码中使用证书

因此,我们有一个用ASP.NET Core编写的Web服务器。我们想用我们的证书来保护它。首先,我们需要在我们服务器的代码中获取这个证书。有两种方法可以做到这一点。

第一个选项是从PFX文件中获取证书。如果您有一个已安装在受信任证书存储中的证书文件,则可以使用此选项。在这种情况下,您可以获得如下证书:

var certificate = new X509Certificate2(
    "certificateForServerAuthorization.pfx",
    "p@ssw0rd"
);

这里的certificateForServerAuthorization.pfx是证书文件的路径,p@ssw0rd是你用来保护它的密码。

但是您可能并不总是可以访问证书文件。在这种情况下,您可以直接从存储中获取证书:

var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var certificate = store.Certificates.OfType()
    .First(c => c.FriendlyName == "Ivan Yakimov Test-only Certificate For Server Authorization");

StoreLocation.CurrentUser值意味着我们要使用当前用户的证书存储,而不是整个计算机。该StoreName.Root值意味着,我们必须在Trusted Root Certification Authorities存储中查找证书。在这里,为简单起见,我正在按名称查找证书,但您可以指定任何合适的标准。

现在我们有了证书。让我们的服务器使用它。为此,我们需要更改Program.cs文件的代码:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);
        var certificate = store.Certificates.OfType()
            .First(c => c.FriendlyName == 
            "Ivan Yakimov Test-only Certificate For Server Authorization");

        return Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder
                    .UseKestrel(options =>
                    {
                        options.Listen(System.Net.IPAddress.Loopback, 44321, listenOptions =>
                        {
                            var connectionOptions = new HttpsConnectionAdapterOptions();
                            connectionOptions.ServerCertificate = certificate;

                            listenOptions.UseHttps(connectionOptions);
                        });
                    })
                    .UseStartup();
            });
    }
}

如您所见,所有的魔法都发生在UseKestrel方法内部。在这里,我们指定要使用的端口和要申请的证书。

现在浏览器认为我们的网站受到保护:

如何在ASP.NET Core中使用证书_第13张图片

但我们并不总是通过浏览器使用Web服务器。有时,我们需要通过代码联系他。然后HttpClient来救援:

var client = new HttpClient()
{
    BaseAddress = new Uri("https://localhost:44321")
};

var result = await client.GetAsync("data");

var content = await result.Content.ReadAsStringAsync();

Console.WriteLine(content);

实际上,标准HttpClient对服务器证书进行验证,如果无法验证其真实性,则不会建立连接。但是如果我们想做一些额外的检查呢?例如,您可能想要检查谁签署了服务器证书。或者您想检查此证书的一些非标准字段。这是可以做到的。我们只需要定义系统执行标准证书验证后将调用的方法:

var handler = new HttpClientHandler()
{
    ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => {
        if (errors != SslPolicyErrors.None) return false;

        return true;
    }
};

var client = new HttpClient(handler)
{
    BaseAddress = new Uri("https://localhost:44321")
};

您将此方法分配给HttpClientHandler实例的ServerCertificateCustomValidationCallback属性。实例必须传递给HttpClient的构造函数。

让我们仔细看看这种验证方法。正如我之前所说,它是在之后调用的,而不是代替标准检查。这个检查的结果可以从这个方法(errors)的最后一个参数中得到。如果该值不等于SslPolicyErrors.No,则标准验证失败,您不能信任这样的证书。此方法还允许您获取以下信息:

  • 请求(request)
  • 服务器证书(certificate)
  • 此证书的信任链(chain)。如果您对此信息感兴趣,可以在这里找到标准检查失败的详细原因。

所以,现在我们知道如何用证书保护我们的服务器了。但是证书也可以用来对客户端进行身份验证。在这种情况下,服务器将只处理来自那些提供正确证书的客户端的请求。如果证书通过标准验证,则认为证书是正确的,并且还满足服务器要求的任何附加条件。

让我们看看如何使服务器需要来自客户端的证书。为此,您只需要进行少量代码更改:

return Host.CreateDefaultBuilder(args)
    .UseSerilog()
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder
            .UseKestrel(options =>
            {
                options.Listen(System.Net.IPAddress.Loopback, 44321, listenOptions =>
                {
                    var connectionOptions = new HttpsConnectionAdapterOptions();
                    connectionOptions.ServerCertificate = certificate;

                    connectionOptions.ClientCertificateMode = 
                                      ClientCertificateMode.RequireCertificate;
                    connectionOptions.ClientCertificateValidation = 
                                           (certificate, chain, errors) =>
                    {
                        if (errors != SslPolicyErrors.None) return false;

                        // Here is your code...

                        return true;
                    };

                    listenOptions.UseHttps(connectionOptions);
                });
            })
            .UseStartup();
    });

如您所见,我们只额外设置了HttpsConnectionAdapterOptions对象的两个属性。使用该ClientCertificateMode属性,我们确定客户端证书是强制性的,并且使用该ClientCertificateValidation属性,我们设置我们的自定义函数以进行额外的证书验证。

如果您在浏览器中打开这样的站点,它会询问您要使用哪个客户端证书:

唯一要做的就是向HttpClient提供一个客户端证书。 您可以获得证书,就像您为服务器所做的那样。其他变化很小:

var handler = new HttpClientHandler()
{
    ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => {
        if (errors != SslPolicyErrors.None) return false;

        // Here is your code...

        return true;
    }
};

handler.ClientCertificates.Add(certificate);

var client = new HttpClient(handler)
{
    BaseAddress = new Uri("https://localhost:44321")
};

您只需将证书添加到HttpClientHandler对象的ClientCertificates集合中。

结论

至此,我们的文章就告一段落了。它很长。我认为它是一个单一的地方,将来我将能够更新我关于证书及其使用的知识。

附录

在我的工作中,我使用了以下材料:

  • 使用HTTPS、自签名证书和ASP.NET Core进行本地开发
  • .Net Core内部的X.509
  • 所有图标均由Flaticon的Vitaly Gorbachev创建

本文的源代码可以在GitHub上找到

https://www.codeproject.com/Articles/5315010/How-to-Use-Certificates-in-ASP-NET-Core

你可能感兴趣的:(ASP.NET,CORE,ASP.NET,Core)