Essayer d'obtenir le certificat SSL à partir d'une réponse dans requests
.
Quel est un bon moyen de faire ça?
requests
_ enveloppe délibérément des trucs de bas niveau comme celui-ci. Normalement, la seule chose que vous voulez faire est de Vérifiez que les certs sont valides . Pour faire ça, juste passer verify=True
. Si vous souhaitez utiliser un paquet cacert non standard, vous pouvez transmettre cela aussi. Par exemple:
resp = requests.get('https://example.com', verify=True, cert=['/path/to/my/ca.crt'])
En outre, requests
est principalement un ensemble d'emballages autour d'autres bibliothèques, principalement urllib3
et le stdlib http.client
(ou, pour 2.x, httplib
) et ssl
.
Parfois, la réponse est juste pour obtenir aux objets de niveau inférieur (par exemple, resp.raw
est le urllib3.response.HTTPResponse
), mais dans de nombreux cas, c'est impossible.
Et c'est l'un de ces cas. Les seuls objets qui jamais voir les certs sont un http.client.HTTPSConnection
(ou un urllib3.connectionpool.VerifiedHTTPSConnection
, mais c'est juste une sous-classe de l'ancien) et un ssl.SSLSocket
et aucun de ceux qui n'existent plus avant le retour de la requête. (Comme le nom connectionpool
implique, l'objet HTTPSConnection
est stocké dans un pool et peut être réutilisé dès que cela sera fait; le SSLSocket
est membre du HTTPSConnection
.)
Donc, vous devez corriger les choses afin que vous puissiez copier les données de la chaîne. Cela peut être aussi simple que ceci:
HTTPResponse = requests.packages.urllib3.response.HTTPResponse
orig_HTTPResponse__init__ = HTTPResponse.__init__
def new_HTTPResponse__init__(self, *args, **kwargs):
orig_HTTPResponse__init__(self, *args, **kwargs)
try:
self.peercert = self._connection.sock.getpeercert()
except AttributeError:
pass
HTTPResponse.__init__ = new_HTTPResponse__init__
HTTPAdapter = requests.adapters.HTTPAdapter
orig_HTTPAdapter_build_response = HTTPAdapter.build_response
def new_HTTPAdapter_build_response(self, request, resp):
response = orig_HTTPAdapter_build_response(self, request, resp)
try:
response.peercert = resp.peercert
except AttributeError:
pass
return response
HTTPAdapter.build_response = new_HTTPAdapter_build_response
C'est non testé, donc pas de garanties; Vous devrez peut-être régler plus que cela.
De plus, le sous-classement et le remplacement seraient probablement plus propres que le skeyPatching (surtout que HTTPAdapter
a été conçu pour être sous-classée).
Ou, encore mieux, forking urllib3
et requests
, modifier votre fourchette et (si vous pensez que cela est légitimement utile) soumettant des demandes de traction en amont.
Quoi qu'il en soit, maintenant, de votre code, vous pouvez le faire:
resp.peercert
Cela vous donnera un dict avec 'subject'
et 'subjectAltName'
clés, comme retourné par pyopenssl.WrappedSocket.getpeercert
. Si vous voulez plutôt plus d'informations sur le certificat, essayez Variante de Christophe Vandeplas de cette réponse qui vous permet d'obtenir un OpenSSL.crypto.X509
objet. Si vous souhaitez obtenir la chaîne de certificats de pairs entière, voir Réponse de Goldenstake .
Bien sûr, vous voudrez peut-être aussi transmettre toutes les informations nécessaires pour vérifier le certificat, mais cela est encore plus facile, car il passe déjà à travers le niveau supérieur.
Merci pour les réponses impressionnantes de tout le monde.
Cela m'a aidé à faire une réponse à cette question:
Veuillez jeter un oeil sur CERT Human: Certificats SSL pour l'homme Pour une réécriture impressionnante de mon https://github.com/neozenith/get-ca Projet par - Lifehackjim .
J'ai archivé le référentiel d'origine maintenant.
#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""
Get Certificates from a request and dump them.
"""
import argparse
import sys
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
"""
Inspired by the answers from this Stackoverflow question:
https://stackoverflow.com/questions/16903528/how-to-get-response-ssl-certificate-from-requests-in-python
What follows is a series of patching the low level libraries in requests.
"""
"""
https://stackoverflow.com/a/47931103/622276
"""
sock_requests = requests.packages.urllib3.contrib.pyopenssl.WrappedSocket
def new_getpeercertchain(self, *args, **kwargs):
x509 = self.connection.get_peer_cert_chain()
return x509
sock_requests.getpeercertchain = new_getpeercertchain
"""
https://stackoverflow.com/a/16904808/622276
"""
HTTPResponse = requests.packages.urllib3.response.HTTPResponse
orig_HTTPResponse__init__ = HTTPResponse.__init__
def new_HTTPResponse__init__(self, *args, **kwargs):
orig_HTTPResponse__init__(self, *args, **kwargs)
try:
self.peercertchain = self._connection.sock.getpeercertchain()
except AttributeError:
pass
HTTPResponse.__init__ = new_HTTPResponse__init__
HTTPAdapter = requests.adapters.HTTPAdapter
orig_HTTPAdapter_build_response = HTTPAdapter.build_response
def new_HTTPAdapter_build_response(self, request, resp):
response = orig_HTTPAdapter_build_response(self, request, resp)
try:
response.peercertchain = resp.peercertchain
except AttributeError:
pass
return response
HTTPAdapter.build_response = new_HTTPAdapter_build_response
"""
Attempt to wrap in a somewhat usable CLI
"""
def cli(args):
parser = argparse.ArgumentParser(description="Request any URL and dump the certificate chain")
parser.add_argument("url", metavar="URL", type=str, nargs=1, help="Valid https URL to be handled by requests")
verify_parser = parser.add_mutually_exclusive_group(required=False)
verify_parser.add_argument("--verify", dest="verify", action="store_true", help="Explicitly set SSL verification")
verify_parser.add_argument(
"--no-verify", dest="verify", action="store_false", help="Explicitly disable SSL verification"
)
parser.set_defaults(verify=True)
return vars(parser.parse_args(args))
def dump_pem(cert, outfile="ca-chain.crt"):
"""Use the CN to dump certificate to PEM format"""
PyOpenSSL = requests.packages.urllib3.contrib.pyopenssl
pem_data = PyOpenSSL.OpenSSL.crypto.dump_certificate(PyOpenSSL.OpenSSL.crypto.FILETYPE_PEM, cert)
issuer = cert.get_issuer().get_components()
print(pem_data.decode("utf-8"))
with open(outfile, "a") as output:
for part in issuer:
output.write(part[0].decode("utf-8"))
output.write("=")
output.write(part[1].decode("utf-8"))
output.write(",\t")
output.write("\n")
output.write(pem_data.decode("utf-8"))
if __name__ == "__main__":
cli_args = cli(sys.argv[1:])
url = cli_args["url"][0]
req = requests.get(url, verify=cli_args["verify"])
for cert in req.peercertchain:
dump_pem(cert)
Pour commencer, Réponse d'Abarnert est très complet
Mais je voudrais ajouter, que dans le cas, vous recherchez la chaîne de peer Cert, vous auriez besoin de patcher encore un autre morceau de code
import requests
sock_requests = requests.packages.urllib3.contrib.pyopenssl.WrappedSocket
def new_getpeercertchain(self,*args, **kwargs):
x509 = self.connection.get_peer_cert_chain()
return x509
sock_requests.getpeercertchain = new_getpeercertchain
après cela, vous pouvez l'appeler de manière très similaire comme la réponse acceptée
HTTPResponse = requests.packages.urllib3.response.HTTPResponse
orig_HTTPResponse__init__ = HTTPResponse.__init__
def new_HTTPResponse__init__(self, *args, **kwargs):
orig_HTTPResponse__init__(self, *args, **kwargs)
try:
self.peercertchain = self._connection.sock.getpeercertchain()
except AttributeError:
pass
HTTPResponse.__init__ = new_HTTPResponse__init__
HTTPAdapter = requests.adapters.HTTPAdapter
orig_HTTPAdapter_build_response = HTTPAdapter.build_response
def new_HTTPAdapter_build_response(self, request, resp):
response = orig_HTTPAdapter_build_response(self, request, resp)
try:
response.peercertchain = resp.peercertchain
except AttributeError:
pass
return response
HTTPAdapter.build_response = new_HTTPAdapter_build_response
tu auras resp.peercertchain
qui contient un Tuple
de OpenSSL.crypto.X509
Objets