ASP.NET:使用文件中的公钥和私钥对 URL 进行签名

标签 asp.net ssl certificate rsa google-amp

我想更新 Google AMP 缓存,因此我需要按照 here 中的说明对 URL 进行签名.

我的主要问题是:我在如何获取证书/ key 以及如何将它们包含在下面的代码中苦苦挣扎。
我只是找不到任何适用于 Windows 和 IIS 的所有涵盖说明。

我一直在阅读这些帖子:

  • Using /update-cache requests to update AMP pages
  • How can I sign a file using RSA and SHA256 with .NET?

  • 我不想使用我的计算机的证书存储,如第二篇文章中所述。我宁愿使用磁盘上的文件作为公钥和私钥。
    从我的生产服务器 IIS,我将我的证书导出到一个 .pfx 文件,然后我使用 this site 底部的说明从中提取私钥和证书。 .
    server.key 包含 -----BEGIN RSA PRIVATE KEY----- ,如果我使用它加载到 privateCert下面代码中的变量抛出错误 Cannot find the requested object.
    我从我的 SSL 提供商那里得到了什么:www_example_com.crt , www_example_com.p7b , certificate code (见下文)。

    我采取的步骤:
  • 我创建了 test-public-key.cer通过打开 www_example.com.crt并使用 Copy to file向导将其复制到 base64 编码的 .cer 文件。
  • 我保存了 certificate code我从 SSL 提供商处收到文件 test-private-key.cer .

  • 当我运行以下代码时,出现错误

    Object reference not set to an instance of an object.



    在线key.FromXmlString(privateCert.PrivateKey.ToXmlString(True))
    Dim publicCert As X509Certificate2 = New X509Certificate2("C:\temp\_certificates\test-public-key.cer")
    Dim privateCert As X509Certificate2 = New X509Certificate2("C:\temp\_certificates\test-private-key.cer")
    
    'Round-trip the key to XML and back, there might be a better way but this works
    Dim key As RSACryptoServiceProvider = New RSACryptoServiceProvider
    key.FromXmlString(privateCert.PrivateKey.ToXmlString(True))
    
    'Create some data to sign
    Dim data() As Byte = System.Text.Encoding.Unicode.GetBytes(signatureUrl)
    
    'Sign the data
    Dim sig() As Byte = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"))
    
    Dim AMPURLSignature As String = EncodeTo64(sig.ToString)
    
    'Lastly, the verification can be done directly with the certificate's public key without need for the reconstruction as we did with the private key:
    key = CType(publicCert.PublicKey.Key, RSACryptoServiceProvider)
    If Not key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig) Then
        Throw New CryptographicException
    End If
    

    EncodeTo64 函数
    Public Shared Function EncodeTo64(ByVal toEncode As String) As String
        Dim toEncodeAsBytes As Byte() = System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode)
        Dim returnValue As String = System.Convert.ToBase64String(toEncodeAsBytes)
        Return returnValue
    End Function
    
    certificate code

    -----BEGIN CERTIFICATE-----
    MIIF (...) DXuJ
    -----END CERTIFICATE-----



    更新 1

    按照 this page 上的导出步骤,我能够生成 mysite.pfx 文件。 .
    在向导中,我确保选择“是,导出私钥”并添加了密码。其余的步骤我一字不差。

    然后我还运行了这些命令:openssl pkcs12 -in mysite.pfx -nocerts -out private-key-VPS.pempenssl pkcs12 -in mysite.pfx -clcerts -nokeys -out certificate-VPS.pem
    我最终得到了 private-key-VPS.pem和一个 certificate-VPS.pem文件

    我知道获取 mysite.pfx 的步骤与 @CodeFuller 描述的略有不同,但到目前为止还好吗?

    然后我添加了代码:
    Dim certificate As X509Certificate2 = New X509Certificate2("C:\temp\_certificates\prodserverV2\mysite.pfx", "mypwd")
    Dim rsa As RSA = certificate.GetRSAPrivateKey()
    Dim data() As Byte = System.Text.Encoding.Unicode.GetBytes(signatureUrl)
    Dim sig() As Byte = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
    
    Dim AMPURLSignature As String = EncodeTo64(sig.ToString)
    

    但是我有 4 个错误:

    GetRSAPrivateKey' is not a member of 'X509Certificate2'.
    'SignData' is not a member of 'RSA'.
    'HashAlgorithmName' is not declared. It may be inaccessible due to its protection level.
    'RSASignaturePadding' is not declared. It may be inaccessible due to its protection level.



    更新 2

    感谢@CodeFuller!我的目标是框架 4.6.1,现在似乎更进了 1 步。我最终得到这样的 URL:https://www-mysite-com.cdn.ampproject.org/update-cache/c/s/www.mysite.com/articles/270/newarticle1/amp?amp_action=flush&_ts=1522939248&_url_signature=U30zdGVtLkJ5uGVbRQ== .我现在如何检查它是否是一个有效的 URL?
    我正在检查 this page 上的“生成 RSA key ”部分,但我很困惑,因为我实际上已经对这些步骤进行了编码?如何检查我现在得到的 URL 是否有效?

    更新 3

    好的,我试过你的新代码。还是得到了URL signature verification error .我试过 /amp我文章的 URL 没有 /amp加入我的网址。两者都会导致相同的 URL 签名验证错误。

    我注意到当我将最终 URL 打印到我的网站时(请参阅下面的代码),该 URL 显示为:
    https://www-toptrouwen-nl.cdn.ampproject.org/update-cache/c/s/www.toptrouwen.nl/artikelen/132/het-uitwisselen-van-de-trouwringen-hoe-voorkom-je-bloopers/amp?amp_action=flush&_ts=1523094395&_url_signature=U3lzdGVrLkn5dGVbXQ==
    注意参数应该在哪里amp_tsamp_url_signature ,他们现在是 _ts_url_signature分别。

    在调用 Google 之前,我尝试通过手动重命名参数来编辑 URL _ts_url_signatureamp_tsamp_url_signature .但我想这会导致签名和实际 URL 之间存在差异。难道我的代码以某种方式搞砸了 &字符,因此当我稍后手动重命名它们时,它总是会导致签名验证?你看到我可以在我的代码中修复什么吗?

    顺便说一句:我尝试更换 &%26在签署 URL 之前的代码隐藏中,但随后我收到了 Google 404 错误:

    The requested URL /update-cache/c/s/www.toptrouwen.nl/artikelen/132/het-uitwisselen-van-de-trouwringen-hoe-voorkom-je-bloopers/amp?amp_action=flush%26amp_ts=1523094395%26amp_url_signature=U3lzdGVrLkJ1dGVbXQ== was not found on this server. That’s all we know.



    我的代码:
    Dim ampBaseUrl As String = "https://www-toptrouwen-nl.cdn.ampproject.org"
    Dim signatureUrl As String = "/update-cache/c/s/www.toptrouwen.nl/artikelen/132/het-uitwisselen-van-de-trouwringen-hoe-voorkom-je-bloopers/amp?amp_action=flush&amp_ts=" + tStamp
    
    Dim rsa As RSA = certificate.GetRSAPrivateKey()
    Dim data() As Byte = System.Text.Encoding.ASCII.GetBytes(signatureUrl)
    Dim sig() As Byte = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
    
    Dim AMPURLSignature As String = EncodeTo64(sig.ToString)
    Dim url As String = ampBaseUrl + signatureUrl + "&amp_url_signature=" + AMPURLSignature
    
    ltStatus.Text = "AMP URL:<a target='_blank' href='" + url + "'>" + url + "</a>"
    

    此外,我确定此页面存在于 Google AMP 缓存中,因为我可以在我的移动设备上的 Google 搜索结果中查看和请求它。

    更新 4

    我想我正在接近并获得一些额外的帮助,请参阅此处:https://github.com/ampproject/amphtml/issues/14483#issuecomment-380549060

    我现在正在尝试使其他人也更容易测试:我现在运行以下命令来获取公钥和私钥,而不是依赖我的 SSL
    openssl genrsa 2048 > private-key.pem
    openssl rsa -in private-key.pem -pubout >public-key.pem
    

    我现在有文件 private-key.pempublic-key.pem
    我要重命名 public-key.pemapikey.pub并将其放在 https://example.com/.well-known/amphtml/apikey.pub

    我想采用@CodeFuller 推荐的最简单的方法并创建一个 .pfx然后我可以将其加载到类型为 X509Certificate2 的变量中的文件.
    当我运行此命令时:openssl pkcs12 -export -out keys.pfx -inkey private-key.pem -in public-key.pem
    我收到错误:unable to load certificates
    但这次我没有 .crt文件,只有一个 public-key.pem .我怎样才能得到一个 .pfx文件?我已经查过 here .

    最佳答案

    I saved certificate code I received from my SSL provider as file test-private-key.cer

    ...

    certificate code

    -----BEGIN CERTIFICATE-----
    MIIF (...) DXuJ
    -----END CERTIFICATE-----



    以某种格式存储的文件
    -----BEGIN CERTIFICATE----- 
    MIIF (...) DXuJ 
    -----END CERTIFICATE-----
    

    是一个基本上包含公钥的证书。它 does not contain 私钥。这就是为什么当你从这样的文件创建 X509Certificate2 的实例时,它的 HasPrivateKey 属性被设置为 False 并且 PrivateKey 返回 Nothing ,并且以下语句预计会抛出 NullReferenceException :
    privateCert.PrivateKey.ToXmlString(True)
    

    为了对数据进行签名,您需要一个私钥。私钥具有以下格式
    -----BEGIN RSA PRIVATE KEY-----
    MIICXQ...B7Bou+
    -----END RSA PRIVATE KEY-----
    

    此类私钥通常存储在 *.key 或 *.pem(隐私增强邮件)文件中。 There is no 从 pem 文件加载 X509Certificate2 实例的内置方法。有很多代码示例可用如何做到这一点,您可以在上面链接的问题中找到它们。然而,最简单的解决方案是创建 pfx 文件(包含私钥和公钥)。然后,您可以使用 X509Certificate2 的相应构造函数轻松加载 pfx。

    pfx 文件的创建是 very easy with SSL tool 。如果 private.key 包含私钥( -----BEGIN RSA PRIVATE KEY----- )并且 public.crt 包含公钥( -----BEGIN CERTIFICATE----- ),您可以使用以下命令创建 pfx 文件:

    openssl pkcs12 -export -out keys.pfx -inkey private.key -in public.crt



    您将被要求输入密码。当您将 key 加载到 X509Certificate2 时,也将使用此密码:
    Dim certificate As X509Certificate2 = New X509Certificate2("d:\CodeFuller\_days\2018.04.05\keys.pfx", "Password here")
    

    现在 HasPrivateKey 属性设置为 True 并且 PrivateKey 返回 RSACryptoServiceProvider 的实例。

    更新

    关于此代码:
    'Round-trip the key to XML and back, there might be a better way but this works
    Dim key As RSACryptoServiceProvider = New
    RSACryptoServiceProvider
    key.FromXmlString(privateCert.PrivateKey.ToXmlString(True))
    
    RSACryptoServiceProvider 的实例实际上存储在 certificate.PrivateKey 中,因此您可以避免使用上述代码并将其替换为:
    Dim provider As RSACryptoServiceProvider = certificate.PrivateKey
    

    但是,您当前的 SignData() 调用将不起作用:
    Dim sig() As Byte = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"))
    

    这将引发以下异常:

    System.Security.Cryptography.CryptographicException: 'Invalid algorithm specified.'



    根本原因是 RSACryptoServiceProvider does not support SHA256 。这就是为什么我建议通过以下方式用 RSACng 替换它:
    Dim rsa As RSA = certificate.GetRSAPrivateKey()
    Dim data() As Byte = System.Text.Encoding.Unicode.GetBytes(signatureUrl)
    Dim sig() As Byte = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
    

    方法 GetRSAPrivateKey 仅从 .NET Framework 4.6 开始添加到 X509Certificate2 类,因此如果出现以下错误,请考虑升级:

    error BC30456: 'GetRSAPrivateKey' is not a member of 'X509Certificate2'



    更新 2 (关于 URL 验证)

    您引用的 The page 包含用于验证签名的 openssl 命令:

    openssl dgst -sha256 -signature signature.bin -verify public-key.pem url.txt



    但是,在您的情况下,这只是一次完整性检查,因为您刚刚使用有效程序生成了签名。所以回答你的问题:

    How can I check whether the URL I now end up with is valid?



    最好的检查就是使用签名 URL 向 AMP Cache 发送请求并检查响应。我之前没有使用过 AMP Cache,但我相信如果签名无效,它会响应一些 HTTP 错误。

    UPDATE 3 (关于签名验证失败)

    Update AMP Content page 包含以下用于签署 URL 的命令行:

    echo -n >url.txt '/update-cache/c/s/example.com/article?amp_action=flush&amp_ts=1484941817' cat url.txt | openssl dgst -sha256 -sign private-key.pem >signature.bin



    我已将此命令构建的结果签名与我的答案中的代码计算出的签名进行了比较。原来他们是有区别的。我研究了可能的根本原因,发现问题是由我们获取 URL 字节的方式引起的。目前是:
    Dim data() As Byte = System.Text.Encoding.Unicode.GetBytes(signatureUrl)
    但是,我们应该对以 ASCII 表示的 URL 进行签名。因此,将上面的行替换为:
    Dim data() As Byte = System.Text.Encoding.ASCII.GetBytes(signatureUrl)
    现在来自 openssl 实用程序和上面代码的两个签名都匹配。如果修复后您仍然从 Google AMP 获得 URL signature verification error,那么问题将出在为签名传递的输入 URL 上。

    UPDATE 4 (从私钥和公钥获取 PFX)

    生成私钥:

    openssl genrsa 2048 > private-key.pem



    生成公钥:

    openssl rsa -in private-key.pem -pubout > public-key.pem



    创建证书签名请求:

    openssl req -new -key private-key.pem -out certificate.csr



    创建证书:

    openssl x509 -req -days 365 -in certificate.csr -signkey private-key.pem -out public.crt



    您将在此处被要求提供一些证书字段,例如国家名称、组织名称等。您使用哪个值并不重要,因为您需要此证书用于测试目的。

    创建 pfx 文件:

    openssl pkcs12 -export -out keys.pfx -inkey private-key.pem -in public.crt

    关于ASP.NET:使用文件中的公钥和私钥对 URL 进行签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49477892/

    相关文章:

    http - 实际web开发中http协议(protocol)还剩下什么?

    ssl - 证书颁发机构与存储的公钥

    ios - 有人能告诉我什么是 iOS 中的移动设备证书身份验证吗?使用它的目的是什么?

    asp.net - ASP.NET Core SignalR 是否向后兼容 ASP.NET SignalR

    c# - 如何将 control.ClientId 传递给 ASP.NET 中的 OnClick 函数?

    python - iOS 推送通知 (APNs) 通过 GAE,SSL 握手失败

    ssl - 基于ip的ssl证书

    c# - 无法在 cookieless 模式下设置 FormsAuthenicationTicket.UserData

    asp.net - 将字符串数组作为参数传递给 asp.net mvc webapi 方法

    java - 面对 javax.net.ssl.SSLHandshakeException : Received fatal alert: bad_certificate issue while implementing SSL two way authentication