ASP.NET: Sign URL avec les clés publiques et privées des fichiers

je veux mettre à jour Google AMP cache, donc je dois signer une URL comme décrit ici.

mon principal problème: je me bats massivement avec la façon dont je devrais obtenir mes certificats / clés et comment les inclure dans mon code ci-dessous. Je ne trouve pas toutes les instructions pour couvrir les fenêtres et IIS.

j'ai été la lecture de ces posts:

  1. Utilisation / mise à jour-demandes de mise à jour de cache pages
  2. Comment puis-je signer un fichier en utilisant RSA et SHA256 avec .NET?

je ne veux pas utiliser mon ordinateur magasin de certificats comme décrit dans le second post. Je préfère utiliser des fichiers sur disque pour les clés publiques et privées. De mon serveur de production IIS, j'ai exporté mon certificat vers un.fichier pfx, à partir duquel j'ai ensuite extrait la clé privée et le certificat en utilisant les instructions au bas de ce site. Serveur.clé contient -----BEGIN RSA PRIVATE KEY-----, qui si je l'utilise pour charger dans le privateCert variable dans le code ci-dessous lance erreur Cannot find the requested object.

ce que j'ai obtenu de mon fournisseur SSL: www_example_com.crt,www_example_com.p7b, le certificate code (voir ci-dessous).

les Étapes que j'ai prises:

  1. j'ai créé test-public-key.cer ouverture www_example.com.crt et Copy to file magicien pour le copier dans un base64 encodé .fichier cer.
  2. j'ai sauvé certificate code j'ai reçu de mon fournisseur SSL en tant que fichier test-private-key.cer.

quand j'exécute le code suivant j'obtiens l'erreur

référence D'objet non définie à une instance d'un objet.

à la ligne key.FromXmlString(privateCert.PrivateKey.ToXmlString(True))

Dim publicCert As X509Certificate2 = New X509Certificate2("C:temp_certificatestest-public-key.cer")
Dim privateCert As X509Certificate2 = New X509Certificate2("C:temp_certificatestest-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

fonction 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

-----CERTIFICAT DE DÉBUT - - - - -

MIIF (...) DXuJ

----CERTIFICAT DE FIN D'-----

mise à jour 1

j'ai pu générer un mysite.pfx file en suivant les étapes d'exportation sur cette page. Dans l'assistant, je me suis assuré de sélectionner "Oui, exporter la clé privée" et j'ai ajouté un mot de passe. Le reste des étapes que j'ai suivies mot pour mot.

j'ai aussi exécuté ces commandes:

openssl pkcs12 -in mysite.pfx -nocerts -out private-key-VPS.pem

penssl pkcs12 -in mysite.pfx -clcerts -nokeys -out certificate-VPS.pem

j'ai fini avec private-key-VPS.pem et certificate-VPS.pem fichiers

je suis conscient des étapes à suivre la monsite.pfx sont légèrement différents de ce que @CodeFuller décrit, mais jusqu'à présent si bon?

j'ai ensuite ajouté le code:

Dim certificate As X509Certificate2 = New X509Certificate2("C:temp_certificatesprodserverV2mysite.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)

Mais là, je reçois 4 erreurs:

GetRSAPrivateKey "n'est pas membre du "X509Certificate2".

"SignData" n'est pas membre de "RSA".

"HashAlgorithmName" n'est pas déclaré. Il peut être inaccessible en raison de son niveau de protection.

"RSASignaturePadding" n'est pas annoncé. Il peut être inaccessible en raison de son niveau de protection.

UPDATE 2

Merci @CodeFuller! J'ai ciblé le cadre 4.6.1 et semble maintenant être 1 Pas plus loin. Je me retrouve avec une URL comme ceci: 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==. Comment puis-je maintenant vérifier si C'est une URL valide? Je vérifie la section "Générer la clé RSA" sur cette page, mais je suis confus, puisque j'ai déjà codé ces étapes ou pas? Comment puis-je vérifier si L'URL que je termine maintenant est-il valide?

UPDATE 3

Ok, j'ai essayé votre nouveau code. Toujours obtenir l' URL signature verification error. J'ai essayé avec les deux /amp URL de mon article et sans le /amp partie de mon URL. Les deux donnent lieu à la même erreur de vérification de la signature URL.

j'ai remarqué que lorsque j'imprime L'URL finale de mon site web( voir code ci-dessous), L'URL indique:

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==

Notez que les paramètres doivent être amp_ts et amp_url_signature, ils sont maintenant _ts et _url_signature respectivement.

j'ai essayé de modifier L'URL avant de faire L'appel à Google en renommant manuellement les paramètres _ts et _url_signatureamp_ts et amp_url_signature. Mais je suppose qu'il en résulterait une différence entre la signature et L'URL réelle. Se pourrait-il que d'une façon ou d'une autre mon code bâille le & caractère et donc quand je les renomme manuellement plus tard, il en résulte toujours une vérification de signature? Vous voyez ce que je puisse les corriger en mon code?

BTW: j'ai essayé de remplacer &%26 dans le code-behind, avant la signature de l'URL mais puis-je obtenir un Google 404 erreur:

L'URL demandée/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== n'a pas été trouvé sur ce serveur. C'est tout ce que nous savons.

mon code:

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>"

en outre, je suis sûr que cette page existe dans Google AMP cache, puisque je peux le voir et le demander dans les résultats de recherche de Google sur mon appareil mobile.

UPDATE 4

je me rapproche je pense et j'ai aussi besoin d'aide supplémentaire, voir ici: https://github.com/ampproject/amphtml/issues/14483#issuecomment-380549060

ce que j'essaie maintenant de rendre plus facile pour les autres de tester aussi: au lieu de selon mon SSL j'ai maintenant couru les commandes suivantes pour obtenir une clé publique et privée

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

j'ai maintenant les fichiers private-key.pem et public-key.pem

je vais renommer public-key.pemapikey.pub et de les placer sur https://example.com/.well-known/amphtml/apikey.pub

je veux suivre l'approche la plus facile recommandée par @CodeFuller et créer un .pfx fichier que je peux ensuite charger dans une variable de type X509Certificate2. Quand je lance cette commande:

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

j'obtiens l'erreur: unable to load certificates

Mais cette fois, je n'ai pas de .crt fichier, seulement un public-key.pem. Comment puis-je obtenir un .pfx fichier? J'ai déjà vérifié!--189-->ici.

13
demandé sur Flo 2018-03-25 19:05:04

2 réponses

j'ai sauvegardé le code de certificat que j'ai reçu de mon fournisseur SSL sous forme de fichier test-clé privée.cer

...

code de certificat

-----CERTIFICAT DE DÉBUT - - - - -

MIIF (...) DXuJ

----CERTIFICAT DE FIN D'-----

Le fichier stocké dans un format

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

est un certificat qui contient une clé publique. ne contient pas d' privé clé. C'est pourquoi lorsque vous créez une instance de X509Certificate2 à partir de ce fichier, il est HasPrivateKey propriété est définie à False et PrivateKey retourne Nothing, et à la suite de la déclaration attendue jette NullReferenceException:

privateCert.PrivateKey.ToXmlString(True)

pour signer les données, vous avez besoin d'une clé privée. Les clés privées ont le format suivant

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

ces clés privées sont habituellement stockées dans *.clé ou *.fichiers pem (Privacy Enhanced Mail). Il n'y a pas de construit de manière à charger l'instance de X509Certificate2 dans le fichier pem. Il y a beaucoup d'exemples de code disponible de comment le faire, vous les trouverez dans la question ci-dessus. Cependant, la solution la plus simple sera de créer un fichier pfx (contenant les clés privées et publiques). Alors vous pouvez facilement charger pfx avec le constructeur correspondant de X509Certificate2.

la création du fichier pfx est très facile avec L'outil SSL. Si private.key contient une clé privée (-----BEGIN RSA PRIVATE KEY----- et public.crt contient une clé publique (-----BEGIN CERTIFICATE-----), vous pourriez créer le fichier pfx avec la commande suivante:

openssl pkcs12-export -clés.pfx -inkey privé.clé -en public.crt

il vous sera demandé d'entrer le mot de passe. Ce mot de passe sera également utilisé lorsque vous chargez la touche X509Certificate2:

Dim certificate As X509Certificate2 = New X509Certificate2("d:\CodeFuller\_days18.04.05\keys.pfx", "Password here")

HasPrivateKey propriété est définie à True et PrivateKey renvoie l'instance de RSACryptoServiceProvider.

UPDATE

au Sujet de cette code:

'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))

L'instance de RSACryptoServiceProvider est en fait stocké dans certificate.PrivateKey donc vous pouvez éviter le code ci-dessus et le remplacer par:

Dim provider As RSACryptoServiceProvider = certificate.PrivateKey

Toutefois, votre courant SignData() appel ne fonctionnera pas:

Dim sig() As Byte = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"))

Cela permettra de jeter de l'exception suivante:

Système.Sécurité.Cryptographie.CryptographicException: "Invalide algorithme spécifié.'

La cause est que RSACryptoServiceProvider ne supporte pas SHA256. C'est pourquoi je suggère de le remplacer par RSACng de la façon suivante:

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)

Méthode GetRSAPrivateKey a été ajouté à X509Certificate2 classe seulement depuis .NET Framework 4.6, donc envisager la mise à niveau si vous obtenez l'erreur suivante:

erreur BC30456: 'GetRSAPrivateKey' n'est pas membre de "X509Certificate2"

UPDATE 2 (concernant la validation des URLs)

La page vous avez fait référence contient openssl commande de vérification de la signature:

openssl dgst-sha256-signature signature.bin -vérifier la clé publique.PEM url.txt

Toutefois, dans votre cas, ce sera juste un test de cohérence, parce que vous venez de générer la signature avec une procédure valide. Afin de répondre à votre question:

Comment puis-je vérifier si L'URL que j'obtiens est valide?

le meilleur chèque est juste pour envoyer demande à L'AMP Cache avec L'URL signée et vérifier la réponse. Je n'ai jamais utilisé de Cache D'AMP, mais je crois qu'il va réagir avec une erreur HTTP si la signature est invalide.

UPDATE 3 (concernant l'échec de la vérification de la signature)

mise à Jour de l'AMPLI Contenu de la page contient la ligne de commande suivante pour signer L'URL:

echo-n > url.txt '/mise à jour de la mémoire cache/c/s/exemple.com/article?amp_action=flush&_ts=1484941817' cat url.txt | openssl dgst-sha256 -signer une clé privé.pem >signature.bin

j'ai comparé la signature de résultat construite par cette commande avec la signature calculée par le code de ma réponse. Il s'est avéré qu'ils diffèrent. J'ai recherché la cause racine possible et j'ai trouvé que le problème est causé par la façon dont nous obtenons des octets D'URL. Actuellement c'est:

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

Cependant nous devons signer L'URL représentée en ASCII. Remplacez donc la ligne ci-dessus par:

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

maintenant les deux signatures, de l'utilitaire openssl et du code ci-dessus, correspondent. Si, après le correctif, vous obtenez toujours URL signature verification error à partir de Google AMPLI, alors le problème sera avec l'URL d'entrée transmis pour signature.

UPDATE 4 (PFX de clés privées et publiques)

Générer clé privée:

openssl genrsa 2048 > clé privée.pem

Générer la clé publique:

openssl rsa-in à clé privée.pem-pubout > clé publique.pem

Créer une demande de signature de certificat:

openssl req -new-key clé privée.certificat de pem-out.la rse

Créer un certificat:

openssl x509-req-days 365-in certificat.rse -signkey clé privée.pem-out public.crt

on vous demandera ici quelques champs de certificat, par exemple Nom du Pays, nom de l'organisation, etc. Peu importe les valeurs que vous utilisez, puisque vous avez besoin de ce certificat à des fins de test.

Créer un fichier pfx:

openssl pkcs12-export -clés.clé privée pfx-inkey.pem en public.crt

4
répondu CodeFuller 2018-04-12 10:09:53

C'est le code que j'utilise pour signer une chaîne de caractères avec un certificat du disque. Je l'ai modifié pour l'utiliser dans une url. Il sera peut-être vous aider.

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);
    }
}

Utilisation:

string originalString = "This is a test string";
string signedString = signString(originalString);
bool stringIsCorrect = verifyString(originalString, signedString);
http://www.carlosag.net/tools/codetranslator. Cependant, il faudra peut-être y apporter des modifications par la suite, car je ne connais pas la précision du convertisseur.

2
répondu VDWWD 2018-04-05 08:22:18