Requests: Especifique a senha para o certificado SSL do lado do cliente

Criado em 3 set. 2013  ·  121Comentários  ·  Fonte: psf/requests

Até onde eu sei, atualmente não é possível especificar a senha para o certificado do lado do cliente que você está usando para autenticação.
Isso é um pouco problemático porque normalmente você sempre deseja proteger com senha o arquivo .pem que contém a chave privada. openssl nem mesmo deixa você criar um sem uma senha.

Documentation Planned

Comentários muito úteis

@botondus Acho que encontrei uma maneira mais simples de fazer isso com a biblioteca de solicitações. Estou documentando isso para outras pessoas que estão enfrentando o problema.

Presumo que você tenha um certificado .p12 e uma senha longa para a chave.

Gere certificado e chave 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

Bem, ainda não terminamos e precisamos gerar a chave que não requer a senha do PEM toda vez que precisar se comunicar com o servidor.

Gerar chave sem senha.

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

Agora, você terá certificate.pem e plainkey.pem , ambos os arquivos necessários para se comunicar com a API usando solicitações.

Aqui está um exemplo de solicitação usando esses certificados e chaves.

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 isto ajude:

cc @kennethreitz @Lukasa @ sigmavirus24

Todos 121 comentários

Algo como:

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

Certeza de que você deve usar o parâmetro cert para isso: cert=('server.pem', 'my_password')

@ sigmavirus24
A tupla é para (certificate, key) . Atualmente não há suporte para arquivos de chave criptografados.
O stdlib só tem suporte para aqueles na versão 3.3.

Heh, @ t-8ch, você acidentalmente vinculou a um arquivo em seu FS local. ;) Link correto .

Muito bem @ t-8ch. É por isso que eu nunca deveria responder questões do ônibus. : /

Portanto, o consenso atual é que não apoiamos isso. Quanto trabalho provavelmente será adicionar suporte em versões não 3.3 do Python?

Quão difícil seria lançar um erro nesta condição? Acabei de me deparar com esse problema bobo e demorou duas horas para descobrir, seria bom se ele gerasse um erro, ele atualmente fica parado em loop. Obrigado pela biblioteca incrível!

Espere, ele fica em looping? Onde, na execução, falhamos? Você pode imprimir o traceback de onde fazemos o loop?

Parece que está pendurado bem aqui:

r = request.get (url,
auth = headeroauth,
cert = self.cert_tuple,
cabeçalhos = cabeçalhos,
tempo limite = 10,
verificar = verdadeiro)

Tentei aumentar ou diminuir o tempo limite sem sucesso, mas imagino que ele sabe bem antes do tempo limite que não pode usar o cert. Obrigado!

Ah, desculpe, não fui claro. Eu pretendia deixá-lo travar e, em seguida, eliminá-lo com Ctrl + C para que o python lance uma exceção KeyboardInterrupt, para ver onde estamos no traceback. Quero saber onde em Requests a execução é interrompida.

O que está acontecendo (ou pelo menos o que tenho visto em muitos casos) é que o OpenSSL, ao receber um certificado protegido por senha, solicitará ao usuário uma senha. Ele não aparece em nenhum log (porque o prompt é impresso diretamente) e não atinge o tempo limite porque está esperando que um usuário pressione Enter.

Desnecessário dizer que é um comportamento cativante e perigoso quando o código está sendo executado em um servidor (porque ele travará seu trabalhador sem nenhuma opção de recuperação além de encerrar o processo).

Existe uma maneira de fazer solicitações levantarem uma exceção nesse caso, em vez de solicitar uma senha, ou isso está completamente fora de seu controle e nas mãos do OpenSSL?

@maxnoel Tenho quase certeza de que está nas mãos do OpenSSL, mas se você puder responder à pergunta de

Você pode confirmar que o OpenSSL está bloqueando no stdin para a senha longa do prompt Python interativo:

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

Se você estiver executando a partir de um processo em segundo plano, presumo que o OpenSSL bloqueará a espera por essa entrada.

Está correto. Há algo que as solicitações possam fazer para evitar que isso aconteça? Gerar uma exceção quando nenhuma senha é fornecida seria muito mais útil do que solicitar coisas no stdin (especialmente em um programa não interativo).

Receio não saber de forma alguma. @reaperhulk?

Existem maneiras de impedir que o OpenSSL faça isso, mas não tenho certeza se elas são expostas por pyOpenSSL. Onde as solicitações chamam pyopenssl para carregar o certificado do cliente? Eu posso cavar um pouco.

@reaperhulk É feito em urllib3, aqui .

Também fazemos algo muito semelhante para o stdlib, que será um problema totalmente separado.

Portanto, podemos fazer isso com PyOpenSSL usando um patch como este . Na versão stdlib, precisamos usar load_cert_chain com uma senha.

Este problema foi resolvido? No momento, estou correndo para isso enquanto tento me conectar a um servidor Apache.

Não foi.

E quanto aos contêineres formatados (e criptografados) PKCS # 12 que podem conter um certificado / chave de cliente? Isso se enquadraria na mesma solicitação de recurso?

@mikelupo Yup.

@telam @mikelupo
Estou com o mesmo problema e pesquisei bastante, finalmente resolvi usando pycurl.
Na minha situação, eu uso o openssl para converter meu arquivo .pfx em um arquivo .pem que contém certificado e chave (criptografado com frase secreta) e, em seguida, invoco o código a seguir.

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, por segurança, é melhor não fazer código fixo para pass phrase

Claro. Dito isso, o problema não é realmente que uma frase secreta seja necessária - é que o OpenSSL faz seu programa travar enquanto espera que alguém digite uma frase secreta em stdin, mesmo no caso de um programa não interativo, GUI ou remoto.

Quando uma frase-senha é necessária e nenhuma é fornecida, uma exceção deve ser levantada em seu lugar.

se você usar uma frase-senha padrão '' para a chave, o openssl não travará.
ele retornará um texto de senha incorreto. você pode alterar imediatamente o seu fluxo py
para então notificar o usuário sem essa aparente paralisação

qualquer plano para adicionar este recurso

Queremos adicioná-lo, mas não temos uma programação para adicioná-lo no momento.

@botondus Acho que encontrei uma maneira mais simples de fazer isso com a biblioteca de solicitações. Estou documentando isso para outras pessoas que estão enfrentando o problema.

Presumo que você tenha um certificado .p12 e uma senha longa para a chave.

Gere certificado e chave 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

Bem, ainda não terminamos e precisamos gerar a chave que não requer a senha do PEM toda vez que precisar se comunicar com o servidor.

Gerar chave sem senha.

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

Agora, você terá certificate.pem e plainkey.pem , ambos os arquivos necessários para se comunicar com a API usando solicitações.

Aqui está um exemplo de solicitação usando esses certificados e chaves.

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 isto ajude:

cc @kennethreitz @Lukasa @ sigmavirus24

Ouvi boatos de que a Amazon faz exatamente isso, internamente.

Também estou enfrentando esse problema. Minha preocupação é que não quero armazenar a chave privada simples no sistema de arquivos (pode haver o risco de ser roubada por outras pessoas). Portanto, em minha opinião, a maneira mais extensível de implementar isso é oferecer suporte ao uso de algo como PEM encoded string of private key vez do caminho do arquivo para especificar a chave privada. Apenas deixei a criptografia / descriptografia da chave privada / certificado para os desenvolvedores a seu favor.
Depois de ler o código-fonte das solicitações, parece que não é muito fácil de implementar, pois as solicitações dependem da lib ssl do python, que só oferece suporte a certificado / arquivo de chave privada. Só estou me perguntando se poderíamos usar pyopenssl em vez de python stdlib. pyopenssl tem um wrapper de conexão openssl, consulte: https://pyopenssl.readthedocs.io/en/latest/api/ssl.html#connection -objects. Assim, podemos usar o objeto 'pkey' como chave privada em vez do caminho do arquivo.

Requests já oferece suporte a PyOpenSSL, desde que ele e algumas outras dependências necessárias estejam instaladas. No entanto, isso nunca se tornará obrigatório: é importante para nós que trabalhemos bem com a biblioteca padrão.

Em uma versão futura, teremos suporte para passar objetos SSLContext para urllib3 a fim de lidar com TLS: isso habilitará essa função.

Para aqueles que enfrentam esse problema, até que as solicitações adicionem a capacidade de passar um ssl.SSLContext / OpenSSL.SSL.Context para urllib3, aqui está uma solução alternativa que realmente suporta o uso de certificado / arquivo de chave criptografado (requer que PyOpenSSL seja instalado e usado em vez da biblioteca padrão ssl, que deve ser se estiver 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 : Isso também funciona para arquivos PKCS # 12 ou é apenas PEM?

@Lukasa : O caso PKCS # 12 realmente deveria ser tratado aqui, ou devo abrir uma edição separada para isso?

PKCS # 12 é um problema mais complicado, mas basicamente você precisará fazer o que for necessário para personalizar seu SSLContext.

@Lukasa : Eu estava pensando mais em fornecer uma boa API de alto nível nas solicitações. Por exemplo, basta fornecer o nome de arquivo client_cert.p12 e a senha por meio do parâmetro de palavra-chave cert=... .

@vog Qual código você acredita que seria necessário para fazer isso funcionar?

@Lukasa Não tenho certeza sobre os detalhes internos de requests , então talvez eu subestime o que já está lá, mas acho que uma das seguintes coisas precisa ser feita:

  • Ou temos uma maneira de fornecer um nome de arquivo PKCS # 12 diretamente para as camadas inferiores (urllib3, etc.). E talvez a senha também. (Porque não conheço ninguém que queira que uma biblioteca de URL peça ao administrador para inserir interativamente sua senha PKCS # 12 em uma ferramenta que roda no lado do servidor.)
  • Se isso for impossível, precisaremos converter PKCS # 12 (+ senha) para PEM e, em seguida, fornecê-los aos níveis inferiores. Isso é feito com algumas chamadas diretamente para a ligação OpenSSL . No entanto, o resultado é o certificado PEM como uma string, e eu ainda não encontrei uma maneira de fornecer o PEM (não criptografado) como string para as camadas inferiores (exceto talvez usando o wrapper OpenSSL / python "ssl" "buffer", por exemplo, wrap_bio , mas isso só está disponível nas versões mais recentes do Python 3, não do Python 2).
  • Portanto, se isso também for impossível, não precisaremos apenas converter PKCS # 12 em PEM, mas também criar um arquivo temporário contendo os dados PEM (não criptografados).

Observe que o último ponto é o que essencialmente estou fazendo no momento, mas não gosto disso. Por que não posso fornecer uma string simples para OpenSSL contendo o certificado? Além disso, por que não posso simplesmente passar o nome de arquivo e a senha PKCS # 12 para as camadas inferiores?

Vou marcar @reaperhulk como um especialista em OpenSSL, mas meu entendimento é que não há APIs para OpenSSL para carregar certificados de formato PKCS # 12 para certificados de cliente. Isso significa que precisamos absolutamente converter para PEM. Fazê-lo na memória certamente é possível, mas a certa altura me pergunto se não queremos apenas considerar esse especialista o suficiente para delegar a qualquer SSLContext que você nos passar.

@Lukasa Obrigado por levar este problema a sério. Desculpe se isso parece muito técnico, mas essencialmente é apenas isso:

Você deseja acessar um serviço por meio de um certificado de cliente. Quase em todos os lugares, você obtém isso como um arquivo e uma senha (onde o arquivo é codificado em PKCS # 12). Na maioria das APIs, como a biblioteca padrão Java, você simplesmente fornece o nome do arquivo e a senha e pronto.

No entanto, em Python, isso é complicado como o inferno.

É por isso que quase ninguém faz isso. Em vez disso, eles convertem o arquivo e a senha em um arquivo PEM manualmente, por meio do OpenSSL, e usam esse arquivo. Esta é a sobrecarga administrativa para cada usuário de tal aplicativo. Porque eles não podem simplesmente nomear o arquivo (PKCS # 12) e a senha.

Acho que a biblioteca requests deve torná-lo pelo menos tão simples quanto em Java.

requests já faz um ótimo trabalho de simplificação de APIs estúpidas e complexas, e o caso de uso do PKCS # 12 é apenas outro exemplo de API estúpida e complexa.

o caso de uso PKCS # 12 é apenas outro exemplo de uma API complexa estúpida.

Sim, eu não discordo disso: eu ficaria totalmente feliz em ter algum tipo de solução para suporte a PKCS # 12 em algum lugar da pilha.

O que estou tentando entender é qual código é necessário para fazer isso funcionar e, como resultado, onde ele deve ser colocado. Meu raciocínio é o seguinte:

  1. De modo geral, Requests só adiciona à superfície da API se houver utilidade substancial ao fazer isso (ou seja, se for usado por muitas pessoas ou muito usado por algumas) e se o que estamos fazendo for difícil de acertar ou tem casos extremos sutis.
  2. Normalmente, o suporte a PKCS # 12 contaria como uma adição à superfície da API, mas se não mudar a sintaxe de cert= alguma (apenas amplia as coisas que suportará) e não regredir o comportamento ( ou seja, podemos dizer com segurança a diferença entre arquivos PKCS # 12 e arquivos PEM, ou podemos facilmente apenas processar por meio de ambas as cadeias de lógica), eu diria que conta como uma alteração suficientemente pequena na superfície que provavelmente vale a pena .
  3. No entanto, existem outros lugares onde isso pode ir. Por exemplo, no nível do Adaptador de Transporte, ou como ajudante no Conjunto de Ferramentas de Solicitações ou qualquer outra coisa.

Isso significa que quero pesar o quão sutil isso é, quão complexo é o código, se ele requer dependências extras e, em seguida, usar essas informações para descobrir onde melhor colocar o código. Por exemplo, eu tenho uma _suspição_ agora que a biblioteca padrão não pode lidar com PKCS # 12, o que significaria que _n best_ Requests só seriam capazes de usar PKCS # 12 com o [security] extra instalado. Em um caso ainda pior, podemos não ter as funções disponíveis em nenhuma ligação OpenSSL, caso em que teremos que fazer algumas coisas realmente malucas para fazê-lo funcionar. É por isso que eu queria que @reaperhulk pesasse: ele provavelmente será capaz de esclarecer isso para nós mais rápido do que eu poderia fazer a pesquisa.

Eu gostaria de ver este suporte adicionado: Eu só preciso que algumas pessoas que saibam qual é o escopo do trabalho comentem aqui e me digam o quão grande é a montanha que precisamos mover.

Mais um detalhe para uma implementação PKCS # 12: versões mais antigas das ligações Python OpenSSL falham se a senha for fornecida como objeto unicode vez de string de bytes. Portanto, deve ser convertido antes de passá-lo para load_pkcs12() assim:

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

Um conversor completo pode ter a seguinte aparência, onde pkcs12_data deve ser uma string de bytes com dados binários, enquanto password pode ser uma string de bytes ou 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

A discussão sobre PKCS # 12 parece-me estar além do escopo do problema inicial, uma vez que a questão em questão é se as solicitações devem suportar PKCS # 12 nativamente. Eu votaria se ele tivesse seu próprio problema, mas obviamente isso depende do pessoal responsável.

Dito isso, como uma solução alternativa que não requer arquivos temporários não criptografados, o método OpenSSL.crypto.dump_privatekey tem um parâmetro de frase secreta opcional, então você poderia obter uma cópia da chave privada criptografada no formato PEM dessa forma. Isso reduziria isso ao problema de PEM criptografado com o qual começamos.

Alternativamente, você também pode escrever um hack semelhante ao que sugeri antes de usar o método use_privatekey de OpenSSL.SSL.Context . No topo da minha cabeça (não testado) 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

Em seguida, basta usar requests.get etc sem especificar um certificado, uma vez que já é manipulado no construtor.

Revendo este tópico agora. Reformulação do original:

dado um certificado de cliente formatado PEM criptografado, as solicitações podem lidar com o fornecimento de uma senha?

Como isso está nas bibliotecas padrão atuais, seria ótimo integrar essa opção. Isso seria extremamente valioso para considerações de segurança corporativa (onde nossos certificados são criptografados e devem permanecer assim).

Neste ponto, isso pode ser feito passando um contexto SSL personalizado para urllib3 usando um adaptador de transporte. Isso pode fazer tudo o que o contexto SSL da biblioteca padrão permitir. Você pode ver um exemplo de passagem de um contexto personalizado aqui .

Consegui usar .pfx e .p12 com solicitações, convertendo-os em .pem usando um arquivo temporário. Consulte https://gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068

Se houver algum interesse, posso enviar um PR. Seria ótimo evitar o arquivo temporário e o gerenciador de contexto. Avise.

Não é provável que isso seja mesclado, infelizmente, mas observe que agora você pode passar um contexto PyOpenSSL diretamente para Requests por meio de adaptadores de transporte, portanto, você pode descobrir que pode contornar esse problema.

Não é provável que isso seja mesclado, infelizmente, mas observe que agora você pode passar um contexto PyOpenSSL diretamente para Requests por meio de adaptadores de transporte, portanto, você pode descobrir que pode contornar esse problema.

desculpe por estar confuso, mas você está dizendo que o suporte pfx / p12 em geral provavelmente não será mesclado? (supondo que seja feito da maneira certa, por meio do contexto, etc.). Fico feliz em tentar, mas obviamente não vale a pena se não for mesclado.

Eu acredito que o "provavelmente não será mesclado" foi sobre a solução de arquivo temporário.

@erikbern Para ser claro, fico feliz em abordar e mesclar qualquer solução que funcione de forma consistente. Por exemplo, uma solução para usar PKCS # 12 por meio do módulo contrib PyOpenSSL em urllib3 seria aceitável.

No entanto, uma solução de arquivo temporário não é aceitável (conforme observado por @vog). Isso significa que é improvável que o suporte para PKCS # 12 funcione com a biblioteca padrão, já que o módulo da biblioteca padrão ssl não expõe nenhum suporte para ele e, portanto, não será suportado em todas as configurações de Requests.

Soa bem. Também concordo que o arquivo temporário é ruim, pois há um risco de segurança ao armazenar chaves descriptografadas no disco. Posso dar uma olhada nisso na próxima semana. Obrigado pelo aviso sobre o módulo ssl - se a limitação estiver fora de requests então, obviamente, ficará mais complicado

Eu olhei para ele e o módulo ssl adicionou um argumento cadata onde você pode passar dados pem como uma string bruta: https://docs.python.org/3/library/ssl.html # ssl.SSLContext.load_verify_locations

Teríamos que corrigir o urllib3 em vários lugares para fazer isso funcionar, então posso começar por aí

@erikbern Para ser claro, quase qualquer solução como essa funcionará melhor apenas passando um objeto SSLContext configurado apropriadamente para urllib3 usando um TransportAdapter .

https://github.com/kennethreitz/requests/issues/2519 parece ser idêntico a este problema, então eles provavelmente devem ser mesclados

Qualquer atualização sobre este problema estou tentando usar um certificado de cliente que é criptografado por senha e não consigo fazê-lo funcionar. Devo procurar outras opções além de solicitações? Você poderia responder o mais rápido possível.

Nós documentamos isso? Eu sinto que este é o nosso recurso mais solicitado.

Acredito que este tópico tenha começado em 2013 e não encontrei nenhuma resolução explicada até o final. Vocês forneceram uma opção para fornecer a senha? ou ainda está em andamento?

Estou tentando usar solicitações em um produto de segurança de aplicativo que estou construindo. Portanto, qualquer indicação será útil

@AnoopPillai Você verificou este comentário? https://github.com/requests/requests/issues/1573#issuecomment -188125157

Sim, eu li este comentário, isso é uma solução alternativa, mas no meu caso, não quero convertê-lo em 2 arquivos de certificado, pois isso terá que ser feito fora do meu aplicativo. Além disso, usamos um cofre para armazenar a senha do arquivo .pem criptografado.

Essa senha é recuperada dinamicamente pelo aplicativo em tempo de execução, sem codificação.

@AnoopPillai Tudo bem.

@kennethreitz Não, não o documentamos.

@AnoopPillai Sim, funciona bem. Você só precisa usar alguns ganchos de nível inferior. Nesse caso, permitimos que você passe SSLContext para urllib3 diretamente no nível do adaptador de transporte. Isso permite que você acesse as funções subjacentes que permitirão que você forneça uma frase-senha ou função frase-senha. É assim que recomendamos que você apoie isso.

@AnoopPillai solução alternativa usando um arquivo temporário que achei útil: https://gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068

Obrigado Lukasa por me informar que existe uma maneira de fazer isso.
Eu sou muito novo no python e estou usando a versão 3.6. Você poderia me orientar onde posso encontrar opções como cifras para passar a senha do certificado do cliente.
@Erikbern Eu não nela hoje. Obrigado pela resposta.

@AnoopPillai Você vai querer load_cert_chain .

@Lukasa , você se importaria de documentar? deve levar apenas alguns minutos (estou pensando na seção avançada, ou talvez em uma nova seção avançada avançada)

Desculpe pessoal, minha falta de experiência com python pode ser a razão, mas eu não consigo modificar o código que Lukasa explicado acima. Meu código é:

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

e recebo um erro
TypeError: create_urllib3_context () obteve um argumento de palavra-chave inesperado 'load_cert_chain'

Isso é um erro, sim. Você deseja chamar create_urllib3_context e obter seu valor de retorno e, em seguida, chamar load_cert_chain no objeto retornado. Experimente brincar com essas funções no interpretador interativo para ver como funcionam.

O urllib3..util.ssl_.py que está instalado no meu mac não tem a opção de senha mais recente.
este é o código

    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)

A opção de senha está faltando. Como faço para atualizar ssl_.py para obter a versão mais recente?

@AnoopPillai Você não. Chame a função sem argumentos e, em seguida, chame load_cert_chain no objeto retornado. Você não precisa fazer alterações no urllib3.

Para ser claro, assim:

ctx = create_urllib3_context()
ctx.load_cert_chain(your_arguments_here)

Vamos documentar isso :)

@ erikbern Tentei sua solução de arquivo temporário, mas recebi o seguinte erro:

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

Abaixo está o meu 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())

desculpe, é difícil saber por que está quebrando olhando para o seu comentário. Eu usei para vários aplicativos e não tive nenhum problema

@Lukasa eu tentei com aquela mudança de código (código colado abaixo) e acabei com o mesmo erro que eu obtive com o método tempfile.

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 pode ser um problema de configuração no meu laptop. Eu uso um 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: 

Você acha que há algo de errado com o certificado?

Qual é a saída de python -m requests.help ?

@Lukasa O resultado é:

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

Remova o ponto de interrogação de sua linha 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
}

Portanto, o erro que você está obtendo é causado por não podermos validar o certificado TLS do servidor. certifi e OpenSSL parecem corretos, então presumo que o servidor esteja se comportando mal. Qual servidor você está tentando acessar?

O aplicativo é implantado no Cloud AWS. Mas, quando chamamos a API, ela primeiro vai para o OSB, que autentica o certificado e, em seguida, encaminha a solicitação para a AWS.
Eu uso o mesmo certificado e usando o postman ou meu código Ruby a API funciona bem

Você precisa de um certificado de raiz específico? Você pode fornecer o nome do host que está sendo contactado para mim?

O URL do host sem o caminho é https://credit-cards-accounts-qa.kdc.capitalone.com
Este é um endpoint interno

Sim, então não consigo ver o que está acontecendo lá. Você pode executar openssl s_client -showcerts -connect credit-cards-accounts-qa.kdc.capitalone.com:443 e fornecer a saída completa?

Excluído

Parece que você não está usando um certificado de raiz confiável globalmente. Onde está o certificado raiz para esse serviço?

Não estou usando nenhum outro certificado. Não tenho certeza se nos bastidores algum certificado raiz está sendo usado, há uma maneira de eu descobrir?

Sim, as ferramentas de desenvolvedor do Chrome informarão a cadeia de certificação completa que está usando.

você provavelmente não quer postar algum certificado interno online para que ninguém veja ...

@erikbern Esta é uma informação pública. Você pode obter o mesmo resultado executando o mesmo comando.

@SethMichaelLarson Do perfil do GitHub de @erikbern "Chief Troll Officer". Talvez eles estivessem apenas trollando?

@erikbern @ sigmavirus24 Ah! Eu não sabia com quem estava falando. Continuar! 🙇

Não consigo ver nada, exceto o certificado sha-1 quando corro do carteiro
Talvez eu precise adicionar isso de alguma forma ao Pycharm

Se você literalmente navegar até o site no Chrome, isso deve ser suficiente.

@SethMichaelLarson executando qual comando? Para sua informação, o comentário foi excluído agora, mas havia um blob inteiro BEGIN CERTIFICATE aqui antes ... afaik você não quer compartilhar isso online

@erikbern Essa era apenas a chave pública para o cert ...

Os certificados são dados públicos; eles são transmitidos em texto simples pela rede em cada tentativa de conexão.

Eu fui para a cadeia de certificados e só encontrei um certificado Sha-1 e o certificado .Pem que estou usando para acessar a API

@AnoopPillai Recebi seu código de exemplo de 1º de setembro funcionando sem problemas usando um arquivo pem do lado do cliente com senha. Parece que o host está usando um certificado regular. Com @Lukasa muito obrigado!

Infelizmente, ainda estou tendo problemas, mesmo com o método de arquivo temporário. Posso usar o .pfx no Google Postman e não tenho problemas de autenticação (portanto, sei que minhas credenciais funcionam), mas ainda estou obtendo 401s com Python. Infelizmente, o cara do suporte da empresa com a qual estou lidando não me ajudou muito - alguém tem alguma sugestão para solucionar o problema?

Neste estágio, estou realmente inseguro sobre onde procurar o problema, já que outras pessoas estão relatando sucesso com o método de arquivo temporário e eu ainda não tive nenhuma resposta de sua equipe de gerenciamento de certificação.

Qualquer conselho seria muito apreciado - por favor, deixe-me saber se eu posso fornecer alguma informação adicional para tornar isso mais fácil.

Obrigado :)

Apenas uma sugestão, você tentou converter PFX em PEM? Além disso, se o servidor também estiver usando um nome de usuário / senha, você precisará adicionar essa solicitação get / post usando auth = (). Tenho usado a abordagem class DESAdapter(HTTPAdapter) acima há várias semanas sem problemas, usando um arquivo PEM protegido por senha.

@ideasean Ainda obtendo credenciais inválidas. Devo apontar o load_cert_chain para um arquivo .pem gerado pela função pfx_to_pem escrita para o método Temp File, correto? Ele contém a chave privada e o certificado.

Como o .pfx funciona com o Postman, mas não é autenticado aqui, isso pode significar que algo está errado no processo de conversão?

Não usei o método de arquivo temporário. Eu usei a abordagem DESAdapter praticamente como escrito no post de AnoopPillai em Set1 acima, começando com -

Eu tentei com essa mudança de código (código colado abaixo) e acabei com o mesmo erro que obtive com o método tempfile.

Não posso falar sobre o processo de conversão, mas talvez um bom teste seja tentar usar o arquivo pem convertido com o Postman?

Observe também que usei a abordagem acima porque meu arquivo pem foi criptografado / protegido por senha e as solicitações Python atualmente não suportam isso. Se o seu pem acabar não sendo protegido por senha, você poderá usar solicitações nativas por link (mas terá um certificado desprotegido em seu sistema de arquivos).

@ideasean Eu este método e obtive um arquivo .pem com Atributos e Certificado do Bag, bem como um arquivo .pem com Atributos do Bag e uma Chave Privada Criptografada.

Ainda obtendo credenciais inválidas, acho que tentarei passar os certificados no Postman e ver se funcionam, mas não consigo descobrir por que aparentemente não consigo desempacotar este .pfx corretamente

Eu também tentei o comando openssl openssl pkcs12 -in <my_pfx>.pfx -out certificate.cer -nodes , e ainda me dá um erro 401 quando mudo para ele: context.load_cert_chain('certificate.cer')

Instalei o .cer acima mencionado e o Postman nem mesmo pede para usá-lo quando faço a chamada API (ao contrário do pop-up quando pede para usar o .pfx), não tenho certeza de que outra forma posso fazer com que use esse certificado específico já que não há painel "Certificados" nas configurações, como dizem os documentos.

Você pode estar usando a versão do navegador do Postman, que não inclui o painel cert, desabilitação de validação SSL etc. Experimente o cliente completo para alterar as configurações do certificado. Você pode querer continuar esta discussão em um tópico diferente, pois estamos um pouco fora do assunto.

@ mkane848 viu seu comentário original onde você estava recebendo um ValueError: String expected . Você pode querer verificar https://github.com/pyca/pyopenssl/issues/701 e https://github.com/shazow/urllib3/issues/1275.

Eu uso meu pem privado com uma senha usando este:

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 sua informação, acabei de implementar o suporte PKCS # 12 para requests como uma biblioteca separada:

O código é uma implementação limpa : ele não usa patching do macaco nem arquivos temporários. Em vez disso, um TransportAdapter personalizado é usado, o que fornece um SSLContext personalizado.

Qualquer feedback e melhorias são bem-vindos!

Claro, eu gostaria que requests fornecesse essa funcionalidade diretamente, mas até que isso aconteça, esta biblioteca irá aliviar a dor.

Seria muito bom se pudéssemos simplesmente fazer isso:

~~~
cert = ("cert.pem", "key.pem", "somepassphrase") # cert / chave separada

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

~~~

... mesmo que funcionasse apenas no python 3.3+. Isso seria apenas uma pequena adição à superfície da API.

AFAICS, isso significaria uma pequena mudança no urllib3 para que HTTPSConnection aceite um argumento opcional password ; isso é passado por ssl_wrap_socket , terminando com:

~if certfile:se a senha não for nenhuma:context.load_cert_chain (certfile, keyfile, senha)outro:context.load_cert_chain (certfile, keyfile)~

Então seria compatível com versões anteriores, levantando uma exceção apenas se você tentar usar uma senha longa de chave privada em uma plataforma mais antiga que não a suporta.

Observe que o adaptador contrib/pyopenssl.py já suporta este argumento extra para load_cert_chain , assim como o python 2.7 .


À parte: estou usando o AWS KMS para gerenciar dados "secretos", portanto, carregaria a senha da chave no tempo de execução do KMS, e não codificaria no aplicativo.

Eu, pessoalmente, não seria contra essa mudança, pois acho que melhoraria muito nossa interface de usuário para muitos usuários em todo o quadro.

@ sigmavirus24 alguma

@candlerb @kennethreitz Seria aceitável incluir o caso PKCS # 12 nessa API também?

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

A distinção pode ser pela extensão do arquivo ( *.p12 versus *.pem ), ou olhando para os primeiros bytes desse arquivo.

Não tenho problemas em permitir que as solicitações tomem um pkcs # 12, contanto que isso possa ser feito com segurança - e na minha opinião, isso impede a gravação da chave privada extraída em um arquivo temporário.

Buscando Python pkcs # 12, encontrei:

  • O código de alguém que escreve a chave privada
  • Algum outro código que eu acho que depende de pyOpenSSL para ler no pkcs # 12. Ele retorna o certificado e a chave como itens de dados.

Então, fazendo isso, acho que seria necessário conectar as coisas de forma que a própria chave / certificado seja passada para o OpenSSL, não os nomes de arquivo que contêm essas coisas. Isso parece uma mudança muito maior.

Se isso for muito difícil, significa apenas que o usuário deve converter o pkcs # 12 em PEM off-line, o que é bastante simples (e pode ser documentado).

@candlerb Como escrevi em meu comentário anterior (https://github.com/requests/requests/issues/1573#issuecomment-348968658), já criei uma implementação limpa que se integra bem com requests .

Portanto, os problemas que você está descrevendo já foram resolvidos.

No momento, minha implementação adiciona novos argumentos de pkcs12_* keywords, para ficar o máximo possível fora do caminho.

Mas acho que deveria ser integrado ao argumento de palavra-chave cert , e minha pergunta é:

  • Isso seria aceitável em geral?
  • Minha proposta concreta cert=('keycert.p12', None, 'somepassphrase') seria aceitável?
  • Como devemos distinguir entre PKCS # 12 e PEM? (Por sufixo do nome do arquivo ou pelo conteúdo do arquivo?)

(Além disso, eu prefiro ver isso em requests vez de minha biblioteca requests_pkcs12 separada. Mas, dada a idade deste problema, tenho pouca esperança de que isso vá para o topo em breve. No entanto , se houvesse uma declaração concreta sobre o tipo de implementação desejada, talvez eu pudesse ajustar minha implementação de acordo e propor uma solicitação pull.)

Então, algumas coisas:

  1. Não acho que devemos pegar a palavra-chave cert e expandi-la assim. São dados implicitamente estruturados e as pessoas já estão confusas com as tuplas da palavra-chave files . Acho que continuar com um padrão sabidamente ruim é tolice.

  2. Acho que, se alguma coisa, o adaptador pkcs12 deve ser modificado e enviado para o conjunto de ferramentas de solicitações. Acho que seria melhor modificá-lo para criar ssl_context uma vez em vez de armazenar a senha pkcs12 na memória desse objeto.

Acho que ainda há outro trabalho que precisa ser feito antes de podermos lidar com isso no caso mais geral, não importa o que aconteça e isso inclui determinar a API certa para isso para Requests 3.0.

@ sigmavirus24 Obrigado pelo feedback.

  1. Ok, então vamos manter as pkcs12_* palavras-chave separadas.
  2. Sim, definitivamente vale a pena melhorar. Criei uma entrada do rastreador de problemas para isso: https://github.com/m-click/requests_pkcs12/issues/2

Como a classe PKCS # 12 TransportAdapter seria incluída em requests ? Essa classe seria simplesmente adicionada a requests , ou existe outra maneira de incluí-la em um nível "mais profundo", para que possa ser usada sem qualquer request()/get()/... wrappers e sem ter que carregar explicitamente isso adaptador?

Minha organização precisa usar certificados PKCS12 e está disposta a fazer as melhorias necessárias em sua biblioteca para fazer isso. Descriptografar os arquivos .p12 em arquivos .pem é considerado muito arriscado e adiciona uma etapa extra a ser tratada. Gostaríamos de adicionar funcionalidade para gerar e fornecer um ssl_context apropriado para uma determinada sessão. Esta ainda é uma funcionalidade que sua equipe estaria disposta a aceitar, presumindo que fosse implementada corretamente?

Apenas um lembrete rápido: uma implementação limpa já foi fornecida por nossa empresa, mas como um adaptador separado: https://github.com/m-click/requests_pkcs12

Sinta-se à vontade para reformatá-lo em uma solicitação pull para as próprias solicitações.

Ao longo do caminho, você pode querer corrigir um pequeno problema: o ssl_context não deve ser mantido na memória por uma sessão inteira, mas o mais breve possível, apenas para uma única conexão. Veja também:

Caso você conserte ao longo do caminho, seria bom se você pudesse fornecê-lo como uma pequena solicitação de pull para https://github.com/m-click/requests_pkcs12 , além das próprias solicitações.

Dessa forma, todas as pessoas que estão usando a biblioteca requests_pkcs12 agora se beneficiariam automaticamente dessa melhoria também, sem ter que mudar para a nova API (então aprimorada) para as próprias solicitações.

Sim, https://github.com/m-click/requests_pkcs12 funcionou para mim e fez exatamente o que eu queria. Muito obrigado @vog ! Espero que os pedidos possam apoiar isso eventualmente.

Também vou agradecer a @vog por sua implementação, funciona exatamente como o esperado e resolve o problema de manter cert / key em armazenamentos não seguros como o S3 no meu caso. Felizmente, isso pode chegar a requests .

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

cnicodeme picture cnicodeme  ·  3Comentários

brainwane picture brainwane  ·  3Comentários

NoahCardoza picture NoahCardoza  ·  4Comentários

JimHokanson picture JimHokanson  ·  3Comentários

eromoe picture eromoe  ·  3Comentários