python - requests - sslerror ssl certificate_verify_failed certificate verify failed(_ ssl c 777
openssl, python solicita error: "error de verificación del certificado" (3)
Si ejecuto el siguiente comando desde mi caja de desarrollo:
$ openssl s_client -connect github.com:443
Obtengo la siguiente última línea de salida:
Verify return code: 20 (unable to get local issuer certificate)
Si trato de hacer esto con las solicitudes, obtengo otra solicitud fallida:
>>> import requests
>>> r = requests.get(''https://github.com/'', verify=True)
Con una excepción planteada:
SSLError: [Errno 1] _ssl.c:507: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
También puedo ejecutar el primer comando con el indicador de verificación y obtener un resultado similar:
$ openssl s_client -connect github.com:443 -verify 9
...
Verify return code: 27 (certificate not trusted)
Básicamente, esto me dice que hay un problema con los certificados. Puedo especificar un certificado específico con ambos métodos y funcionará:
$ openssl s_client -connect github.com:443 -CAfile /etc/ssl/certs/DigiCert_High_Assurance_EV_Root_CA.pem -verify 9
...
Verify return code: 0 (ok)
y:
>>> r = requests.get(''https://github.com/'', verify=''/etc/ssl/certs/DigiCert...pem'')
<Response [200]>
Entonces, a mi pregunta, ¿qué es exactamente lo que está mal aquí? ¿Las solicitudes / openssl ya no deberían saber dónde encontrar certificados válidos?
Otra información:
- Python == 2.7.6
- peticiones == 2.2.1
- openssl 0.9.8h
También, sé que el método verify=False
al método requests.get
también funcionará, pero quiero verificar.
EDITAR
He confirmado que, como lo indicó @Heikki Toivonen en una respuesta, especificar el indicador -CAfile para la versión de openssl que estoy ejecutando funciona.
$ openssl s_client -connect github.com:443 -CAfile `python -c ''import requests; print(requests.certs.where())''`
...
Verify return code: 0 (ok)
Así que no hay nada malo con la versión de openssl que estoy ejecutando, y no hay nada malo con el archivo cacert.pem predeterminado que las solicitudes proporcionan.
Ahora que sé que openssl está destinado a funcionar de esa manera, que el archivo de CA o el lugar para encontrar certificados debe especificarse, me preocupa más que las solicitudes funcionen.
Si corro:
>>> r = requests.get(''https://github.com/'', verify=''path to cacert.pem file'')
Todavía estoy recibiendo el mismo error que antes. Incluso intenté descargar el archivo cacert.pem desde http://curl.haxx.se/ca y aún no funcionó. las solicitudes solo parecen funcionar (en esta máquina específica) si especifico un archivo de certificado de proveedor específico.
Una nota al margen: en mi máquina local todo funciona como se espera. Sin embargo, hay varias diferencias entre las dos máquinas. Hasta ahora no he podido determinar cuál es la diferencia específica que causa este problema.
Si ejecuto el siguiente comando desde mi caja de desarrollo:
$ openssl s_client -connect github.com:443
Obtengo la siguiente última línea de salida:
Verify return code: 20 (unable to get local issuer certificate)
Le falta DigiCert High Assurance EV CA-1
como raíz de confianza:
$ openssl s_client -connect github.com:443
CONNECTED(00000003)
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV CA-1
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
0 s:/businessCategory=Private Organization/1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=Delaware/serialNumber=5157550/street=548 4th Street/postalCode=94107/C=US/ST=California/L=San Francisco/O=GitHub, Inc./CN=github.com
i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV CA-1
1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV CA-1
i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA
---
Server certificate
...
Start Time: 1393392088
Timeout : 300 (sec)
Verify return code: 20 (unable to get local issuer certificate)
Descargue DigiCert High Assurance EV CA-1
de DigiCert Trusted Root Authority Certificates :
$ wget https://www.digicert.com/CACerts/DigiCertHighAssuranceEVCA-1.crt
--2014-02-26 00:27:50-- https://www.digicert.com/CACerts/DigiCertHighAssuranceEVCA-1.crt
Resolving www.digicert.com (www.digicert.com)... 64.78.193.234
...
Convertir el certifcado codificado DER a PEM:
$ openssl x509 -in DigiCertHighAssuranceEVCA-1.crt -inform DER -out DigiCertHighAssuranceEVCA-1.pem -outform PEM
Luego, -CAfile
con OpenSSL a través del -CAfile
:
$ openssl s_client -CAfile DigiCertHighAssuranceEVCA-1.pem -connect github.com:443
CONNECTED(00000003)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV CA-1
verify return:1
depth=0 businessCategory = Private Organization, 1.3.6.1.4.1.311.60.2.1.3 = US, 1.3.6.1.4.1.311.60.2.1.2 = Delaware, serialNumber = 5157550, street = 548 4th Street, postalCode = 94107, C = US, ST = California, L = San Francisco, O = "GitHub, Inc.", CN = github.com
verify return:1
---
Certificate chain
0 s:/businessCategory=Private Organization/1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=Delaware/serialNumber=5157550/street=548 4th Street/postalCode=94107/C=US/ST=California/L=San Francisco/O=GitHub, Inc./CN=github.com
i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV CA-1
1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV CA-1
i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIHOjCCBiKgAwIBAgIQBH++LkveAITSyvjj7P5wWDANBgkqhkiG9w0BAQUFADBp
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSgwJgYDVQQDEx9EaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBDQS0xMB4XDTEzMDYxMDAwMDAwMFoXDTE1MDkwMjEyMDAwMFowgfAxHTAb
BgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYBBAGCNzwCAQMTAlVT
MRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQFEwc1MTU3NTUwMRcw
FQYDVQQJEw41NDggNHRoIFN0cmVldDEOMAwGA1UEERMFOTQxMDcxCzAJBgNVBAYT
AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2Nv
MRUwEwYDVQQKEwxHaXRIdWIsIEluYy4xEzARBgNVBAMTCmdpdGh1Yi5jb20wggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDt04nDXXByCfMzTxpydNm2WpVQ
u2hhn/f7Hxnh2gQxrxV8Gn/5c68d5UMrVgkARWlK6MRb38J3UlEZW9Er2TllNqAy
GRxBc/sysj2fmOyCWws3ZDkstxCDcs3w6iRL+tmULsOFFTmpOvaI2vQniaaVT4Si
N058JXg6yYNtAheVeH1HqFWD7hPIGRqzPPFf/jsC4YX7EWarCV2fTEPwxyReKXIo
ztR1aE8kcimuOSj8341PTYNzdAxvEZun3WLe/+LrF+b/DL/ALTE71lmi8t2HSkh7
bTMRFE00nzI49sgZnfG2PcVG71ELisYz7UhhxB0XG718tmfpOc+lUoAK9OrNAgMB
AAGjggNUMIIDUDAfBgNVHSMEGDAWgBRMWMsl8EFPUvQoyIFDm6aooOaS5TAdBgNV
HQ4EFgQUh9GPGW7kh29TjHeRB1Dfo79VRyAwJQYDVR0RBB4wHIIKZ2l0aHViLmNv
bYIOd3d3LmdpdGh1Yi5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG
AQUFBwMBBggrBgEFBQcDAjBjBgNVHR8EXDBaMCugKaAnhiVodHRwOi8vY3JsMy5k
aWdpY2VydC5jb20vZXZjYTEtZzIuY3JsMCugKaAnhiVodHRwOi8vY3JsNC5kaWdp
Y2VydC5jb20vZXZjYTEtZzIuY3JsMIIBxAYDVR0gBIIBuzCCAbcwggGzBglghkgB
hv1sAgEwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9z
c2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4A
eQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQA
ZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUA
IABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAA
YQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcA
cgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIA
aQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQA
ZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMH0G
CCsGAQUFBwEBBHEwbzAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
Y29tMEcGCCsGAQUFBzAChjtodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
aUNlcnRIaWdoQXNzdXJhbmNlRVZDQS0xLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqG
SIb3DQEBBQUAA4IBAQBfFW1nwzrVo94WnEUzJtU9yRZ0NMqHSBsUkG31q0eGufW4
4wFFZWjuqRJ1n3Ym7xF8fTjP3fdKGQnxIHKSsE0nuuh/XbQX5DpBJknHdGFoLwY8
xZ9JPI57vgvzLo8+fwHyZp3Vm/o5IYLEQViSo+nlOSUQ8YAVqu6KcsP/e612UiqS
+UMBmgdx9KPDDzZy4MJZC2hbfUoXj9A54mJN8cuEOPyw3c3yKOcq/h48KzVguQXi
SdJbwfqNIbQ9oJM+YzDjzS62+TCtNSNWzWbwABZCmuQxK0oEOSbTmbhxUF7rND3/
+mx9u8cY//7uAxLWYS5gIZlCbxcf0lkiKSHJB319
-----END CERTIFICATE-----
subject=/businessCategory=Private Organization/1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=Delaware/serialNumber=5157550/street=548 4th Street/postalCode=94107/C=US/ST=California/L=San Francisco/O=GitHub, Inc./CN=github.com
issuer=/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV CA-1
---
No client certificate CA names sent
---
SSL handshake has read 4139 bytes and written 446 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: 59D2883BBCE8E81E63E5551FAE7D1ACC00C49A9473C1618237BBBB0DD9016B8D
Session-ID-ctx:
Master-Key: B6D2763FF29E77C67AD83296946A4D44CDBA4F37ED6F20BC27602F1B1A2D137FACDEAC862C11279C01095594F9776F79
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1393392673
Timeout : 300 (sec)
Verify return code: 0 (ok)
¿Las solicitudes / openssl ya no deberían saber dónde encontrar certificados válidos?
No. OpenSSL no confía en nada por defecto. Es un polo opuesto al modelo de un navegador, donde casi todo es de confianza por defecto.
$ openssl s_client -connect github.com:443 -CAfile `python -c ''import requests; print(requests.certs.where())''`
...
>>> r = requests.get(''https://github.com/'', verify=''path to cacert.pem file'')
¿Por qué confiaría en cientos de CA y CA subordinadas (re: cacert.pem
) cuando conoce la única CA que certifica la clave pública del sitio? Confíe en la única raíz necesaria y nada más: DigiCert High Assurance EV CA-1
.
Confiar en todo, como en el modelo del navegador, es lo que permitió a Comodo Hacker falsificar certificados para Gmail, Hotmail, Yahoo, etc. cuando la raíz de Diginotar se vio comprometida.
Desde la Solicitud 2.4.0, el autor recommends usar certifi , que es una colección de Certificados de raíz. Hay un paquete de python para ello:
pip install certifi
openssl s_client de forma predeterminada no utilizará el archivo de certificados de CA con el que se envía, pero intenta verificar la conexión. Esta es la razón por la que su prueba falla sin ningún parámetro y funciona con -CAfile.
De manera similar, Requests intenta verificar la conexión de forma predeterminada, pero parece que no sabe dónde están los certificados de CA. Esto podría ser un problema de configuración en su entorno al compilar / instalar OpenSSL, Python o Requests. Digo esto porque el sitio web de solicitudes muestra que su ejemplo funciona en contra de https://github.com sin necesidad de establecer la ruta de CA.