Requests: Spécifier le mot de passe pour le certificat côté client SSL

Créé le 3 sept. 2013  ·  121Commentaires  ·  Source: psf/requests

Pour autant que je sache, il n'est actuellement pas possible de spécifier le mot de passe du certificat côté client que vous utilisez pour l'authentification.
C'est un peu un problème car vous voulez généralement toujours protéger par mot de passe votre fichier .pem qui contient la clé privée. openssl ne vous permettra même pas d'en créer un sans mot de passe.

Documentation Planned

Commentaire le plus utile

@botondus Je pense avoir trouvé un moyen plus simple d'y parvenir avec la bibliothèque de requêtes. Je documente cela pour d'autres personnes confrontées au problème.

Je suppose que vous avez un certificat .p12 et une phrase secrète pour la clé.

Générez un certificat et une clé privée.

// Generate the certificate file.
openssl pkcs12 -in /path/to/p12cert -nokeys -out certificate.pem
// Generate private key with passpharse, First enter the password provided with the key and then an arbitrary PEM password //(say: 1234) 
openssl pkcs12 -in /path/to/p12cert -nocerts -out privkey.pem

Eh bien, nous n'avons pas encore terminé et nous devons générer la clé qui ne nécessite pas le mot de passe PEM à chaque fois qu'elle doit parler au serveur.

Générer une clé sans mot de passe.

// Running this command will prompt for the pem password(1234), on providing which we will obtain the plainkey.pem
openssl rsa -in privkey.pem -out plainkey.pem

Maintenant, vous aurez certificate.pem et plainkey.pem , les deux fichiers requis pour parler à l'API à l'aide de requêtes.

Voici un exemple de requête utilisant ces certificats et ces clés.

import requests
url = 'https://exampleurl.com'
headers = {
            'header1': '1214141414',
            'header2': 'adad-1223-122'
          }
response = requests.get(url, headers=headers, cert=('~/certificate.pem', '~/plainkey.pem'), verify=True)
print response.json()

J'espère que cela t'aides:

cc @kennethreitz @Lukasa @sigmavirus24

Tous les 121 commentaires

Quelque chose comme:

requests.get('https://kennethreitz.com', cert='server.pem', cert_pw='my_password')

À peu près sûr, vous êtes censé utiliser le cert pour cela : cert=('server.pem', 'my_password')

@sigmavirus24
Le tuple est pour (certificate, key) . Actuellement, il n'y a pas de support pour les fichiers de clés cryptés.
La stdlib n'a pris en charge que ceux de la version 3.3.

Heh, @t-8ch, vous avez accidentellement lié à un fichier sur votre FS local. ;) Corriger le lien .

Tout à fait raison @t-8ch. C'est pourquoi je ne devrais jamais répondre aux problèmes depuis le bus. :/

Donc, le consensus actuel est que nous ne soutenons pas cela. Combien de travail est susceptible d'être d'ajouter la prise en charge dans les versions non-3.3 de Python ?

Serait-il difficile de lancer une erreur sur cette condition ? Je viens de rencontrer ce problème idiot et il m'a fallu deux heures pour le comprendre, ce serait bien s'il renvoyait une erreur, il reste actuellement en boucle. Merci pour la bibliothèque géniale!

Attendez, il se trouve où en boucle? Où dans l'exécution échouons-nous ? Pouvez-vous imprimer le retraçage d'où nous faisons la boucle ?

Il semble accrocher ici :

r = request.get(url,
auth=headeroauth,
cert=self.cert_tuple,
en-têtes=en-têtes,
délai d'attente=10,
vérifier=Vrai)

J'ai essayé d'augmenter ou de diminuer le délai d'attente en vain, mais j'imagine qu'il sait bien avant l'expiration du délai qu'il ne peut pas utiliser le certificat. Merci!

Ah, désolé, je n'ai pas été clair. Je voulais le laisser pendre, puis le tuer avec Ctrl + C pour que python lève une exception KeyboardInterrupt, puis pour voir où nous en sommes dans le retraçage. Je veux savoir où dans Requests l'exécution s'arrête.

Ce qui se passe (ou du moins ce que j'ai vu dans de nombreux cas), c'est qu'OpenSSL, après avoir reçu un certificat protégé par mot de passe, demandera à l'utilisateur un mot de passe. Il n'apparaît dans aucun journal (car l'invite est directement imprimée) et il n'expire pas car il attend qu'un utilisateur appuie sur Entrée.

Inutile de dire que c'est un comportement encombrant et dangereux lorsque le code s'exécute sur un serveur (car cela bloquera votre travailleur sans autre option de récupération que de tuer le processus).

Existe-t-il un moyen de faire en sorte que les demandes soulèvent une exception dans ce cas au lieu de demander un mot de passe, ou est-ce complètement hors de votre contrôle et entre les mains d'OpenSSL ?

@maxnoel Je suis à peu près sûr que cela est entre les mains d'OpenSSL, mais si vous pouvez répondre à la question de @Lukasa (le dernier commentaire sur ce problème), il serait très utile de donner une réponse définitive quant à savoir s'il y avait quelque chose que nous puissions faire pour aider .

Vous pouvez confirmer qu'OpenSSL bloque sur stdin pour la phrase secrète à partir de l'invite python interactive :

>>> r = requests.get("https://foo.example.com/api/user/bill", cert=("client.crt", "client.key"))
Enter PEM pass phrase:
>>>

Si vous exécutez à partir d'un processus en arrière-plan, je suppose qu'OpenSSL bloquera l'attente de cette entrée.

C'est correct. Y a-t-il quelque chose que les demandes peuvent faire pour empêcher que cela se produise ? Lever une exception lorsqu'aucun mot de passe n'est donné serait bien plus utile que de demander des choses sur stdin (en particulier dans un programme non interactif).

J'ai peur de ne connaître aucun moyen. @reaperhulk ?

Il existe des moyens d'empêcher OpenSSL de le faire, mais je ne suis pas sûr qu'ils soient exposés par pyOpenSSL. Où les requêtes appellent-elles pyopenssl pour charger le certificat client ? Je peux creuser un peu.

@reaperhulk C'est fait à partir de urllib3, ici .

Nous faisons également quelque chose de très similaire pour la stdlib, ce qui sera un problème à part entière.

Nous pouvons donc le faire avec PyOpenSSL en utilisant un correctif comme celui-ci . Dans la version stdlib, nous devons utiliser load_cert_chain avec un mot de passe.

Ce problème a-t-il été résolu ? Je rencontre actuellement ce problème en essayant de me connecter à un serveur Apache.

Il n'a pas.

Qu'en est-il des conteneurs formatés (et chiffrés) PKCS#12 qui pourraient contenir un certificat/clé client ? Cela relèverait-il de la même demande de fonctionnalité ?

@mikelupo Ouais .

@telam @mikelupo
J'ai le même problème et j'ai beaucoup cherché sur Google, enfin, je l'ai résolu en utilisant pycurl.
Dans ma situation, j'utilise openssl pour convertir mon fichier .pfx en fichier .pem qui contient à la fois le certificat et la clé (cryptés avec une phrase de passe), puis invoque le code suivant.

import pycurl
import StringIO

b = StringIO.StringIO()
c = pycurl.Curl()
url = "https://example.com"
c.setopt(pycurl.URL, url)
c.setopt(pycurl.WRITEFUNCTION, b.write)
c.setopt(pycurl.CAINFO, "/path/cacert.pem")
c.setopt(pycurl.SSLKEY, "/path/key_file.pem")
c.setopt(pycurl.SSLCERT, "/path/cert_file.pem")
c.setopt(pycurl.SSLKEYPASSWD, "your pass phrase")
c.perform()
c.close()
response_body = b.getvalue()

BTW, pour la sécurité, il vaut mieux ne pas faire de code dur pour pass phrase

Bien sûr. Cela dit, le problème n'est pas vraiment qu'une phrase de passe soit requise - c'est qu'OpenSSL fait se bloquer votre programme en attendant que quelqu'un tape une phrase de passe dans stdin, même dans le cas d'un programme non interactif, GUI ou distant.

Lorsqu'une phrase secrète est requise et qu'aucune n'est fournie, une exception doit être levée à la place.

si vous utilisez une phrase secrète par défaut de '' pour la clé, openssl ne se bloquera pas.
il renverra un mauvais texte de mot de passe. vous pouvez immédiatement modifier votre flux py
pour avertir ensuite l'utilisateur sans ce décrochage apparent

tout projet d'ajouter cette fonctionnalité

Nous voulons l'ajouter, mais nous n'avons pas de calendrier pour l'ajouter pour le moment.

@botondus Je pense avoir trouvé un moyen plus simple d'y parvenir avec la bibliothèque de requêtes. Je documente cela pour d'autres personnes confrontées au problème.

Je suppose que vous avez un certificat .p12 et une phrase secrète pour la clé.

Générez un certificat et une clé privée.

// Generate the certificate file.
openssl pkcs12 -in /path/to/p12cert -nokeys -out certificate.pem
// Generate private key with passpharse, First enter the password provided with the key and then an arbitrary PEM password //(say: 1234) 
openssl pkcs12 -in /path/to/p12cert -nocerts -out privkey.pem

Eh bien, nous n'avons pas encore terminé et nous devons générer la clé qui ne nécessite pas le mot de passe PEM à chaque fois qu'elle doit parler au serveur.

Générer une clé sans mot de passe.

// Running this command will prompt for the pem password(1234), on providing which we will obtain the plainkey.pem
openssl rsa -in privkey.pem -out plainkey.pem

Maintenant, vous aurez certificate.pem et plainkey.pem , les deux fichiers requis pour parler à l'API à l'aide de requêtes.

Voici un exemple de requête utilisant ces certificats et ces clés.

import requests
url = 'https://exampleurl.com'
headers = {
            'header1': '1214141414',
            'header2': 'adad-1223-122'
          }
response = requests.get(url, headers=headers, cert=('~/certificate.pem', '~/plainkey.pem'), verify=True)
print response.json()

J'espère que cela t'aides:

cc @kennethreitz @Lukasa @sigmavirus24

J'ai entendu à travers la vigne qu'Amazon fait exactement cela, en interne.

Je suis également confronté à ce problème. Mon souci est que je ne veux pas stocker la clé privée simple dans le système de fichiers (peut avoir un risque d'être volé par d'autres). Donc, à mon avis, la façon la plus extensible d'implémenter cela est de prendre en charge quelque chose comme PEM encoded string of private key au lieu du chemin de fichier pour spécifier la clé privée. Je viens de laisser le cryptage / décryptage de la clé privée / du certificat aux développeurs en leur faveur.
Après avoir lu le code source des requêtes, il semble que ce ne soit pas assez facile à implémenter puisque les requêtes dépendent de la lib ssl de python qui ne supporte que le fichier certificat/clé privée. Je me demande juste si nous pourrions utiliser pyopenssl au lieu de python stdlib ? pyopenssl a un wrapper de connexion openssl, voir : https://pyopenssl.readthedocs.io/en/latest/api/ssl.html#connection -objects . Ainsi, nous pouvons utiliser l'objet 'pkey' comme clé privée au lieu du chemin du fichier.

Requests prend déjà en charge PyOpenSSL, tant que celui-ci et quelques autres dépendances requises sont installés. Cependant, cela ne deviendra jamais obligatoire : il est important pour nous que nous travaillions bien avec la bibliothèque standard.

Dans une future version, nous prendrons en charge la transmission d'objets SSLContext à urllib3 afin de gérer TLS : cela activera cette fonction.

Pour ceux qui sont confrontés à ce problème, jusqu'à ce que les demandes ajoutent la possibilité de transmettre un ssl.SSLContext/OpenSSL.SSL.Context à urllib3, voici une solution de contournement qui prend en charge l'utilisation d'un certificat/fichier de clé chiffré (nécessite que PyOpenSSL soit installé et utilisé à la place de la bibliothèque standard ssl, ce qu'il devrait être s'il est installé)

import requests

 # Get the password from the user/configfile/whatever
password = ...

# Subclass OpenSSL.SSL.Context to use a password callback that gives your password
class PasswordContext(requests.packages.urllib3.contrib.pyopenssl.OpenSSL.SSL.Context):
    def __init__(self, method):
        super(PasswordContext, self).__init__(method)
        def passwd_cb(maxlen, prompt_twice, userdata):
            return password if len(password) < maxlen else ''
        self.set_passwd_cb(passwd_cb)

# Monkey-patch the subclass into OpenSSL.SSL so it is used in place of the stock version
requests.packages.urllib3.contrib.pyopenssl.OpenSSL.SSL.Context = PasswordContext

# Use requests as normal, e.g.
endpoint = 'https://example.com/authenticated'
ca_certs = '/path/to/ca/certs/bundle'
certfile = '/path/to/certificate'
keyfile = '/path/to/encrypted/keyfile'
requests.get(endpoint, verify=ca_certs, cert=(certfile, keyfile))

@ahnolds : Cela fonctionne-t-il également pour les fichiers PKCS#12, ou est-ce uniquement PEM ?

@Lukasa : Le cas PKCS#12 est-il vraiment censé être traité ici, ou dois-je ouvrir un numéro séparé pour cela ?

PKCS#12 est un problème plus délicat, mais en gros, vous devrez faire tout ce qui est nécessaire pour personnaliser votre SSLContext.

@Lukasa : Je pensais plutôt fournir une bonne API de haut niveau dans les requêtes. Par exemple, en fournissant simplement le nom client_cert.p12 fichier cert=... .

@vog Selon vous, quel code serait nécessaire pour que cela fonctionne ?

@Lukasa Je ne suis pas sûr des éléments internes de requests , alors peut-être que je sous-estime ce qui est déjà là, mais je pense que l'une des choses suivantes doit être faite :

  • Soit nous avons un moyen de fournir un nom de fichier PKCS#12 directement aux couches inférieures (urllib3, etc.). Et peut-être le mot de passe aussi. (Parce que je ne connais personne qui souhaite qu'une bibliothèque d'URL demande à l'administrateur de manière interactive d'entrer son mot de passe PKCS#12 sur un outil qui s'exécute côté serveur.)
  • Si cela est impossible, nous aurions besoin de convertir PKCS#12 (+ mot de passe) en PEM, puis de les fournir aux niveaux inférieurs. Cela se fait avec quelques appels directement à la liaison OpenSSL . Cependant, le résultat est le certificat PEM sous forme de chaîne, et je n'ai pas encore trouvé de moyen de fournir le PEM (non chiffré) sous forme de chaîne aux couches inférieures (sauf peut-être en utilisant OpenSSL / python "ssl" "buffer" wrapper, par exemple wrap_bio , mais ceci n'est disponible que dans les dernières versions de Python 3, pas Python 2).
  • Donc, si cela est également impossible, nous aurions non seulement besoin de convertir PKCS#12 en PEM, mais également de créer un fichier temporaire contenant les données PEM (non cryptées).

Notez que le dernier point est ce que je fais essentiellement en ce moment, mais je n'aime pas du tout ça. Pourquoi ne puis-je pas fournir une chaîne simple à OpenSSL contenant le certificat ? De plus, pourquoi ne puis-je pas simplement transmettre le nom de fichier et le mot de passe PKCS#12 aux couches inférieures ?

Je vais marquer @reaperhulk en tant qu'expert OpenSSL, mais je crois comprendre qu'il n'y a pas d'API pour OpenSSL pour charger les certificats au format PKCS#12 pour les certificats clients. Cela signifie que nous devons absolument nous convertir au PEM. Le faire en mémoire est certainement possible, mais à un moment donné, je me demande si nous ne voulons pas simplement considérer cet expert suffisamment pour que nous le déléguions à n'importe quel SSLContext que vous nous passerez.

@Lukasa Merci d'avoir pris ce problème au sérieux. Désolé si cela semble trop technique, mais il s'agit essentiellement de ceci :

Vous souhaitez accéder à un service via un certificat client. Presque partout, vous obtenez cela sous forme de fichier et de mot de passe (où le fichier est encodé en PKCS#12). Dans la plupart des API, telles que la bibliothèque standard Java, vous lui donnez simplement le nom de fichier et le mot de passe, et vous avez terminé.

Cependant, en Python, c'est compliqué comme l'enfer.

C'est pourquoi presque personne ne le fait. Au lieu de cela, ils convertissent leur fichier et leur mot de passe en fichier PEM à la main, via OpenSSL, et utilisent ce fichier. Il s'agit d'une surcharge administrative pour chaque utilisateur d'une telle application. Parce qu'ils ne peuvent pas simplement nommer le fichier (PKCS#12) et le mot de passe.

Je pense que la bibliothèque requests devrait le rendre au moins aussi simple qu'en Java.

requests déjà un excellent travail de simplification des API complexes stupides, et le cas d'utilisation PKCS#12 n'est qu'un autre exemple d'API complexe stupide.

le cas d'utilisation PKCS#12 n'est qu'un autre exemple d'API complexe et stupide.

Oui, je ne suis pas du tout en désaccord avec cela : je serais totalement heureux d'avoir une sorte de solution pour le support PKCS#12 quelque part dans la pile.

Ce que j'essaie de comprendre, c'est quel code est nécessaire pour que cela fonctionne et, par conséquent, où cela doit être placé. Mon raisonnement est le suivant :

  1. De manière générale, Requests n'ajoute à sa surface d'API que s'il y a une utilité substantielle à le faire (c'est-à-dire s'il est utilisé par beaucoup de gens ou très utilisé par certains), et si la chose que nous faisons est difficile à faire correctement ou a des cas de bord subtils.
  2. Normalement, la prise en charge de PKCS#12 compterait comme un ajout à la surface de l'API, mais si cela ne change pas du tout la syntaxe de cert= (il élargit simplement les éléments qu'il prendra en charge) et ne régresse pas le comportement ( c'est-à-dire que nous pouvons faire la différence de manière fiable entre les fichiers PKCS#12 et les fichiers PEM, ou nous pouvons facilement traiter les deux chaînes logiques), je dirais que cela compte comme un changement suffisamment mineur à la surface que cela en vaut probablement la peine .
  3. Cependant, il y a d'autres endroits où cela peut aller. Par exemple, au niveau de l'adaptateur de transport, ou en tant qu'assistant dans le Requests Toolbelt, ou autre chose.

Cela signifie que je veux évaluer à quel point c'est subtil, à quel point le code est complexe, s'il nécessite des dépendances supplémentaires, puis utiliser ces informations pour déterminer où placer le code au mieux. Par exemple, j'ai un _suspicion_ en ce moment que la bibliothèque standard ne peut pas gérer PKCS#12, ce qui signifierait que _au mieux_ Les requêtes ne pourraient utiliser PKCS#12 qu'avec le [security] supplémentaire installé. Dans un cas encore pire, nous n'aurons peut-être pas du tout les fonctions disponibles dans une liaison OpenSSL, auquel cas nous devrons faire de vrais trucs dingues pour que cela fonctionne. C'est pourquoi je voulais que @reaperhulk intervienne : il sera probablement en mesure de clarifier cela pour nous plus rapidement que je ne pourrais faire la recherche.

J'aimerais voir ce soutien ajouté : j'ai juste besoin de quelques personnes qui connaissent la portée du travail pour commenter ici et me faire savoir à quel point la montagne que nous devons déplacer est grande.

Un détail supplémentaire pour une implémentation PKCS#12 : les anciennes versions des liaisons Python OpenSSL échouent si le mot de passe est donné en tant qu'objet unicode au lieu d'une chaîne d'octets. Il doit donc être converti avant de le passer à load_pkcs12() comme ceci :

if isinstance(password, unicode):
    password_bytes = password.encode('utf8')
else:
    password_bytes = password
pkcs12 = OpenSSL.crypto.load_pkcs12(pkcs12_data, password_bytes)

Un convertisseur complet pourrait ressembler à ceci, où pkcs12_data devrait être une chaîne d'octets avec des données binaires, tandis que password peut être une chaîne d'octets ou une chaîne unicode :

def pkcs12_to_pem(pkcs12_data, password):
    # Old versions of OpenSSL.crypto.load_pkcs12() fail if the password is a unicode object
    if isinstance(password, unicode):
        password_bytes = password.encode('utf8')
    else:
        password_bytes = password
    p12 = OpenSSL.crypto.load_pkcs12(pkcs12_data, password_bytes)
    p12_cert = p12.get_certificate()
    p12_key = p12.get_privatekey()
    pem_cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, p12_cert)
    pem_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12_key)
    pem = pem_cert + pem_key
    return pem

La discussion PKCS#12 me semble dépasser le cadre du problème initial, car le problème en question est de savoir si les requêtes doivent prendre en charge PKCS#12 de manière native. Je voterais pour qu'il ait son propre problème, mais cela dépend évidemment des responsables.

Cela dit, comme solution de contournement qui ne nécessite pas de fichiers temporaires non cryptés, la méthode OpenSSL.crypto.dump_privatekey a un paramètre de phrase secrète facultatif, vous pouvez donc obtenir une copie de la clé privée cryptée au format PEM de cette façon. Cela réduirait cela au problème PEM crypté avec lequel nous avons commencé.

Alternativement, vous pouvez aussi probablement écrire un hack similaire à celui que j'ai suggéré avant d'utiliser la méthode use_privatekey de OpenSSL.SSL.Context . Du haut de ma tête (non testé) quelque chose comme

# From somewhere
pkcs12_data = ...
password_bytes = ...

class Pkcs12Context(requests.packages.urllib3.contrib.pyopenssl.OpenSSL.SSL.Context):
    def __init__(self, method):
        super(PasswordContext, self).__init__(method)
        p12 = OpenSSL.crypto.load_pkcs12(pkcs12_data, password_bytes)
        self.use_certificate(p12.get_certificate())
        self.use_privatekey(p12.get_privatekey())
# Monkey-patch the subclass into OpenSSL.SSL so it is used in place of the stock version
requests.packages.urllib3.contrib.pyopenssl.OpenSSL.SSL.Context = Pkcs12Context

Ensuite, utilisez simplement requests.get etc sans spécifier de certificat du tout, car il est déjà géré dans le constructeur.

Revoir ce fil maintenant. Reformulation de l'original :

étant donné un certificat client chiffré au format PEM, les demandes peuvent-elles gérer la fourniture d'un mot de passe ?

Comme cela se trouve dans les bibliothèques standard actuelles, ce serait bien d'intégrer cette option. Cela serait extrêmement précieux pour les considérations de sécurité d'entreprise (où nos certificats sont cryptés et sont censés le rester).

À ce stade, cela peut être fait en passant un contexte SSL personnalisé à urllib3 à l'aide d'un adaptateur de transport. Cela peut faire tout ce que le contexte SSL de la bibliothèque standard permet. Vous pouvez voir un exemple de passage d'un contexte personnalisé ici .

J'ai pu utiliser .pfx et .p12 avec des requêtes en les convertissant en .pem à l'aide d'un fichier temporaire. Voir https://gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068

S'il y a un intérêt, je peux soumettre un PR. Ce serait bien d'éviter le fichier temporaire et le gestionnaire de contexte. Fais-moi savoir.

Il est peu probable que cela soit fusionné, j'en ai peur, mais notez que vous pouvez désormais transmettre un contexte PyOpenSSL directement aux requêtes via des adaptateurs de transport, vous pouvez donc découvrir que vous pouvez contourner ce problème.

Il est peu probable que cela soit fusionné, j'en ai peur, mais notez que vous pouvez désormais transmettre un contexte PyOpenSSL directement aux requêtes via des adaptateurs de transport, vous pouvez donc découvrir que vous pouvez contourner ce problème.

désolé d'être confus, mais dites-vous que le support pfx/p12 en général ne sera probablement pas fusionné? (en supposant que c'est fait de la bonne manière, à travers le contexte, etc.). Heureux de tenter le coup, mais cela ne vaut évidemment pas mon temps s'il ne doit pas être fusionné.

Je crois que le "pas susceptible d'être fusionné" concernait la solution de fichier temporaire.

@erikbern Pour être clair, je suis heureux d'aborder et de fusionner toute solution qui fonctionne de manière assez cohérente. Par exemple, une solution pour utiliser PKCS#12 via le module de contrib PyOpenSSL dans urllib3 serait acceptable.

Cependant, une solution de fichier temporaire n'est pas acceptable (comme indiqué par @vog). Cela signifie que la prise en charge de PKCS#12 ne fonctionnera probablement pas avec la bibliothèque standard, car le module ssl la bibliothèque standard n'expose aucune prise en charge, et il ne sera donc pas pris en charge dans toutes les configurations de requêtes.

Ça a l'air bien. Je suis également d'accord pour dire que le fichier temporaire est mauvais car il existe un risque de sécurité en stockant des clés déchiffrées sur le disque. Je pourrais y jeter un œil la semaine prochaine. Merci pour l'avertissement concernant le module ssl - si la limitation est en dehors de requests cela devient évidemment plus compliqué

Je l'ai examiné et le module ssl ajouté un argument cadata où vous pouvez transmettre les données pem sous forme de chaîne brute : https://docs.python.org/3/library/ssl.html #ssl.SSLContext.load_verify_locations

Nous aurions à patcher urllib3 dans un tas d'endroits pour que cela fonctionne, donc je pourrais commencer par là

@erikbern Pour être clair, presque toutes les solutions de ce type fonctionneront mieux simplement en passant un objet SSLContext correctement configuré à urllib3 à l'aide d'un TransportAdapter .

https://github.com/kennethreitz/requests/issues/2519 semble être identique à ce problème, ils devraient donc probablement être fusionnés

Toute mise à jour sur ce problème, j'essaie d'utiliser un certificat client crypté par mot de passe et incapable de le faire fonctionner. Dois-je rechercher d'autres options que les demandes ? Pourriez-vous s'il vous plaît répondre au plus vite.

Documentons-nous cela? J'ai l'impression que c'est notre fonctionnalité la plus demandée.

Je crois que ce fil a commencé en 2013 et je n'ai trouvé aucune résolution expliquée jusqu'à la fin. Avez-vous fourni une option pour fournir le mot de passe? ou est-ce encore en cours ?

J'essaie d'utiliser des requêtes dans un produit de sécurité d'application que je crée. Donc, tous les pointeurs seront utiles

@AnoopPillai Avez-vous vérifié ce commentaire ? https://github.com/requests/requests/issues/1573#issuecomment -188125157

Oui, j'ai lu ce commentaire, il s'agit d'une solution de contournement, mais dans mon cas, je ne souhaite pas le convertir en 2 fichiers cert, car cela devra être fait en dehors de mon application. De plus, nous utilisons un coffre-fort pour stocker le mot de passe du fichier .pem crypté.

Ce mot de passe est récupéré dynamiquement par l'application au moment de l'exécution, donc pas de codage en dur.

@AnoopPillai D'accord .

@kennethreitz Non, nous ne l'avons pas documenté.

@AnoopPillai Oui, cela fonctionne bien. Vous avez juste besoin d'utiliser des crochets de niveau inférieur. Dans ce cas, nous vous permettons de passer un SSLContext à urllib3 directement au niveau de l'adaptateur de transport. Cela vous permet d'accéder aux fonctions sous-jacentes qui vous permettront de fournir une phrase secrète ou une fonction de phrase secrète. C'est ainsi que nous vous recommandons de soutenir cela.

Merci Lukasa de m'avoir fait savoir qu'il existe un moyen de le faire.
Je suis très nouveau sur python et j'utilise la version 3.6. Pourriez-vous me guider où je peux trouver des options telles que des chiffrements pour transmettre le mot de passe du certificat client.
@Erikbern Je n'ai pas encore

@AnoopPillai Vous voudrez load_cert_chain .

@Lukasa , ça vous dérangerait de le documenter ? ne devrait prendre que quelques minutes (je pense dans la section avancée, ou peut-être dans une nouvelle section avancée avancée)

Désolé les gars, mon manque d'expérience avec python pourrait être la raison mais je ne peux pas modifier le code Lukasa expliqué ci-dessus. Mon code est :

class DESAdapter(HTTPAdapter):
    """
    A TransportAdapter that re-enables 3DES support in Requests.
    """
    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context(load_cert_chain='rtmqa-clientid.pem',password='weblogic')
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        context = create_urllib3_context(load_cert_chain='rtmqa-clientid.pem', password='weblogic')
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).proxy_manager_for(*args, **kwargs)
s = requests.Session()
s.mount(url, DESAdapter())
r = s.get(url, headers=request_header).json()

et j'obtiens une erreur
TypeError : create_urllib3_context() a obtenu un argument de mot-clé inattendu 'load_cert_chain'

C'est une erreur, oui. Vous voulez appeler create_urllib3_context et obtenir sa valeur de retour, puis appeler load_cert_chain sur l'objet renvoyé. Essayez de jouer avec ces fonctions dans l'interpréteur interactif pour voir comment elles fonctionnent.

L'urllib3..util.ssl_.py qui est installé sur mon mac n'a pas la dernière option pour le mot de passe.
c'est le code

    if certfile:
        context.load_cert_chain(certfile, keyfile)
    if HAS_SNI:  # Platform-specific: OpenSSL with enabled SNI
        return context.wrap_socket(sock, server_hostname=server_hostname)

L'option de mot de passe est manquante. Comment mettre à jour ssl_.py pour obtenir la dernière version ?

@AnoopPillai Vous ne le faites pas. Appelez la fonction sans arguments, puis appelez load_cert_chain sur l'objet renvoyé. Vous n'avez pas besoin de modifier urllib3.

Pour être clair, comme ceci :

ctx = create_urllib3_context()
ctx.load_cert_chain(your_arguments_here)

Documentons cela :)

@ erikbern j'ai essayé votre solution de fichier temporaire mais j'ai eu l'erreur suivante:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 441, in wrap_socket
    cnx.do_handshake()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1716, in do_handshake
    self._raise_ssl_error(self._ssl, result)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1456, in _raise_ssl_error
    _raise_current_error()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/_util.py", line 54, in exception_from_error_queue
    raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 595, in urlopen
    self._prepare_proxy(conn)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 816, in _prepare_proxy
    conn.connect()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connection.py", line 326, in connect
    ssl_context=context)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/util/ssl_.py", line 329, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 448, in wrap_socket
    raise ssl.SSLError('bad handshake: %r' % e)
ssl.SSLError: ("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/adapters.py", line 440, in send
    timeout=timeout
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 639, in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/util/retry.py", line 388, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='credit-cards-accounts-qa.kdc.capitalone.com', port=443): Max retries exceeded with url: /credit-cards-accounts/credit-cards/accounts/XqLuxBTABbIDvpw56ba34p2WV9JoWUSkPJ09hrBlWD8= (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/tsu892/Desktop/Office/Pythone-work/ASR-pythone/ASR-python3.6/test-request.py", line 48, in <module>
    r = requests.get(url, headers=request_header, cert=cert).json()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/api.py", line 72, in get
    return request('get', url, params=params, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/api.py", line 58, in request
    return session.request(method=method, url=url, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 508, in request
    resp = self.send(prep, **send_kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 618, in send
    r = adapter.send(request, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/adapters.py", line 506, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='credit-cards-accounts-qa.kdc.capitalone.com', port=443): Max retries exceeded with url: /credit-cards-accounts/credit-cards/accounts/XqLuxBTABbIDvpw56ba34p2WV9JoWUSkPJ09hrBlWD8= (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))

Ci-dessous mon code :

import requests
import json
import OpenSSL.crypto
import tempfile
import os
import contextlib
import ssl

json_file='apiInput.json'
hdr_key=[]
hdr_value=[]
json_data=open(json_file)
data = json.load(json_data)
request_body={}
#pprint(data)
json_data.close()
request_data = data['request1']
request_header=request_data['header-data']
url=request_header['url']

@contextlib.contextmanager
def pfx_to_pem():
    print('inside pfx tp pem')
    with tempfile.NamedTemporaryFile(suffix='.pem') as t_pem:
        f_pem = open(t_pem.name, 'wb')
        fr_pfx = open('rtmqa-clientid.pfx', 'rb').read()
        p12 = OpenSSL.crypto.load_pkcs12(fr_pfx,'xxxxxxxxx')
        f_pem.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey()))
        f_pem.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, p12.get_certificate()))
        ca = p12.get_ca_certificates()
        if ca is not None:
            for cert in ca:
                f_pem.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
        f_pem.close()
        yield t_pem.name

with pfx_to_pem() as cert:
    print(cert)
    r = requests.get(url, headers=request_header, cert=cert).json()
print(r.status_code)
print(r.json())

désolé, difficile de savoir pourquoi ça casse en regardant votre commentaire. je l'ai utilisé pour un tas d'applications et je n'ai eu aucun problème

@Lukasa J'ai essayé avec ce changement de code (code collé ci-dessous) et je

import requests
import json
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.ssl_ import create_urllib3_context

json_file='apiInput.json'
hdr_key=[]
hdr_value=[]
json_data=open(json_file)
data = json.load(json_data)
request_body={}
#pprint(data)
json_data.close()
request_data = data['request1']
request_header=request_data['header-data']
url=request_header['url']

class DESAdapter(HTTPAdapter):
    """
    A TransportAdapter that re-enables 3DES support in Requests.
    """
    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context()
        context.load_cert_chain('rtmqa-clientid.pem',password='weblogic')
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        context = create_urllib3_context()
        context.load_cert_chain('rtmqa-clientid.pem',password='weblogic')
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).proxy_manager_for(*args, **kwargs)

s = requests.Session()
s.headers=request_header
s.mount(url, DESAdapter())
r = s.get(url)
/Users/tsu892/Python3.6/bin/python /Users/tsu892/Desktop/Office/Pythone-work/ASR-pythone/ASR-python3.6/Test-ASRreq.py
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 441, in wrap_socket
    cnx.do_handshake()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1716, in do_handshake
    self._raise_ssl_error(self._ssl, result)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1456, in _raise_ssl_error
    _raise_current_error()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/_util.py", line 54, in exception_from_error_queue
    raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 595, in urlopen
    self._prepare_proxy(conn)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 816, in _prepare_proxy
    conn.connect()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connection.py", line 326, in connect
    ssl_context=context)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/util/ssl_.py", line 329, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 448, in wrap_socket
    raise ssl.SSLError('bad handshake: %r' % e)
ssl.SSLError: ("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/adapters.py", line 440, in send
    timeout=timeout
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 639, in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/util/retry.py", line 388, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='credit-cards-accounts-qa.kdc.capitalone.com', port=443): Max retries exceeded with url: /credit-cards-accounts/credit-cards/accounts/XqLuxBTABbIDvpw56ba34p2WV9JoWUSkPJ09hrBlWD8= (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/tsu892/Desktop/Office/Pythone-work/ASR-pythone/ASR-python3.6/Test-ASRreq.py", line 37, in <module>
    r = s.get(url)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 521, in get
    return self.request('GET', url, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 508, in request
    resp = self.send(prep, **send_kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 618, in send
    r = adapter.send(request, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/adapters.py", line 506, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='credit-cards-accounts-qa.kdc.capitalone.com', port=443): Max retries exceeded with url: /credit-cards-accounts/credit-cards/accounts/XqLuxBTABbIDvpw56ba34p2WV9JoWUSkPJ09hrBlWD8= (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))

@erikbern pourrait-il s'agir d'un problème de configuration sur mon ordinateur portable. J'utilise un mac, Pythone3.6

6c40089ea258:~ tsu892$ pip3 show requests
Name: requests
Version: 2.18.4
Summary: Python HTTP for Humans.
Home-page: http://python-requests.org
Author: Kenneth Reitz
Author-email: [email protected]
License: Apache 2.0
Location: /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages
Requires: idna, certifi, chardet, urllib3
6c40089ea258:~ tsu892$ pip3 show certifi
Name: certifi
Version: 2017.7.27.1
Summary: Python package for providing Mozilla's CA Bundle.
Home-page: http://certifi.io/
Author: Kenneth Reitz
Author-email: [email protected]
License: MPL-2.0
Location: /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages
Requires: 

Pensez-vous que quelque chose ne va pas avec le certificat?

Quelle est la sortie de python -m requests.help ?

@Lukasa La sortie est :

6c40089ea258:~ tsu892$ python3 -m requests.help?
/Library/Frameworks/Python.framework/Versions/3.6/bin/python3: No module named requests.help?

Veuillez supprimer le point d'interrogation de votre ligne de commande.

6c40089ea258:~ tsu892$ python3 -m requests.help
{
  "chardet": {
    "version": "3.0.4"
  },
  "cryptography": {
    "version": "2.0.3"
  },
  "idna": {
    "version": "2.6"
  },
  "implementation": {
    "name": "CPython",
    "version": "3.6.2"
  },
  "platform": {
    "release": "16.7.0",
    "system": "Darwin"
  },
  "pyOpenSSL": {
    "openssl_version": "1010006f",
    "version": "17.2.0"
  },
  "requests": {
    "version": "2.18.4"
  },
  "system_ssl": {
    "version": "100020bf"
  },
  "urllib3": {
    "version": "1.22"
  },
  "using_pyopenssl": true
}

L'erreur que vous obtenez est donc due au fait que nous ne pouvons pas valider le certificat TLS du serveur. certifi et OpenSSL semblent corrects, donc je suppose que le serveur se comporte mal. Quel serveur essayez-vous d'atteindre ?

L'application est déployée sur Cloud AWS. Mais lorsque nous appelons l'API, elle va d'abord à OSB qui authentifie le certificat, puis achemine la demande vers AWS.
J'utilise le même certificat et en utilisant le facteur ou mon code ruby ​​l'API fonctionne bien

Avez-vous besoin d'un certificat racine spécifique ? Pouvez-vous me fournir le nom d'hôte auquel je suis parvenu ?

L'URL de l'hôte sans le chemin est https://credit-cards-accounts-qa.kdc.capitalone.com
Il s'agit d'un point de terminaison interne

Oui, donc je ne vois pas ce qui se passe là-bas. Pouvez-vous exécuter openssl s_client -showcerts -connect credit-cards-accounts-qa.kdc.capitalone.com:443 et fournir la sortie complète ?

Supprimé

Il semble que vous n'utilisiez pas un certificat racine de confiance mondiale. Où se trouve le certificat racine de ce service ?

Je n'utilise aucun autre certificat. Je ne sais pas si un certificat racine est utilisé en coulisse, y a-t-il un moyen pour moi de le savoir ?

Oui, les outils de développement de Chrome vous indiqueront la chaîne de certificats complète qu'il utilise.

vous ne voulez probablement pas publier un certificat interne en ligne pour que quiconque puisse le voir...

@erikbern Ceci est une information publique. Vous pouvez obtenir le même résultat en exécutant la même commande.

@SethMichaelLarson Du profil GitHub de @erikbern "Chief Troll Officer". Peut-être étaient-ils juste en train de troller ?

@erikbern @sigmavirus24 Ah ! Je ne savais pas à qui je parlais. Procéder! ??

Je ne vois rien d'autre que le certificat sha-1 quand je fuis le facteur
Peut-être que je dois ajouter ceci d'une manière ou d'une autre à Pycharm

Si vous naviguez littéralement sur le site Web dans Chrome, cela devrait suffire.

@SethMichaelLarson exécutant quelle commande ? Pour info, le commentaire a été supprimé maintenant, mais il y avait un blob entier BEGIN CERTIFICATE ici plus tôt... si vous ne voulez pas partager cela en ligne

@erikbern C'était juste la clé publique du certificat...

Les certificats sont des données publiques ; ils sont transmis en clair sur le réseau à chaque tentative de connexion.

Je suis allé à la chaîne de certification et n'ai trouvé qu'un certificat Sha-1 et le certificat .Pem que j'utilise pour accéder à l'API

@AnoopPillai J'ai obtenu votre exemple de code du 1er septembre fonctionnant sans problème en utilisant un fichier pem côté client avec mot de passe. Il semble que l'hôte utilise un certificat régulier. Avec @Lukasa merci beaucoup !

J'ai malheureusement toujours des problèmes, même avec la méthode du fichier temporaire. Je peux utiliser le .pfx dans Google Postman et n'ai aucun problème d'authentification (donc je sais que mes informations d'identification fonctionnent), mais j'obtiens toujours des 401 avec Python. Malheureusement, le gars de l'assistance de la société avec laquelle je traite n'a pas été d'une grande aide - quelqu'un a-t-il des suggestions pour le dépannage ?

À ce stade, je ne sais vraiment pas où chercher le problème, car d'autres personnes signalent un succès avec la méthode du fichier temporaire et je n'ai toujours rien reçu de leur équipe de gestion des certificats.

Tout conseil serait très apprécié - s'il vous plaît laissez-moi savoir si je peux fournir des informations supplémentaires pour rendre cela plus facile.

Merci :)

Juste une suggestion, avez-vous essayé de convertir PFX en PEM ? De plus, si le serveur utilise également un nom d'utilisateur/mot de passe, vous devrez ajouter cette requête get/post en utilisant auth=(). J'utilise l'approche class DESAdapter(HTTPAdapter) ci-dessus depuis plusieurs semaines maintenant sans problème, en utilisant un fichier PEM protégé par mot de passe.

@ideasean Obtention d'informations d'identification non valides. Je devrais pointer load_cert_chain vers un fichier .pem généré par la fonction pfx_to_pem écrite pour la méthode Temp File, n'est-ce pas ? Il contient la clé privée et le certificat.

Étant donné que le fichier .pfx fonctionne avec Postman mais qu'il ne s'authentifiera pas ici, cela pourrait-il signifier que quelque chose ne va pas dans le processus de conversion ?

Je n'ai pas utilisé la méthode du fichier temporaire. J'ai utilisé l'approche DESAdapter à peu près comme écrit dans le post d'AnoopPillai sur Sep1 ci-dessus en commençant par -

J'ai essayé avec ce changement de code (code collé ci-dessous) et je me suis retrouvé avec la même erreur que j'ai eue avec la méthode du fichier temporaire.

Je ne peux pas parler du processus de conversion, mais peut-être qu'un bon test consiste à essayer d'utiliser le fichier pem converti avec Postman ?

Notez également que j'ai utilisé l'approche ci-dessus car mon fichier pem était crypté / protégé par mot de passe, et les requêtes Python ne le prennent actuellement pas en charge. Si votre pem n'est pas protégé par mot de passe, vous devriez pouvoir utiliser des requêtes natives par lien (mais vous aurez alors un certificat non protégé sur votre système de fichiers).

@ideasean J'ai décomposé le .pfx selon cette méthode et j'ai obtenu un fichier .pem avec des attributs de sac et un certificat ainsi qu'un fichier .pem avec des attributs de sac et une clé privée cryptée.

J'obtiens toujours des informations d'identification non valides, je suppose que je vais essayer de mettre les certificats sur Postman et de voir s'ils fonctionnent, mais je ne comprends pas pourquoi je suis apparemment incapable de décompresser ce .pfx correctement

J'ai également essayé la commande openssl openssl pkcs12 -in <my_pfx>.pfx -out certificate.cer -nodes , et elle me donne toujours une erreur 401 lorsque je la change comme ceci: context.load_cert_chain('certificate.cer')

J'ai installé le .cer mentionné ci-dessus et Postman ne demande même pas de l'utiliser lorsque je fais l'appel d'API (contrairement à la fenêtre contextuelle lorsqu'il demande d'utiliser le .pfx), je ne sais pas comment je peux le faire utiliser ce certificat spécifique car il n'y a pas de panneau "Certificats" dans les paramètres comme le disent les docs.

Vous utilisez peut-être la version du navigateur de Postman, qui n'inclut pas le panneau de certification, la désactivation de la validation SSL, etc. Essayez le client complet pour modifier les paramètres du certificat. Vous voudrez peut-être continuer cette discussion sur un autre fil alors, car nous sommes un peu hors sujet.

@mkane848 a vu votre commentaire original où vous ValueError: String expected . Vous voudrez peut-être vérifier https://github.com/pyca/pyopenssl/issues/701 et https://github.com/shazow/urllib3/issues/1275.

J'utilise mon pem privé avec un mot de passe en utilisant ceci :

from requests.adapters import HTTPAdapter

from urllib3.util.ssl_ import create_urllib3_context

class SSLAdapter(HTTPAdapter):
    def __init__(self, certfile, keyfile, password=None, *args, **kwargs):
        self._certfile = certfile
        self._keyfile = keyfile
        self._password = password
        return super(self.__class__, self).__init__(*args, **kwargs)

    def init_poolmanager(self, *args, **kwargs):
        self._add_ssl_context(kwargs)
        return super(self.__class__, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        self._add_ssl_context(kwargs)
        return super(self.__class__, self).proxy_manager_for(*args, **kwargs)

    def _add_ssl_context(self, kwargs):
        context = create_urllib3_context()
        context.load_cert_chain(certfile=self._certfile,
                                keyfile=self._keyfile,
                                password=str(self._password))
        kwargs['ssl_context'] = context

Pour votre information, je viens d'implémenter le support PKCS#12 pour requests tant que bibliothèque séparée :

Le code est une implémentation propre : il n'utilise ni Monkey Patching ni fichiers temporaires. À la place, un TransportAdapter personnalisé est utilisé, qui fournit un SSLContext personnalisé.

Tous les commentaires et améliorations sont les bienvenus!

Bien sûr, je souhaite que requests fournisse cette fonctionnalité directement, mais jusqu'à ce que nous y soyons, cette bibliothèque soulagera la douleur.

Ce serait très bien si nous pouvions simplement faire ceci:

~~~
cert=("cert.pem", "key.pem", "somepassphrase") # cert/clé séparé

cert=("keycert.pem", None, "somepassphrase")    # combined cert/key

~~~

...même si cela ne fonctionnait que sur python 3.3+. Ce ne serait qu'un ajout mineur à la surface de l'API.

AFAICS, cela signifierait une petite modification de urllib3 afin que HTTPSConnection accepte un argument optionnel password ; ceci est transmis par ssl_wrap_socket , se terminant par :

~si fichier de certificat :si le mot de passe n'est pas Aucun :context.load_cert_chain(certfile, keyfile, mot de passe)autre:context.load_cert_chain(certfile, keyfile)~

Il serait alors rétrocompatible, ne soulevant une exception que si vous essayez d'utiliser une phrase secrète de clé privée sur une ancienne plate-forme qui ne la prend pas en charge.

Notez que l'adaptateur contrib/pyopenssl.py prend déjà en charge cet argument supplémentaire pour load_cert_chain , tout comme python 2.7 .


Mis à part : j'utilise AWS KMS pour gérer les données « secrètes ».

Personnellement, je ne serais pas contre ce changement, car je pense que cela améliorerait considérablement notre interface utilisateur pour de nombreux utilisateurs à tous les niveaux.

@ sigmavirus24 des pensées?

@candlerb @kennethreitz Serait-il acceptable d'inclure également le boîtier PKCS#12 dans cette API ?

cert=('keycert.p12', None, 'somepassphrase')

La distinction peut être soit par extension de fichier ( *.p12 contre *.pem ), soit en regardant les premiers octets de ce fichier.

Je n'ai aucun problème à autoriser les demandes à prendre un pkcs#12, tant que cela peut être fait en toute sécurité - et à mon avis, cela empêche d'écrire la clé privée extraite dans un fichier temporaire.

En cherchant sur Google Python pkcs#12, je trouve :

  • Le code de quelqu'un qui écrit la clé privée
  • Un autre code qui, je pense, dépend de pyOpenSSL à lire dans le pkcs#12. Il renvoie le certificat et la clé en tant qu'éléments de données.

Donc, en faisant cela, je pense qu'il serait nécessaire de connecter les choses de manière à ce que la clé/le certificat eux-mêmes soient transmis à OpenSSL, et non les noms de fichiers contenant ces choses. Cela ressemble à un changement beaucoup plus important.

Si c'est trop difficile, cela signifie simplement que l'utilisateur doit convertir pkcs#12 en PEM hors ligne, ce qui est assez simple (et peut être documenté).

@candlerb Comme je l'ai écrit dans mon commentaire précédent (https://github.com/requests/requests/issues/1573#issuecomment-348968658), j'ai déjà créé une implémentation propre qui s'intègre bien avec requests .

Les problèmes que vous décrivez sont donc déjà résolus.

À l'heure actuelle, mon implémentation ajoute de nouveaux arguments de mots-clés pkcs12_* , pour rester à l'écart autant que possible.

Mais je pense qu'il devrait plutôt être intégré dans l'argument du mot clé cert , et ma question est la suivante :

  • Serait-ce acceptable en général?
  • Ma proposition concrète cert=('keycert.p12', None, 'somepassphrase') serait-elle acceptable ?
  • Comment distinguer PKCS#12 et PEM ? (Par suffixe de nom de fichier ou par contenu de fichier ?)

(De plus, je préférerais voir cela dans requests plutôt que dans ma bibliothèque séparée requests_pkcs12 . Mais étant donné l'âge de ce problème, j'ai peu d'espoir que cela aille en amont de sitôt. Cependant , s'il y avait une déclaration concrète sur le type d'implémentation souhaité, je pourrais peut-être ajuster mon implémentation en conséquence et proposer une pull request.)

Alors, quelques trucs :

  1. Je ne pense pas que nous devrions prendre le mot-clé cert et le développer comme ceci. Ce sont des données implicitement structurées et les gens sont déjà confus par les tuples du mot-clé files . Je pense que continuer un modèle connu-mauvais est stupide.

  2. Je pense que si quoi que ce soit, l'adaptateur pkcs12 devrait être modifié et mis en amont dans le request-toolbelt. Je pense qu'il serait préférable de le modifier pour créer le ssl_context une fois au lieu de stocker le mot de passe pkcs12 en mémoire sur cet objet.

Je pense qu'il reste encore du travail à faire avant de pouvoir gérer cela dans le cas plus général, quoi qu'il en soit, et cela inclut la détermination de la bonne API pour cela pour les demandes 3.0.

@sigmavirus24 Merci pour le retour.

  1. D'accord, gardons donc les mots-clés pkcs12_* séparés.
  2. Oui, cela vaut vraiment la peine d'être amélioré. J'ai créé une entrée de suivi des problèmes pour cela : https://github.com/m-click/requests_pkcs12/issues/2

Comment la classe PKCS#12 TransportAdapter serait-elle incluse dans requests ? Cette classe serait-elle simplement ajoutée à requests , ou existe-t-il un autre moyen de l'inclure à un niveau "plus profond", afin qu'il puisse être utilisé sans aucun request()/get()/... et sans avoir à le charger explicitement adaptateur?

Mon organisation a besoin d'utiliser des certificats PKCS12 et est prête à apporter les améliorations nécessaires à votre bibliothèque pour ce faire. Le décryptage des fichiers .p12 en fichiers .pem est considéré comme trop risqué et ajoute une étape supplémentaire à gérer. Nous aimerions ajouter des fonctionnalités pour générer et fournir un ssl_context approprié pour une session donnée. S'agit-il toujours d'une fonctionnalité que votre équipe serait prête à accepter en supposant qu'elle soit correctement implémentée ?

Juste un petit rappel : une implémentation propre a déjà été fournie par notre société, mais en tant qu'adaptateur séparé : https://github.com/m-click/requests_pkcs12

N'hésitez pas à le reformater en une pull request pour les requêtes elle-même.

En cours de route, vous souhaiterez peut-être résoudre un problème mineur : le ssl_context ne doit pas être conservé en mémoire pendant toute une session, mais le plus rapidement possible, uniquement pour une seule connexion donnée. Voir également:

Au cas où vous le répariez en cours de route, ce serait bien si vous pouviez le fournir sous forme de petite demande d'extraction à https://github.com/m-click/requests_pkcs12 en plus des demandes elles-mêmes.

De cette façon, toutes les personnes qui utilisent actuellement la bibliothèque requests_pkcs12 bénéficieront également automatiquement de cette amélioration, sans avoir à passer à la nouvelle API (alors améliorée) pour les requêtes elle-même.

Oui, https://github.com/m-click/requests_pkcs12 a fonctionné pour moi et a fait exactement ce que je voulais qu'il fasse. Merci beaucoup @vog ! J'espère que les demandes seront en mesure de soutenir cela éventuellement.

Je vais également remercier @vog pour sa mise en œuvre, fonctionne comme prévu et résout le problème de la conservation de cert/key dans les stockages non sécurisés comme S3 dans mon cas. Espérons que cela puisse faire son chemin vers requests .

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

NoahCardoza picture NoahCardoza  ·  4Commentaires

cnicodeme picture cnicodeme  ·  3Commentaires

iLaus picture iLaus  ·  3Commentaires

ReimarBauer picture ReimarBauer  ·  4Commentaires

eromoe picture eromoe  ·  3Commentaires