From f47c3765e2b3b18a456c8b8a6467b3ed7d0b341a Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Wed, 14 Oct 2015 12:40:03 -0300 Subject: [PATCH 1/2] New test to check custom government certificate (self-signed) --- python2/httplib2/test/custom_cacerts.txt | 48 ++++++++++++++++++++++++ python2/httplib2test.py | 20 ++++++++++ 2 files changed, 68 insertions(+) create mode 100644 python2/httplib2/test/custom_cacerts.txt diff --git a/python2/httplib2/test/custom_cacerts.txt b/python2/httplib2/test/custom_cacerts.txt new file mode 100644 index 0000000..52c0068 --- /dev/null +++ b/python2/httplib2/test/custom_cacerts.txt @@ -0,0 +1,48 @@ +# Custom Certifcate Authority certificates for validating SSL connections. +# +# This file contains PEM format certificates downloaded from +# http://www.arba.gov.ar/Apartados/Agentes/remitoelectr%C3%B3nico.asp +# http://www.arba.gov.ar/bajadas/Fiscalizacion/Operativos/TransporteBienes/Documentacion/CertificadoArba_11102015.rar +# +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJANZdOWGV4vbRMA0GCSqGSIb3DQEBCwUAMIH/MRswGQYK +CZImiZPyLGQBGRYLYXJiYS5nb3YuYXIxCzAJBgNVBAYTAkFSMREwDwYDVQQHDAhM +YSBQbGF0YTEVMBMGA1UECAwMQnVlbm9zIEFpcmVzMUYwRAYDVQQKDD1BUkJBIC0g +QWdlbmNpYSBkZSBSZWNhdWRhY2lvbiBkZSBsYSBQcm92aW5jaWEgZGUgQnVlbm9z +IEFpcmVzMRkwFwYDVQQLDBBTZWd1cmlkYWQgTG9naWNhMSYwJAYDVQQDDB1BUkJB +IC0gQXV0b3JpZGFkIENlcnRpZmljYW50ZTEeMBwGCSqGSIb3DQEJARYPcGtpQGFy +YmEuZ292LmFyMB4XDTEwMTAxODA5NTkxNFoXDTIwMTAxNTA5NTkxNFowgf8xGzAZ +BgoJkiaJk/IsZAEZFgthcmJhLmdvdi5hcjELMAkGA1UEBhMCQVIxETAPBgNVBAcM +CExhIFBsYXRhMRUwEwYDVQQIDAxCdWVub3MgQWlyZXMxRjBEBgNVBAoMPUFSQkEg +LSBBZ2VuY2lhIGRlIFJlY2F1ZGFjaW9uIGRlIGxhIFByb3ZpbmNpYSBkZSBCdWVu +b3MgQWlyZXMxGTAXBgNVBAsMEFNlZ3VyaWRhZCBMb2dpY2ExJjAkBgNVBAMMHUFS +QkEgLSBBdXRvcmlkYWQgQ2VydGlmaWNhbnRlMR4wHAYJKoZIhvcNAQkBFg9wa2lA +YXJiYS5nb3YuYXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnNZm2 +Osf9SxGSZKID4IZ63iG7ckOXCDVrGjoCAFYr59SBd3BUZ8dwFY30Ll/EApfd+xMI +zChHotYf9+dLB0dVqUXbbAZQMsRRaME4mEz6o/Vns6NNKUL/9koNoN2a9Q8DQhRN +ShRinJJekfEDRc/S3pljn2NgBZIdwziXe8BBO7IrVW3Yyb6xMLVlnK1Z6wF0w+1Y +n5Hq9xGeFRS86B46xtwN7YIXwzop6jCgicTPZK8TDicnTLOfPHmfLmFeG4yMGPvK +gLthYG9MOEyNW97lhFWovd//IyalrvE3QlmAlUA2Aqv+a2/P0WY7Z/fgn1uUu2tL +4va0OaG8MC8/0MCzR1j5WLuZU8038N6/0fA4eEZUMLct5bmH6W8R26tCLGUG7reo +eqCo3tPIKd+gdGzpB4dU1lL1UWD6tAk64YES804oMY9hMDXSxOo9UwAMQ+aoh24J +IH126KMgC6aBe86CpF5ahPqdnnhkfFaKFV6dxWwsTpACQ4jufJy+iq7MY8DdUKUh +lJ1y3bq4eJErLHAdZOJXy7FZ7BihpDKhz1PxIwgonfGutvuDVeVF/Jdu7EDhmDyC +eILWDhHXHhrI6qMyct3/fx9ZVxcqj59O7bnt/F6HwxMHbnMgj/m6dAoB8ljARcAs +m5hasHlji1VTj0NgdWY2llZDo94iXo9U/1MXLwIDAQABo4HLMIHIMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFP99RGRtMAsJd2Zx7nB+AO+ctE6hMB8GA1UdIwQY +MBaAFP99RGRtMAsJd2Zx7nB+AO+ctE6hMA4GA1UdDwEB/wQEAwIBBjAmBgNVHREE +HzAdgRtzZWd1cmlkYWRsb2dpY2FAYXJiYS5nb3YuYXIwPQYDVR0fBDYwNDAyoDCg +LoYsaHR0cDovL3BraS5hcmJhLmdvdi5hci9wa2kvcHViL2NybC9jYWNybC5jcmww +DQYJKoZIhvcNAQELBQADggIBABv8/Ujsq6qMBWXmWXT1Oi3J/Oocai/k6pnaQzoq +hR6eoy3vQYR67wyyblOPQ1F3ql5QoyTKsnh44x9FCzetLzHguX9RcO7+UYQyxB0l +KtbGzZmqVcQmp/A5syeepM6QKPco6strMQWJ5n5cd/W2q8OsKTvD6BMoRe1lz1Bq +nAKvRmpkxK6r3U47mSMNkAfa3uxqZwg2Y60x7b5ahI6uAiI45MysnbOSz4wwsfHu +kupYuvsDhNGzUWAPJjKOZEpCizhbnt8TKsmN0PHtoLnMIDgMERW9afQnhIac/3u2 +6Ku0bE1zH8xYDqSgSPvxINjMx20qavIm3K1iV7mYQoFJOjktnpIIKx2gP+UzseNi +qMvy5cL1jQThcFItDUVgV4TsWWYXRxOwbsQGs/GZLo85PVIULDt3P9LfGGW/nWCy +jg/wMEZKSeP4zfx/IMyqaKwEBYX6B9XTLhSLPs3xrqm0zvdhh28TFpR78JgyUoMS +CDnvd/jS28N5eaXImqDqBbw5KVpHtdYCO8+LQ6FVYer/8SIl+6s3DMDw4f65sJYU +UD4+eba6DVj0knFExiHa4LA9nr/eRIpEnHyrALTf+vlOKaLcQTbcAChKq7HEg8bR +x2FlPljCiKIwYpw24TVCaoNwh4NPXGyfWRmTysIiYEuqzvSYXnd6KPPcRXNPUz70 +vUh9 +-----END CERTIFICATE----- diff --git a/python2/httplib2test.py b/python2/httplib2test.py index d9bfdb6..930bc07 100755 --- a/python2/httplib2test.py +++ b/python2/httplib2test.py @@ -552,6 +552,26 @@ def testSslHostnameValidation(self): # self.assertRaises(httplib2.CertificateHostnameMismatch, # self.http.request, "https://google.com/", "GET") + def testSslHostnameValidationCustom(self): + # test self-signed government certificate (subjectAltName without dns) + # if unpatched, a CertificateHostnameMismatch exception will be raise: + # Server presented certificate that does not match host cot.arba.gov.ar + # {'notAfter': 'Jun 22 15:41:12 2020 GMT', + # 'subjectAltName': (('email', 'seguridadlogica@arba.gov.ar'),), + # 'subject': ((('countryName', u'AR'),), + # (('stateOrProvinceName', u'Buenos Aires'),), + # (('localityName', u'La Plata'),), + # (('organizationName', u'ARBA'),), + # (('commonName', u'*.arba.gov.ar'),))} + custom_ca_certs = os.path.join( + os.path.dirname(os.path.abspath(httplib2.__file__ )), + "test", "custom_cacerts.txt") + http = httplib2.Http(ca_certs=custom_ca_certs) + try: + http.request("https://cot.arba.gov.ar/", "GET") + except httplib2.CertificateHostnameMismatch: + self.fail('custom cacert for *.arba.gov.ar should be valid') + def testSslCertValidationWithoutSslModuleFails(self): if sys.version_info < (2, 6): http = httplib2.Http(disable_ssl_certificate_validation=False) From b05060575ab145140c186ca0628a9f7adf91d6f1 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Wed, 14 Oct 2015 13:32:49 -0300 Subject: [PATCH 2/2] Fix valid host list extraction from cert Use subject if subjectAltName doesn't contains DNS entries (this could happen with old/broken certs). Now the logic is similar to the one used in Python's SSL match_hostname: https://hg.python.org/cpython/file/2.7/Lib/ssl.py#l238 The return value should be backward compatible (None if no hostname is found at all) --- python2/httplib2/__init__.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/python2/httplib2/__init__.py b/python2/httplib2/__init__.py index 6fa3cc6..acfc022 100644 --- a/python2/httplib2/__init__.py +++ b/python2/httplib2/__init__.py @@ -978,12 +978,16 @@ def _GetValidHostsForCert(self, cert): Returns: list: A list of valid host globs. """ + ret = None + # first check SAN extension for DNS names (new certs should have this): if 'subjectAltName' in cert: - return [x[1] for x in cert['subjectAltName'] - if x[0].lower() == 'dns'] - else: - return [x[0][1] for x in cert['subject'] - if x[0][0].lower() == 'commonname'] + ret = [x[1] for x in cert['subjectAltName'] + if x[0].lower() == 'dns'] + # no SubjectAltName, or no DNS entry on it, use just subject: + if not ret: + ret = [x[0][1] for x in cert['subject'] + if x[0][0].lower() == 'commonname'] + return ret def _ValidateCertificateHostname(self, cert, hostname): """Validates that a given hostname is valid for an SSL certificate.