Подтвердить что ты не робот

ASP.NET: вывести URL-адрес с открытыми и закрытыми ключами из файлов

Я хочу обновить кеш Google AMP, поэтому мне нужно подписать URL-адрес, как описано здесь.

Моя основная проблема: я изо всех сил борюсь с тем, как мне получить свои сертификаты/ключи и как их включить в мой код ниже. Я просто не могу найти никаких инструкций по покрытию для Windows и IIS.

Я читал эти сообщения:

  1. Использование /update-cache запросов для обновления страниц AMP
  2. Как я могу подписать файл с помощью RSA и SHA256 с.NET?

Я не хочу использовать хранилище сертификатов компьютеров, как описано во втором сообщении. Я бы предпочел использовать файлы на диске для открытых и закрытых ключей. На моем производственном сервере IIS я экспортировал свой сертификат в файл.pfx, из которого я извлек секретный ключ и сертификат, используя инструкции в нижней части этого сайта. Сервер.key содержит -----BEGIN RSA PRIVATE KEY-----, который, если я использую это для загрузки в переменную privateCert в коде ниже, вызывает ошибку Cannot find the requested object.

То, что я получил от моего поставщика SSL: www_example_com.crt, www_example_com.p7b, certificate code (см. Ниже).

Шаги, которые я сделал:

  1. Я создал test-public-key.cer, открыв www_example.com.crt и используя мастер Copy to file чтобы скопировать его в кодированный base64 файл.cer.
  2. Я сохранил certificate code полученный мной от поставщика SSL, в качестве файла test-private-key.cer.

Когда я запускаю следующий код, я получаю ошибку

В экземпляре объекта не задана ссылка на объект.

on line 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 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

Мне удалось создать файл 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.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 'не является членом' X509Certificate2 '.
"SignData" не является членом "RSA".
'HashAlgorithmName' не объявляется. Он может быть недоступен из-за его уровня защиты.
"RSASignaturePadding" не объявляется. Он может быть недоступен из-за его уровня защиты.

ОБНОВЛЕНИЕ 2

Спасибо @CodeFuller! Я нацелился на рамки 4.6.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? Я проверяю раздел "Генерировать ключ RSA" на этой странице, но я смущен, так как я на самом деле уже просто закодировал эти шаги или нет? Как я могу проверить, действителен ли URL-адрес, который я сейчас закончил?

ОБНОВЛЕНИЕ 3

Хорошо, я попробовал ваш новый код. Все еще получите URL signature verification error. Я попытался с URL-адресом /amp моей статьи и без части /amp в моем URL-адресе. Оба результата приводят к той же ошибке проверки подлинности подписи 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_ts и amp_url_signature, теперь они _ts и _url_signature соответственно.

Я пробовал редактировать URL-адрес, прежде чем совершать звонок в Google, вручную переименовывая параметры _ts и _url_signature в amp_ts и amp_url_signature. Но я предполагаю, что это приведет к различию между сигнатурой и фактическим URL. Может быть, что-то мой код подрывает символ & и поэтому, когда я переименую их вручную позже, это всегда приводит к проверке подписи? Вы видите, что я могу исправить в своем коде?

BTW: Я попытался заменить & %26 на код перед тем, как подписать URL-адрес, но затем я получаю ошибку Google 404:

Запрошенный 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% 26 amp_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.pem и public-key.pem

Я переименую public-key.pem в apikey.pub и 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? Я уже проверил здесь.

4b9b3361

Ответ 1

Я сохранил код сертификата, полученный мной от поставщика SSL, в качестве файла test-private-key.cer

...

код сертификата

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

Файл, хранящийся в формате

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

это сертификат, который в основном содержит открытый ключ. Он не содержит секретный ключ. Поэтому, когда вы создаете экземпляр X509Certificate2 из такого файла, его свойство HasPrivateKey имеет значение False и PrivateKey возвращает Nothing, а следующий оператор, как ожидается, NullReferenceException:

privateCert.PrivateKey.ToXmlString(True)

Чтобы подписать данные, вам нужен секретный ключ. Частные ключи имеют следующий формат

-----BEGIN RSA PRIVATE KEY-----
MIICXQ...B7Bou+
-----END RSA PRIVATE KEY-----

Такие закрытые ключи обычно хранятся в файлах *.key или *.pem(Privacy Enhanced Mail). Не существует встроенного способа -in для загрузки экземпляра X509Certificate2 из файла pem. Есть много примеров кода, доступных, как это сделать, вы найдете их в связанном выше вопросе. Однако самым простым решением будет создание файла pfx (содержащего как частные, так и открытые ключи). Затем вы можете легко загрузить pfx с соответствующим конструктором X509Certificate2.

Создание файла pfx очень просто с помощью SSL-инструмента. Если 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 имеет значение 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: "Указан неверный алгоритм".

Основная причина заключается в том, что RSACryptoServiceProvider не поддерживает 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 был добавлен в класс X509Certificate2 только с.NET Framework 4.6, поэтому рассмотрим возможность обновления, если вы получите следующую ошибку:

ошибка BC30456: "GetRSAPrivateKey" не является членом "X509Certificate2"

ОБНОВЛЕНИЕ 2 (относительно проверки URL)

Страница, на которую вы ссылались, содержит команду openssl для проверки подписи:

openssl dgst -sha256 -сигнал подписи.bin -verify public-key.pem url.txt

Однако в вашем случае это будет просто проверка на работоспособность, потому что вы только что создали подпись с действительной процедурой. Поэтому, отвечая на ваш вопрос:

Как я могу проверить, действителен ли URL-адрес, который я сейчас закончил?

Лучшая проверка - это просто отправить запрос в AMP-кэш с подписанным URL-адресом и проверить ответ. Я раньше не использовал AMP-кеш, но считаю, что он ответит некоторой ошибкой HTTP, если подпись недопустима.

ОБНОВЛЕНИЕ 3 (относительно проверки подписи подписи)

Обновление страницы содержимого AMP содержит следующую командную строку для подписания 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)

Однако мы должны подписать URL-адрес, представленный в ASCII. Поэтому замените выше строку следующим образом:

Dim data() As Byte = System.Text.Encoding.ASCII.GetBytes(signatureUrl)

Теперь совпадают оба подписи, от утилиты openssl и вышеприведенного кода. Если после исправления вы по-прежнему получаете URL signature verification error от Google AMP, тогда проблема будет связана с URL-адресом ввода, который будет подписан для подписания.

ОБНОВЛЕНИЕ 4 (Получение PFX из частных и открытых ключей)

Создание секретного ключа:

openssl genrsa 2048> private-key.pem

Создание открытого ключа:

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

Создать запрос подписи сертификата:

openssl req -n ew -key private-key.pem -out certificate.csr

Создать сертификат:

openssl x509 -req -дней 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

Ответ 2

Это код, который я использую для подписания строки с сертификатом с диска. Я изменил его для использования в URL-адресе. Может быть, это поможет вам.

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. Однако впоследствии может потребоваться некоторое изменение, поскольку я не знаю точности преобразователя.