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:
- Utilisation / mise à jour-demandes de mise à jour de cache pages
- 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:
- j'ai créé
test-public-key.cer
ouverturewww_example.com.crt
etCopy to file
magicien pour le copier dans un base64 encodé .fichier cer. - j'ai sauvé
certificate code
j'ai reçu de mon fournisseur SSL en tant que fichiertest-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_signature
amp_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&_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 + "&_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.pem
apikey.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.
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
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.