ASP.NET:使用文件

时间:2018-03-25 16:05:04

标签: asp.net ssl certificate rsa google-amp

我想更新Google AMP缓存,因此我需要按照here所述签署一个网址。

我的主要问题:我在如何获取我的证书/密钥以及如何将它们包含在我的下面的代码中大挣扎。 我无法找到适用于Windows和IIS的所有覆盖说明。

我一直在阅读这些帖子:

  1. Using /update-cache requests to update AMP pages
  2. How can I sign a file using RSA and SHA256 with .NET?
  3. 我不想使用我在第二篇文章中描述的计算机证书存储区。我宁愿在磁盘上使用公钥和私钥。 在我的生产服务器IIS中,我将证书导出到.pfx文件,然后使用this site底部的说明从中提取私钥和证书。 server.key包含-----BEGIN RSA PRIVATE KEY-----,如果我使用它加载到下面代码中的privateCert变量,则抛出错误Cannot find the requested object.

    我从SSL提供商处得到的信息: www_example_com.crtwww_example_com.p7bcertificate code(见下文)。

    我采取的步骤:

    1. 我通过打开test-public-key.cer并使用www_example.com.crt向导将其复制到base64编码的.cer文件来创建Copy to file
    2. 我保存certificate code我从SSL提供商处收到的文件为test-private-key.cer
    3. 当我运行以下代码时,我收到错误

        

      对象引用未设置为对象的实例。

      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

        

      -----开始证书-----
        MIIF(...)DXuJ
        -----结束证书-----

      更新1

      我可以按照this page上的导出步骤生成一个mysite.pfx文件。 在向导中,我确保选择"是,导出私钥"我添加了一个密码。其余的步骤我逐字逐句。

      然后我也运行了这些命令:
      openssl pkcs12 -in mysite.pfx -nocerts -out private-key-VPS.pem
      penssl pkcs12 -in mysite.pfx -clcerts -nokeys -out certificate-VPS.pem

      我最终得到了private-key-VPS.pemcertificate-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'不是' X509Certificate2'的成员   ' SignData'不是' RSA'的成员   ' HashAlgorithmName'没有宣布。由于其防护等级,它可能无法访问   ' RSASignaturePadding'没有宣布。由于其保护级别,它可能无法访问。

      更新2

      谢谢@CodeFuller!我针对框架4.6.1,现在似乎更进了一步。我最终得到了这样的网址: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? 我正在检查部分"生成RSA密钥"在this page,但我很困惑,因为我实际上已经编写了这些步骤?如何检查我现在最终使用的URL是否有效?

      更新3

      好的,我尝试了你的新代码。仍然得到URL signature verification error。我尝试使用文章的/amp网址,但网址中没有/amp部分。两者都会导致相同的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

      我通过手动将参数_ts_url_signature重命名为amp_tsamp_url_signature,在我拨打Google电话之前编辑了网址。但我想这会导致签名和实际URL之间的差异。可能是因为我的代码以某种方式破坏了&字符,因此当我稍后手动重命名它时,它总是会导致签名验证?你看到我能在我的代码中修复什么吗?

      顺便说一句:在签署网址之前,我尝试在代码隐藏中用&替换%26,但后来我收到了Google 404错误:

        

      请求的网址/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 ==在此服务器上找不到。这就是我们所知道的。

      我的代码:

      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.pem重命名为apikey.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

2 个答案:

答案 0 :(得分:4)

  

我保存了从SSL提供商处收到的证书代码作为文件   测试私人key.cer

     

...

     

证书代码

     

-----开始证书-----
  MIIF(...)DXuJ
  -----结束证书-----

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

是基本上包含公钥的证书。它是does not contain私钥。这就是为什么当您从此类文件创建X509Certificate2的实例时,它的HasPrivateKey属性设置为FalsePrivateKey返回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

系统会要求您输入密码。将密钥加载到X509Certificate2

时,也会使用此密码
Dim certificate As X509Certificate2 = New X509Certificate2("d:\CodeFuller\_days\2018.04.05\keys.pfx", "Password here")

现在HasPrivateKey属性设置为TruePrivateKey返回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:&#39;无效   指定的算法。&#39;

根本原因是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)

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

  

错误BC30456:&#39; GetRSAPrivateKey&#39;不是会员   &#39; X509Certificate2&#39;

更新2 (关于网址验证)

您引用的

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

  

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

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

  

如何检查我现在最终使用的网址是否有效?

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

更新3 (关于签名验证失败)

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

  

echo -n&gt; url.txt   &#39; /update-cache/c/s/example.com/article amp_action =平齐&安培; amp_ts = 1484941817&#39;   cat url.txt | openssl dgst -sha256 -sign private-key.pem&gt; 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,那么问题将在于为签名传递的输入网址。

更新4 (从私钥和公钥获取PFX)

生成私钥:

  

openssl genrsa 2048&gt;私人key.pem

生成公钥:

  

openssl rsa -in private-key.pem -pubout&gt;公共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

答案 1 :(得分:2)

这是我用于使用磁盘证书对字符串进行签名的代码。我将其修改为在网址中使用。也许这会对你有所帮助。

public string signString(string originalString)
{
    //load the certificate from disk
    X509Certificate2 cert = loadCertificateFromFile("myCert.pfx", "myPassword");

    //get the associated CSP and private key
    using (RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PrivateKey)
    using (SHA1Managed sha1 = new SHA1Managed())
    {
        //hash the data
        UnicodeEncoding encoding = new UnicodeEncoding();

        byte[] data = encoding.GetBytes(originalString);

        byte[] hash = sha1.ComputeHash(data);

        //sign the hash
        byte[] signed = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));

        //convert to base64 and encode for use in an URL
        return Server.UrlEncode(Convert.ToBase64String(signed));

        //or return a regular string
        //return Encoding.Default.GetString(signed);
    }
}


public bool verifyString(string originalString, string signedString)
{
    //convert back from base64 and url encoded string
    byte[] signature = Convert.FromBase64String(Server.UrlDecode(signedString));

    //or a regular string
    //byte[] signature = Encoding.Default.GetBytes(signedString);

    //load the certificate from disk
    X509Certificate2 cert = loadCertificateFromFile("myCert.pfx", "myPassword");

    //get the associated CSP and private key
    using (RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PrivateKey)
    using (SHA1Managed sha1 = new SHA1Managed())
    {
        //hash the data
        UnicodeEncoding encoding = new UnicodeEncoding();

        byte[] data = encoding.GetBytes(originalString);

        byte[] hash = sha1.ComputeHash(data);

        //sign the hash
        byte[] signed = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));

        //verify the signature
        return csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), signature);
    }
}


public X509Certificate2 loadCertificateFromFile(string path, string password)
{
    //get the absolute path
    string absolutePath = Server.MapPath(path);

    //for non-website users, use this
    //string absolutePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path);

    if (!string.IsNullOrEmpty(password))
    {
        return new X509Certificate2(absolutePath, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
    }
    else
    {
        return new X509Certificate2(absolutePath);
    }
}

用法:

string originalString = "This is a test string";
string signedString = signString(originalString);
bool stringIsCorrect = verifyString(originalString, signedString);

我现在看到你使用VB。您可以使用它来转换代码http://www.carlosag.net/tools/codetranslator。然而,之后可能需要进行一些修改,因为我不知道转换器的准确性。