Requests: Especifique la contraseña para el certificado del lado del cliente SSL

Creado en 3 sept. 2013  ·  121Comentarios  ·  Fuente: psf/requests

Hasta donde yo sé, actualmente no es posible especificar la contraseña para el certificado del lado del cliente que está usando para la autenticación.
Esto es un pequeño problema porque normalmente siempre desea proteger con contraseña su archivo .pem que contiene la clave privada. openssl ni siquiera te permitirá crear uno sin contraseña.

Documentation Planned

Comentario más útil

@botondus Creo que encontré una forma más sencilla de lograr esto con la biblioteca de solicitudes. Estoy documentando esto para otras personas que enfrentan el problema.

Supongo que tiene un certificado .p12 y una frase de contraseña para la clave.

Genere certificado y clave privada.

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

Bueno, aún no hemos terminado y necesitamos generar la clave que no requiere la contraseña PEM cada vez que necesita hablar con el servidor.

Genere clave sin frase de contraseña.

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

Ahora, tendrá certificate.pem y plainkey.pem , ambos archivos necesarios para comunicarse con la API mediante solicitudes.

A continuación, se muestra una solicitud de ejemplo que utiliza estos certificados y claves.

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

Espero que esto ayude:

cc @kennethreitz @Lukasa @ sigmavirus24

Todos 121 comentarios

Algo como:

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

Estoy bastante seguro de que se supone que debes usar el cert para eso: cert=('server.pem', 'my_password')

@ sigmavirus24
La tupla es de (certificate, key) . Actualmente no hay soporte para archivos de claves cifrados.
El stdlib solo tiene soporte para aquellos en la versión 3.3.

Je, @ t-8ch, se vinculó accidentalmente a un archivo en su FS local. ;) Enlace correcto .

Muy bien @ t-8ch. Es por eso que nunca debería responder problemas desde el autobús. : /

Entonces, el consenso actual es que no apoyamos esto. ¿Cuánto trabajo es probable que sea para agregar soporte en versiones de Python que no son 3.3?

¿Qué tan difícil sería arrojar un error en esta condición? Me encontré con este problema tonto y me tomó dos horas resolverlo, sería bueno si arrojara un error, actualmente solo se encuentra allí en bucle. ¡Gracias por la increíble biblioteca!

Espera, ¿se sienta en el bucle? ¿Dónde fallamos en la ejecución? ¿Puedes imprimir el rastreo desde donde hacemos el bucle?

Parece colgar aquí mismo:

r = request.get (url,
auth = headeroauth,
cert = self.cert_tuple,
encabezados = encabezados,
tiempo de espera = 10,
verificar = Verdadero)

Intenté aumentar o disminuir el tiempo de espera sin éxito, pero imagino que sabe bien antes del tiempo de espera que no puede usar el certificado. ¡Gracias!

Ah, lo siento, no estaba claro. Quería dejarlo colgar y luego matarlo con Ctrl + C para que Python arroje una excepción KeyboardInterrupt, luego para ver dónde estamos en el rastreo. Quiero saber en qué parte de las solicitudes se detiene la ejecución.

Lo que está sucediendo (o al menos lo que he visto en muchos casos) es que OpenSSL, al recibir un certificado protegido con contraseña, solicitará al usuario una contraseña. No aparece en registros (porque el mensaje se imprime directamente) y no se agota el tiempo de espera porque está esperando que un usuario presione Intro.

No hace falta decir que es un comportamiento peligroso y engorroso cuando el código se ejecuta en un servidor (porque colgará a su trabajador sin otra opción de recuperación que no sea matar el proceso).

¿Hay alguna forma de hacer que las solicitudes generen una excepción en ese caso en lugar de solicitar una contraseña, o está completamente fuera de su control y en manos de OpenSSL?

@maxnoel Estoy bastante seguro de que esto está en manos de OpenSSL, pero si puede responder a la pregunta de @Lukasa (el último comentario sobre este tema) sería muy útil para dar una respuesta definitiva sobre si hay algo que podamos hacer para ayudar .

Puede confirmar que OpenSSL está bloqueando stdin para la frase de contraseña desde el indicador interactivo de Python:

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

Si está ejecutando desde un proceso en segundo plano, supongo que OpenSSL bloqueará la espera de esa entrada.

Eso es correcto. ¿Hay algo que las solicitudes puedan hacer para evitar que eso suceda? Generar una excepción cuando no se proporciona una contraseña sería mucho más útil que pedir cosas en stdin (especialmente en un programa no interactivo).

Me temo que no conozco ninguna forma. @reaperhulk?

Hay formas de evitar que OpenSSL haga esto, pero no estoy seguro de que pyOpenSSL las exponga. ¿Dónde llaman las solicitudes a pyopenssl para cargar el certificado de cliente? Puedo cavar un poco.

@reaperhulk Se hace desde urllib3, aquí .

También hacemos algo muy similar para stdlib, que será un problema completamente separado.

Entonces podemos hacer esto con PyOpenSSL usando un parche como este . En la versión stdlib, necesitamos usar load_cert_chain con una contraseña.

¿Se ha resuelto este problema? Actualmente me encuentro con esto al intentar conectarme a un servidor Apache.

No tiene.

¿Qué pasa con los contenedores formateados (y cifrados) PKCS # 12 que podrían contener un certificado / clave de cliente? ¿Caería esto dentro de la misma solicitud de función?

@mikelupo Sí.

@telam @mikelupo
Tengo el mismo problema y busqué mucho en Google, finalmente, lo resolví usando pycurl.
En mi situación, uso openssl para convertir mi archivo .pfx en un archivo .pem que contiene tanto el certificado como la clave (cifrados con una frase de contraseña), luego invoco el siguiente código.

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

Por cierto, por seguridad, es mejor no hacer hardcode por pass phrase

Por supuesto. Dicho esto, el problema no es realmente que se requiera una frase de contraseña; es que OpenSSL hace que su programa se cuelgue mientras espera que alguien escriba una frase de contraseña en stdin, incluso en el caso de un programa no interactivo, GUI o remoto.

Cuando se requiere una frase de contraseña y no se proporciona ninguna, se debe generar una excepción en su lugar.

si usa una frase de contraseña predeterminada de "" para la clave, openssl no se bloqueará.
devolverá un texto de contraseña incorrecta. puedes alterar inmediatamente tu flujo de py
para luego notificar al usuario sin esa parada aparente

cualquier plan para agregar esta característica

Queremos agregarlo, pero no tenemos un horario para agregarlo en este momento.

@botondus Creo que encontré una forma más sencilla de lograr esto con la biblioteca de solicitudes. Estoy documentando esto para otras personas que enfrentan el problema.

Supongo que tiene un certificado .p12 y una frase de contraseña para la clave.

Genere certificado y clave privada.

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

Bueno, aún no hemos terminado y necesitamos generar la clave que no requiere la contraseña PEM cada vez que necesita hablar con el servidor.

Genere clave sin frase de contraseña.

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

Ahora, tendrá certificate.pem y plainkey.pem , ambos archivos necesarios para comunicarse con la API mediante solicitudes.

A continuación, se muestra una solicitud de ejemplo que utiliza estos certificados y claves.

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

Espero que esto ayude:

cc @kennethreitz @Lukasa @ sigmavirus24

Escuché a través de la vid que Amazon hace exactamente esto, internamente.

Yo también estoy enfrentando este problema. Mi preocupación es que no quiero almacenar la clave privada simple en el sistema de archivos (puede haber riesgo de que otros la roben). Entonces, en mi opinión, la forma más extensible de implementar esto es admitir el uso de algo como PEM encoded string of private key lugar de la ruta del archivo para especificar la clave privada. Simplemente deje el cifrado / descifrado de la clave / certificado privado a los desarrolladores a su favor.
Después de leer el código fuente de las solicitudes, parece que no es muy fácil de implementar, ya que las solicitudes dependen de la biblioteca ssl de python, que solo admite archivos de certificado / clave privada. Me pregunto si podríamos usar pyopenssl en lugar de python stdlib. pyopenssl tiene un contenedor de conexión openssl, consulte: https://pyopenssl.readthedocs.io/en/latest/api/ssl.html#connection -objects. Por lo tanto, podemos usar el objeto 'pkey' como clave privada en lugar de la ruta del archivo.

Las solicitudes ya son compatibles con PyOpenSSL, siempre que este y algunas otras dependencias necesarias estén instaladas. Sin embargo, eso nunca será obligatorio: es importante para nosotros que trabajemos bien con la biblioteca estándar.

En una versión futura, tendremos soporte para pasar objetos SSLContext a urllib3 para manejar TLS: eso habilitará esta función.

Para aquellos que enfrentan este problema, hasta que las solicitudes agreguen la capacidad de pasar un ssl.SSLContext / OpenSSL.SSL.Context a urllib3, aquí hay una solución que realmente admite el uso de certificado / archivo de claves cifrado (requiere que PyOpenSSL esté instalado y usado en lugar de la biblioteca estándar ssl, que debería ser si está instalado)

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 : ¿Esto también funciona para archivos PKCS # 12, o es solo PEM?

@Lukasa : ¿Se supone que el caso PKCS # 12 realmente se maneja aquí, o debería abrir un problema separado para eso?

PKCS # 12 es un tema más complicado, pero básicamente deberá hacer lo que sea necesario para personalizar su SSLContext.

@Lukasa : Estaba pensando más en proporcionar una buena API de alto nivel en las solicitudes. Por ejemplo, simplemente proporcionando el nombre client_cert.p12 archivo cert=... .

@vog ¿Qué código crees que se necesitaría para que eso funcione?

@Lukasa No estoy seguro acerca de los requests , así que tal vez subestimo lo que ya está allí, pero creo que se debe hacer una de las siguientes cosas:

  • O tenemos una forma de proporcionar un nombre de archivo PKCS # 12 directamente a las capas inferiores (urllib3, etc.). Y tal vez la contraseña también. (Porque no conozco a nadie que quiera que una biblioteca de URL le pida al administrador de forma interactiva que ingrese su contraseña PKCS # 12 en una herramienta que se ejecuta en el lado del servidor).
  • Si eso es imposible, necesitaríamos convertir PKCS # 12 (+ contraseña) a PEM, luego proporcionándolos a los niveles más bajos. Esto se hace con algunas llamadas directamente al enlace OpenSSL . Sin embargo, el resultado es el certificado PEM como una cadena, y todavía no he encontrado una manera de proporcionar el PEM (sin cifrar) como cadena a las capas inferiores (excepto tal vez mediante el uso de la envoltura OpenSSL / python "ssl" "buffer", por ejemplo, wrap_bio , pero esto solo está disponible en las últimas versiones de Python 3, no en Python 2).
  • Entonces, si eso también es imposible, no solo necesitaríamos convertir PKCS # 12 a PEM, sino que también tendríamos que crear un archivo temporal que contenga los datos PEM (sin cifrar).

Tenga en cuenta que el último punto es lo que esencialmente estoy haciendo en este momento, pero no me gusta esto en absoluto. ¿Por qué no puedo proporcionar una cadena simple a OpenSSL que contenga el certificado? Además, ¿por qué no puedo simplemente pasar el nombre de archivo PKCS # 12 y la contraseña a las capas inferiores?

Voy a etiquetar en @reaperhulk como experto en OpenSSL, pero tengo entendido que no hay API para que OpenSSL cargue certificados de formato PKCS # 12 para certificados de cliente. Esto significa que tendríamos que convertirnos absolutamente a PEM. Hacerlo en memoria es ciertamente posible, pero en cierto punto me pregunto si no queremos considerar a este experto lo suficiente como para delegarlo en cualquier SSLContext que nos pase.

@Lukasa Gracias por tomarse este problema en serio. Lo siento si esto suena demasiado técnico, pero esencialmente es solo esto:

Quiere acceder a un servicio a través de un certificado de cliente. Casi en todas partes obtiene esto como un archivo y una contraseña (donde el archivo está codificado en PKCS # 12). En la mayoría de las API, como la biblioteca estándar de Java, simplemente le da el nombre de archivo y la contraseña, y ya está.

Sin embargo, en Python esto es muy complicado.

Por eso casi nadie lo hace. En su lugar, convierten su archivo y contraseña a un archivo PEM a mano, a través de OpenSSL, y usan ese archivo. Se trata de una sobrecarga administrativa para cada usuario de dicha aplicación. Porque no pueden simplemente nombrar el archivo (PKCS # 12) y la contraseña.

Creo que la biblioteca requests debería hacerlo al menos tan simple como en Java.

requests ya hace un gran trabajo al simplificar las estúpidas API complejas, y el caso de uso de PKCS # 12 es solo otro ejemplo de una estúpida API compleja.

el caso de uso de PKCS # 12 es solo otro ejemplo de una API compleja y estúpida.

Sí, no estoy en desacuerdo con eso en absoluto: estaría totalmente feliz de tener algún tipo de solución para el soporte de PKCS # 12 en algún lugar de la pila.

Lo que estoy tratando de entender es qué código se requiere para que funcione y, como resultado, dónde debe colocarse. Mi razonamiento es así:

  1. En términos generales, las solicitudes solo se agregan a su superficie de API si hay una utilidad sustancial al hacerlo (es decir, si lo usan muchas personas o algunas lo usan mucho), y si lo que estamos haciendo es difícil de hacer bien o tiene casos de borde sutiles.
  2. Normalmente, la compatibilidad con PKCS # 12 se consideraría una adición a la superficie de la API, pero si no cambia la sintaxis de cert= en absoluto (solo amplía las cosas que admitirá) y no hace retroceder el comportamiento ( es decir, podemos distinguir de manera confiable la diferencia entre los archivos PKCS # 12 y los archivos PEM, o podemos procesar fácilmente a través de ambas cadenas de lógica), yo diría que cuenta como un cambio suficientemente menor en la superficie que probablemente valga la pena. .
  3. Sin embargo, hay otros lugares a los que puede ir. Por ejemplo, en el nivel del Adaptador de transporte, o como ayudante en el cinturón de herramientas de solicitudes, u otra cosa.

Eso significa que quiero sopesar qué tan sutil es esto, qué tan complejo es el código, si requiere dependencias adicionales y luego usar esa información para averiguar dónde es mejor colocar el código. Por ejemplo, tengo una _suspicion_ en este momento de que la biblioteca estándar no puede manejar PKCS # 12, lo que significaría que _en el mejor de los casos_ Requests solo podría usar PKCS # 12 con el [security] extra instalado. En un caso aún peor, es posible que no tengamos las funciones disponibles en ningún enlace OpenSSL, en cuyo caso tendremos que hacer algunas cosas realmente locas para que funcione. Es por eso que quería que @reaperhulk interviniera : es probable que pueda aclararnos esto más rápido de lo que yo podría hacer la investigación.

Me gustaría que se agregara este soporte: solo necesito que algunas personas que sepan cuál es el alcance del trabajo comenten aquí y me digan qué tan grande es realmente la montaña que debemos mover.

Un detalle más para una implementación de PKCS # 12: las versiones anteriores de los enlaces de Python OpenSSL fallan si la contraseña se da como unicode objeto en lugar de una cadena de bytes. Por lo tanto, debe convertirse antes de pasarlo a load_pkcs12() esta manera:

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

Un convertidor completo podría verse así, donde se espera que pkcs12_data sea ​​una cadena de bytes con datos binarios, mientras que password puede ser una cadena de bytes o una cadena 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 discusión sobre PKCS # 12 me parece que está más allá del alcance del problema inicial, ya que el problema en cuestión es si las solicitudes deben admitir PKCS # 12 de forma nativa. Votaría que tiene su propio problema, pero obviamente eso depende de las personas a cargo.

Dicho esto, como una solución que no requiere archivos temporales sin cifrar, el método OpenSSL.crypto.dump_privatekey tiene un parámetro de frase de contraseña opcional, por lo que podría obtener una copia de la clave privada cifrada en formato PEM de esa manera. Eso reduciría esto al problema de PEM cifrado con el que comenzamos.

Alternativamente, probablemente también podrías escribir un truco similar al que sugerí antes de usar el método use_privatekey de OpenSSL.SSL.Context . Fuera de mi cabeza (no probado) algo como

# 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

Luego simplemente use requests.get etc.sin especificar un certificado en absoluto, ya que ya está manejado en el constructor.

Revisando este hilo ahora. Reformulación del original:

dado un certificado de cliente con formato PEM cifrado, ¿se pueden gestionar las solicitudes proporcionando una contraseña?

Dado que esto está en las bibliotecas estándar actuales, sería genial integrar esta opción. Esto sería extremadamente valioso para las consideraciones de seguridad empresarial (donde nuestros certificados están encriptados y deben permanecer así).

En este punto, esto se puede hacer pasando un contexto ssl personalizado a urllib3 usando un adaptador de transporte. Esto puede hacer lo que permita el contexto ssl de la biblioteca estándar. Puede ver un ejemplo de cómo pasar un contexto personalizado aquí .

Pude usar .pfx y .p12 con solicitudes convirtiéndolo a .pem usando un archivo temporal. Ver https://gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068

Si hay algún interés, puedo enviar un PR. Sería genial evitar el archivo temporal y el administrador de contexto. Hágamelo saber.

Me temo que no es probable que se fusione, pero tenga en cuenta que ahora puede pasar un contexto PyOpenSSL directamente a las solicitudes a través de adaptadores de transporte, por lo que puede encontrar que puede eludir ese problema.

Me temo que no es probable que se fusione, pero tenga en cuenta que ahora puede pasar un contexto PyOpenSSL directamente a las solicitudes a través de adaptadores de transporte, por lo que puede encontrar que puede eludir ese problema.

perdón por estar confundido, pero ¿está diciendo que es probable que el soporte pfx / p12 en general no se fusione? (suponiendo que se haga de la manera correcta, a través del contexto, etc.). Feliz de intentarlo, pero obviamente no vale la pena mi tiempo si no se fusiona.

Creo que "no es probable que se fusione" se trataba de la solución de archivos temporales.

@erikbern Para ser claros, estoy feliz de abordar y fusionar cualquier solución que funcione de manera un tanto consistente. Por ejemplo, una solución para usar PKCS # 12 a través del módulo contrib de PyOpenSSL en urllib3 sería aceptable.

Sin embargo, una solución de archivo temporal no es aceptable (como lo señala @vog). Esto significa que es poco probable que el soporte para PKCS # 12 funcione con la biblioteca estándar, ya que el módulo de la biblioteca estándar ssl no expone ningún soporte para él, por lo que no será compatible con todas las configuraciones de Solicitudes.

Suena bien. También estoy de acuerdo en que el archivo temporal es incorrecto, ya que existe un riesgo de seguridad al almacenar claves descifradas en el disco. Podría echarle un vistazo la semana que viene. Gracias por los avisos sobre el módulo ssl : si la limitación está fuera de requests , obviamente se vuelve más complicado

Lo miré y el módulo ssl agregó un argumento cadata donde puede pasar datos pem como una cadena sin procesar: https://docs.python.org/3/library/ssl.html # ssl.SSLContext.load_verify_locations

Tendríamos que parchear urllib3 en varios lugares para que esto funcione, así que podría empezar por ahí.

@erikbern Para ser claros, casi cualquier solución como esa funcionará mejor simplemente pasando un objeto SSLContext configurado apropiadamente a urllib3 usando un TransportAdapter .

https://github.com/kennethreitz/requests/issues/2519 parece ser idéntico a este problema, por lo que probablemente deberían fusionarse

Cualquier actualización sobre este problema Estoy tratando de usar un certificado de cliente que está encriptado con contraseña y no puedo hacerlo funcionar. ¿Debo buscar otras opciones además de las solicitudes? ¿Podría responder lo antes posible?

¿Documentamos esto? Siento que esta es nuestra función más solicitada.

Creo que este hilo comenzó en 2013 y no encontré ninguna solución explicada hasta el final. ¿Les dieron una opción para proporcionar la contraseña? ¿O es esto todavía en progreso?

Estoy intentando utilizar solicitudes en un producto de seguridad de aplicaciones que estoy creando. Entonces, cualquier sugerencia será útil.

Sí, leí este comentario, esto es una solución alternativa, pero en mi caso no quiero convertirlo en archivos de 2 certificados, ya que tendré que hacerlo fuera de mi aplicación. Además, utilizamos una bóveda para almacenar la contraseña del archivo .pem cifrado.

La aplicación recupera esta contraseña dinámicamente en tiempo de ejecución, por lo que no hay codificación rígida.

@AnoopPillai Muy bien.

@kennethreitz No, no lo documentamos.

@AnoopPillai Sí, esto funciona bien. Solo necesita usar algunos ganchos de nivel inferior. En este caso, le permitimos pasar un SSLContext a urllib3 directamente en el nivel del Adaptador de transporte. Esto le permite acceder a las funciones subyacentes que le permitirán proporcionar una frase de contraseña o una función de frase de contraseña. Así es como le recomendamos que apoye esto.

@AnoopPillai solución con un archivo temporal que me ha resultado útil: https://gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068

Gracias Lukasa por dejarme saber que hay una manera de hacerlo.
Soy muy nuevo en Python y estoy usando la versión 3.6. ¿Podría orientarme dónde puedo encontrar opciones como cifrados para pasar la contraseña del certificado del cliente?
@Erikbern No he pasado por la solución de archivos temporales, pero hoy

@AnoopPillai Querrás load_cert_chain .

@Lukasa, ¿te importaría documentarlo? solo debería tomar unos minutos (estoy pensando en la sección avanzada, o tal vez en una nueva sección avanzada avanzada)

Lo siento chicos, mi falta de experiencia con Python podría ser la razón, pero no puedo modificar el código que Lukasa explicó anteriormente. Mi codigo es:

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

y me sale un error
TypeError: create_urllib3_context () obtuvo un argumento de palabra clave inesperado 'load_cert_chain'

Eso es un error, sí. Desea llamar a create_urllib3_context y obtener su valor de retorno, y luego llamar a load_cert_chain en el objeto devuelto. Intente jugar con esas funciones en el intérprete interactivo para ver cómo funcionan.

El urllib3..util.ssl_.py que está instalado en mi Mac no tiene la última opción de contraseña.
este es el codigo

    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)

Falta la opción de contraseña. ¿Cómo actualizo ssl_.py para obtener la última versión?

@AnoopPillai No lo haces. Llame a la función sin argumentos, luego llame a load_cert_chain en el objeto devuelto. No es necesario realizar cambios en urllib3.

Para ser claro, así:

ctx = create_urllib3_context()
ctx.load_cert_chain(your_arguments_here)

Documentemos esto :)

@ erikbern Probé su solución de archivo temporal Pero obtuve el siguiente error:

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')],)",),))

A continuación se muestra mi código:

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

lo siento, es difícil saber por qué se está rompiendo en tu comentario. Lo he usado para muchas aplicaciones y no he tenido ningún problema.

@Lukasa Intenté con ese cambio de código (código pegado a continuación) y terminé con el mismo error que obtuve con el método de archivo temporal.

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 ¿ podría ser un problema de configuración en mi computadora portátil? Yo uso una 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: 

¿Crees que hay algún problema con el certificado?

¿Cuál es la salida de python -m requests.help ?

@Lukasa El resultado es:

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

Elimine el signo de interrogación de su línea de comando.

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
}

Entonces, el error que está recibiendo se debe a que no podemos validar el certificado TLS del servidor. certifi y OpenSSL parecen correctos, así que supongo que el servidor se está portando mal. ¿A qué servidor estás intentando llegar?

La aplicación se implementa en Cloud AWS. Pero cuando llamamos a la API, primero va a OSB, que autentica el certificado y luego enruta la solicitud a AWS.
Utilizo el mismo certificado y al usar cartero o mi código ruby, la API funciona bien

¿Necesita un certificado raíz específico? ¿Puede proporcionarme el nombre de host que se está comunicando?

La URL del host sin la ruta es https://credit-cards-accounts-qa.kdc.capitalone.com
Este es un punto final interno

Sí, no puedo ver qué está pasando allí. ¿Puede ejecutar openssl s_client -showcerts -connect credit-cards-accounts-qa.kdc.capitalone.com:443 y proporcionar la salida completa?

Eliminado

Parece que no está utilizando un certificado raíz de confianza global. ¿Dónde está el certificado raíz para ese servicio?

No estoy usando ningún otro certificado. No estoy seguro de si detrás de la escena se utiliza algún certificado raíz, ¿hay alguna manera de averiguarlo?

Sí, las herramientas para desarrolladores de Chrome le dirán la cadena de certificados completa que está utilizando.

probablemente no desee publicar algún certificado interno en línea para que nadie lo vea ...

@erikbern Esta es información pública. Puede lograr el mismo resultado ejecutando el mismo comando.

@SethMichaelLarson Del perfil de GitHub de @erikbern "Chief Troll Officer". ¿Quizás solo estaban trolleando?

@erikbern @ sigmavirus24 ¡Ah! No sabía con quién estaba hablando. ¡Continuar! 🙇

No puedo ver nada excepto el certificado sha-1 cuando huyo del cartero
Puede que necesite agregar esto de alguna manera a Pycharm

Si navega literalmente al sitio web en Chrome, debería ser suficiente.

@SethMichaelLarson ejecutando qué comando? Para su información, el comentario se ha eliminado ahora, pero antes había un blob completo de BEGIN CERTIFICATE aquí ... afaik, no desea compartir eso en línea

@erikbern Esa fue solo la clave pública para el certificado ...

Los certificados son datos públicos; se transmiten en texto plano a través de la red en cada intento de conexión.

Fui a la cadena de certificados y solo encontré un certificado Sha-1 y el certificado .Pem que estoy usando para acceder a la API

@AnoopPillai Obtuve su código de ejemplo del 1 de septiembre funcionando sin problemas usando un archivo pem del lado del cliente con contraseña. Parece que el anfitrión está usando un certificado regular. ¡Con @Lukasa muchas gracias!

Lamentablemente, sigo teniendo problemas, incluso con el método de archivo temporal. Puedo usar el .pfx en Google Postman y no tengo problemas para autenticarme (así que sé que mis credenciales funcionan), pero sigo obteniendo 401 con Python. Desafortunadamente, el tipo de soporte de la empresa con la que estoy tratando no ha sido de mucha ayuda. ¿Alguien tiene alguna sugerencia para solucionar problemas?

En esta etapa, no estoy realmente seguro de dónde buscar el problema, ya que otras personas informan sobre el éxito con el método de archivo temporal y todavía no he recibido noticias de su equipo de gestión de certificados.

Cualquier consejo sería muy apreciado. Por favor, avíseme si puedo proporcionar información adicional para facilitar esta tarea.

Gracias :)

Solo una sugerencia, ¿intentó convertir PFX a PEM? Además, si el servidor también usa un nombre de usuario / contraseña, deberá agregar la solicitud get / post usando auth = (). He estado usando el enfoque class DESAdapter(HTTPAdapter) anterior durante varias semanas sin problemas, usando un archivo PEM protegido por contraseña.

@ideasean Aún obteniendo credenciales inválidas. Debería apuntar load_cert_chain a un archivo .pem generado por la función pfx_to_pem escrita para el método de archivo temporal, ¿correcto? Tiene la clave privada y el certificado.

Dado que el .pfx funciona con Postman pero no se autentica aquí, ¿podría significar que algo va mal en el proceso de conversión?

No utilicé el método de archivo temporal. Utilicé el enfoque DESAdapter casi como está escrito en la publicación de AnoopPillai el 1 de septiembre anterior comenzando con -

Intenté con ese cambio de código (código pegado a continuación) y terminé con el mismo error que obtuve con el método de archivo temporal.

No puedo hablar sobre el proceso de conversión, pero tal vez una buena prueba sea intentar usar el archivo pem convertido con Postman.

También tenga en cuenta que utilicé el enfoque anterior porque mi archivo pem estaba encriptado / protegido con contraseña, y las solicitudes de Python actualmente no lo admiten. Si su pem termina sin estar protegido por contraseña, entonces debería poder usar solicitudes nativas por enlace (pero luego tendrá un certificado desprotegido en su sistema de archivos).

@ideasean Desglosé el .pfx según este método y obtuve un archivo .pem con Atributos y certificado de bolsa, así como un archivo .pem con Atributos de bolsa y una clave privada cifrada.

Aún obteniendo credenciales no válidas, supongo que intentaré pasar los certificados en Postman y ver si funcionan, pero no puedo entender por qué aparentemente no puedo descomprimir este .pfx correctamente

También probé el comando openssl openssl pkcs12 -in <my_pfx>.pfx -out certificate.cer -nodes , y todavía me da un error 401 cuando lo cambio así: context.load_cert_chain('certificate.cer')

Instalé el .cer mencionado anteriormente y Postman ni siquiera pide usarlo cuando hago la llamada a la API (a diferencia de la ventana emergente cuando pide usar el .pfx), no estoy seguro de qué otra manera puedo hacer que use ese certificado específico ya que no hay un panel de "Certificados" en la configuración como dicen los documentos.

Es posible que esté utilizando la versión del navegador de Postman, que no incluye el panel de certificación, la desactivación de la validación de SSL, etc. Pruebe el cliente completo para cambiar la configuración del certificado. Es posible que desee continuar esta discusión en un hilo diferente, ya que estamos un poco fuera de tema.

@ mkane848 vio su comentario original donde recibía un ValueError: String expected . Es posible que desee consultar https://github.com/pyca/pyopenssl/issues/701 y https://github.com/shazow/urllib3/issues/1275.

Utilizo mi pem privado con una contraseña usando esto:

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

Para su información, acabo de implementar el soporte PKCS # 12 para requests como una biblioteca separada:

El código es una implementación limpia : no usa parches de mono ni archivos temporales. En su lugar, se utiliza un TransportAdapter personalizado, que proporciona un SSLContext personalizado.

¡Cualquier comentario y mejora son bienvenidos!

Por supuesto, desearía que requests proporcionara esta funcionalidad directamente, pero hasta que estemos allí, esta biblioteca aliviará el dolor.

Sería muy bueno si pudiéramos simplemente hacer esto:

~~~
cert = ("cert.pem", "key.pem", "alguna frase de contraseña") # cert / key separado

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

~~~

... incluso si solo funcionó en Python 3.3+. Esto solo sería una pequeña adición a la superficie de la API.

AFAICS, esto significaría un pequeño cambio en urllib3 para que HTTPSConnection acepte un argumento password opcional; esto se transmite a través de ssl_wrap_socket , terminando con:

~si certfile:si la contraseña no es Ninguna:context.load_cert_chain (certfile, keyfile, contraseña)demás:context.load_cert_chain (certfile, keyfile)~

Entonces sería compatible con versiones anteriores, generando una excepción solo si intenta usar una frase de contraseña de clave privada en una plataforma anterior que no la admite.

Tenga en cuenta que el adaptador contrib/pyopenssl.py ya admite este argumento adicional para load_cert_chain , al igual que Python 2.7 .


Aparte: estoy usando AWS KMS para administrar datos "secretos", por lo que cargaría la contraseña clave en tiempo de ejecución desde KMS, no la codificaría en la aplicación.

Personalmente, no estaría en contra de este cambio, ya que creo que mejoraría enormemente nuestra interfaz de usuario para muchos usuarios en general.

@ sigmavirus24 ¿ alguna idea?

@candlerb @kennethreitz ¿Sería aceptable incluir el caso PKCS # 12 en esa API también?

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

La distinción podría ser por extensión de archivo ( *.p12 versus *.pem ), o mirando los primeros bytes de ese archivo.

No tengo ningún problema en permitir que las solicitudes tomen un pkcs # 12, siempre que se pueda hacer de manera segura, y en mi opinión, eso impide escribir la clave privada extraída en un archivo temporal.

Buscando en Google Python pkcs # 12, encuentro:

  • El código de alguien que escribe la clave privada
  • Algún otro código que creo que depende de pyOpenSSL para leer en el pkcs # 12. Devuelve el certificado y la clave como elementos de datos.

Entonces, al hacer esto, creo que sería necesario conectar las cosas de tal manera que la clave / certificado en sí se pasen a OpenSSL, no los nombres de archivo que contienen esas cosas. Eso suena como un cambio mucho mayor.

Si eso es demasiado difícil, entonces solo significa que el usuario tiene que convertir pkcs # 12 a PEM fuera de línea, lo cual es bastante sencillo (y puede documentarse).

@candlerb Como escribí en mi comentario anterior (https://github.com/requests/requests/issues/1573#issuecomment-348968658), ya creé una implementación limpia que se integra bien con requests .

Entonces, los problemas que está describiendo ya están resueltos.

En este momento, mi implementación agrega nuevos argumentos de palabras clave pkcs12_* , para mantenerse al margen tanto como sea posible.

Pero creo que debería integrarse en el argumento de palabra clave cert lugar, y mi pregunta es:

  • ¿Sería eso aceptable en general?
  • ¿Sería aceptable mi propuesta concreta cert=('keycert.p12', None, 'somepassphrase') ?
  • ¿Cómo distinguir entre PKCS # 12 y PEM? (¿Por sufijo de nombre de archivo o por contenido de archivo?)

(Además, preferiría ver eso en requests lugar de en mi biblioteca requests_pkcs12 separada. Pero dada la antigüedad de este problema, tengo pocas esperanzas de que esto ocurra pronto. , si hubiera una declaración concreta sobre qué tipo de implementación se desea exactamente, tal vez podría ajustar mi implementación en consecuencia y proponer una solicitud de extracción).

Entonces, algunas cosas:

  1. No creo que debamos tomar la palabra clave cert y expandirla de esta manera. Son datos estructurados implícitamente y las personas ya están confundidas por las tuplas en la palabra clave files . Creo que continuar con un patrón conocido-malo es una tontería.

  2. Creo que, en todo caso, el adaptador pkcs12 debería modificarse y actualizarse en el cinturón de herramientas de solicitudes. Creo que sería mejor modificarlo para crear ssl_context una vez en lugar de almacenar la contraseña pkcs12 en la memoria de ese objeto.

Creo que todavía hay otro trabajo por hacer antes de que podamos manejar esto en el caso más general sin importar qué y eso incluye determinar la API correcta para esto para Requests 3.0.

@ sigmavirus24 Gracias por los comentarios.

  1. Bien, mantengamos las palabras clave pkcs12_* separadas.
  2. Sí, definitivamente vale la pena mejorarlo. Creé una entrada de seguimiento de problemas para eso: https://github.com/m-click/requests_pkcs12/issues/2

¿Cómo se incluiría la clase PKCS # 12 TransportAdapter en requests ? ¿Esa clase simplemente se agregaría a requests , o hay otra forma de incluirla en un nivel "más profundo", de modo que pueda usarse sin envoltorios request()/get()/... y sin tener que cargar explícitamente eso? ¿adaptador?

Mi organización necesita utilizar certificados PKCS12 y está dispuesta a realizar las mejoras necesarias en su biblioteca para poder hacerlo. Descifrar los archivos .p12 en archivos .pem se considera un riesgo excesivo y agrega un paso adicional que tratar. Nos gustaría agregar funcionalidad para generar y proporcionar un ssl_context apropiado para una sesión determinada. ¿Sigue siendo esta funcionalidad que su equipo estaría dispuesto a aceptar suponiendo que se implemente correctamente?

Solo un recordatorio rápido: nuestra empresa ya ha proporcionado una implementación limpia, pero como un adaptador separado: https://github.com/m-click/requests_pkcs12

Siéntase libre de reformatearlo en una solicitud de extracción para solicitudes.

En el camino, es posible que desee solucionar un problema menor: el ssl_context no debe mantenerse en la memoria durante una sesión completa, sino lo más pronto posible, solo para una única conexión determinada. Ver también:

En caso de que lo arregle en el camino, sería bueno si pudiera proporcionarlo como una pequeña solicitud de extracción a https://github.com/m-click/requests_pkcs12 además de las solicitudes en sí.

De esa manera, todas las personas que estén usando la biblioteca requests_pkcs12 este momento también se beneficiarían automáticamente de esa mejora, sin tener que cambiar a la nueva API (entonces mejorada) para las solicitudes en sí.

Sí, https://github.com/m-click/requests_pkcs12 funcionó para mí e hizo exactamente lo que quería que hiciera. ¡Muchas gracias @vog ! Espero que las solicitudes puedan respaldar eso eventualmente.

También voy a agradecer a @vog por su implementación, funciona como se esperaba y resuelve el problema de mantener cert / key en los almacenamientos no seguros como S3 en mi caso. Con suerte, esto puede llegar a requests .

¿Fue útil esta página
0 / 5 - 0 calificaciones