From 0830097411fa68c831edf6b0328d3aebb40171b2 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Wed, 19 Aug 2015 14:34:12 +0100 Subject: [PATCH 01/41] Experimenting with namespace package settings - following similar strategy to Paste and using pkg_resources if available otherwise pkgutil --- .gitignore | 2 ++ ndg/__init__.py | 30 +++++++++--------------- ndg/httpsclient/ssl_peer_verification.py | 3 ++- setup.py | 18 +++++++------- 4 files changed, 25 insertions(+), 28 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e921c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/ndg_httpsclient.egg-info/ +/dist/ diff --git a/ndg/__init__.py b/ndg/__init__.py index 3b01e15..a9cf1da 100644 --- a/ndg/__init__.py +++ b/ndg/__init__.py @@ -1,19 +1,11 @@ -"""ndg_httpsclient - PyOpenSSL utility to make a httplib-like interface suitable -for use with urllib2 - -This is a setuptools namespace_package. DO NOT place any other -code in this file! There is no guarantee that it will be installed -with easy_install. See: - -http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages - -... for details. -""" -__author__ = "P J Kershaw" -__date__ = "06/01/12" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' - -__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file +try: + import pkg_resources + pkg_resources.declare_namespace(__name__) +except ImportError: + # don't prevent use if pkg_resources isn't installed + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) + +import modulefinder +for p in __path__: + modulefinder.AddPackagePath(__name__, p) \ No newline at end of file diff --git a/ndg/httpsclient/ssl_peer_verification.py b/ndg/httpsclient/ssl_peer_verification.py index 5e82dae..2de390b 100644 --- a/ndg/httpsclient/ssl_peer_verification.py +++ b/ndg/httpsclient/ssl_peer_verification.py @@ -41,7 +41,8 @@ class ServerSSLCertVerification(object): 'userid': 'UID' } SUBJ_ALT_NAME_EXT_NAME = 'subjectAltName' - PARSER_RE_STR = '/(%s)=' % '|'.join(list(DN_LUT.keys()) + list(DN_LUT.values())) + PARSER_RE_STR = '/(%s)=' % '|'.join(list(DN_LUT.keys()) + \ + list(DN_LUT.values())) PARSER_RE = re.compile(PARSER_RE_STR) __slots__ = ('__hostname', '__certDN', '__subj_alt_name_match') diff --git a/setup.py b/setup.py index d566d50..1477c14 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,8 @@ use_setuptools() from setuptools import setup, find_packages +NAMESPACE_PKGS = ['ndg'] + _long_description = ''' This is a HTTPS client implementation for httplib and urllib2 based on PyOpenSSL. PyOpenSSL provides a more fully featured SSL implementation over the @@ -102,7 +104,7 @@ setup( name='ndg_httpsclient', - version="0.4.0", + version="0.4.1", description='Provides enhanced HTTPS support for httplib and urllib2 using ' 'PyOpenSSL', author='Richard Wilkinson and Philip Kershaw', @@ -110,9 +112,9 @@ url='https://github.com/cedadev/ndg_httpsclient/', long_description=_long_description, license='BSD - See LICENCE file for details', - namespace_packages=['ndg'], packages=find_packages(), - package_dir={'ndg.httpsclient': 'ndg/httpsclient'}, + namespace_packages=NAMESPACE_PKGS, +# package_dir={'ndg.httpsclient': 'ndg/httpsclient'}, package_data={ 'ndg.httpsclient': [ 'test/README', @@ -121,9 +123,9 @@ 'test/pki/ca/*.0' ], }, - install_requires = ['PyOpenSSL'], - extras_require = {'subjectAltName_support': 'pyasn1'}, - classifiers = [ + install_requires=['PyOpenSSL'], + extras_require={'subjectAltName_support': 'pyasn1'}, + classifiers=[ 'Development Status :: 3 - Alpha', 'Environment :: Console', 'Environment :: Web Environment', @@ -143,8 +145,8 @@ 'Topic :: System :: Systems Administration :: Authentication/Directory', 'Topic :: Software Development :: Libraries :: Python Modules' ], - zip_safe = False, - entry_points = { + zip_safe=False, + entry_points={ 'console_scripts': ['ndg_httpclient = ndg.httpsclient.utils:main', ], } From 282ffdb0fec499fd5c233c035831b272d8ade17e Mon Sep 17 00:00:00 2001 From: pjkersha Date: Mon, 15 Feb 2016 15:54:48 +0000 Subject: [PATCH 02/41] Added explicit Python 3 text in classifier - https://github.com/vaibhavsagar/ndg_httpsclient/blob/480f623702ce89f672e3bc13e6525d37fc086adc/setup.py --- README.md | 4 ++++ setup.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index d587439..3018702 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ SSL peer. Releases ======== +0.4.1 +----- + * Added explicit ref to Python 3 in classifier text for Python 3 checking tools. + 0.4.0 ----- * Made dual compatible with Python 2 / 3. diff --git a/setup.py b/setup.py index 1477c14..d5a7419 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,10 @@ Releases ======== +0.4.1 +----- + * Added explicit ref to Python 3 in classifier text for Python 3 checking tools. + 0.4.0 ----- * Made dual compatible with Python 2 / 3. @@ -138,6 +142,7 @@ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', + 'Programming Language :: Python :: 3', 'Topic :: Security', 'Topic :: Internet', 'Topic :: Scientific/Engineering', From 0cd5d71afaa5dc80921cfa65c4db788a3643f586 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 15 Apr 2016 11:53:51 +0100 Subject: [PATCH 03/41] Moved license file into package, minor fix to utils open_url to fix kwargs to auth handler --- LICENSE => ndg/httpsclient/LICENSE | 0 ndg/httpsclient/utils.py | 4 ++-- setup.py | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) rename LICENSE => ndg/httpsclient/LICENSE (100%) diff --git a/LICENSE b/ndg/httpsclient/LICENSE similarity index 100% rename from LICENSE rename to ndg/httpsclient/LICENSE diff --git a/ndg/httpsclient/utils.py b/ndg/httpsclient/utils.py index a2b0ed3..d55d8f6 100644 --- a/ndg/httpsclient/utils.py +++ b/ndg/httpsclient/utils.py @@ -188,8 +188,8 @@ def open_url(url, config, data=None, handlers=None): # currently only supports http basic auth auth_handler = HTTPBasicAuthHandler_(HTTPPasswordMgrWithDefaultRealm_()) auth_handler.add_password(realm=None, uri=url, - user=config.httpauth[0], - passwd=config.httpauth[1]) + user=config.http_basicauth[0], + passwd=config.http_basicauth[1]) handlers.append(auth_handler) diff --git a/setup.py b/setup.py index d5a7419..edf0c2e 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ 0.4.1 ----- * Added explicit ref to Python 3 in classifier text for Python 3 checking tools. + * Moved LICENSE file into package 0.4.0 ----- @@ -121,6 +122,7 @@ # package_dir={'ndg.httpsclient': 'ndg/httpsclient'}, package_data={ 'ndg.httpsclient': [ + 'LICENSE', 'test/README', 'test/scripts/*.sh', 'test/pki/localhost.*', From 7a06245c0636ea8b7404428e8bb8971ecf8abc8f Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 15 Apr 2016 11:55:13 +0100 Subject: [PATCH 04/41] Update info about license file location. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index edf0c2e..ff5134e 100644 --- a/setup.py +++ b/setup.py @@ -116,7 +116,7 @@ author_email='Philip.Kershaw@stfc.ac.uk', url='https://github.com/cedadev/ndg_httpsclient/', long_description=_long_description, - license='BSD - See LICENCE file for details', + license='BSD - See ndg/httpsclient/LICENCE file for details', packages=find_packages(), namespace_packages=NAMESPACE_PKGS, # package_dir={'ndg.httpsclient': 'ndg/httpsclient'}, From 5e250bd1f84adfa921d7c03ed01dfcf9b240202f Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 15 Apr 2016 11:57:37 +0100 Subject: [PATCH 05/41] Added license info location change --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3018702..cdc68fc 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Releases 0.4.1 ----- * Added explicit ref to Python 3 in classifier text for Python 3 checking tools. + * Moved LICENSE file into package 0.4.0 ----- From 0c9ee2ac2dab92c621627cf48c3cbd990e40b0f7 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 15 Apr 2016 12:41:53 +0100 Subject: [PATCH 06/41] Updated pre-req info. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cdc68fc..dceb31b 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ Releases 0.3.1 ----- - * extended utils functions to support keyword for passing additional urllib2 + * extended utils functions to support keyword for passing additional ``urllib2`` handlers. 0.3.0 ----- - * Added ndg.httpsclient.utils.fetch_stream_from_url function and added - parameter for data to post in open_url and fetch_* methods. + * Added ``ndg.httpsclient.utils.fetch_stream_from_url`` function and added + parameter for data to post in ``open_url`` and ``fetch_*`` methods. * fix to ndg.httpsclient.utils module _should_use_proxy and open_url functions 0.2.0 @@ -51,10 +51,10 @@ Initial release Prerequisites ============= -This has been developed and tested for Python 2.6 and 2.7 with pyOpenSSL 0.13 and 0.14. -Version 0.4.0 tested with pyOpenSSL 0.15.1 and Python 2.7 and 3.4. Note that proxy support -is only available from Python 2.6.2 onwards. pyasn1 is required for correct SSL -verification with subjectAltNames. +This has been developed and tested for Python 2.6 and 2.7 with pyOpenSSL 0.13 +and 0.14. Version 0.4.0 tested with ``pyOpenSSL`` 0.15.1 and Python 2.7 and +3.4. Note that proxy support is only available from Python 2.6.2 onwards. +``pyasn1`` is required for correct SSL verification with ``subjectAltNames``. Installation ============ From b68bab3a7cf961a1d430fcb341cae136c89adbb6 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 15 Apr 2016 12:50:12 +0100 Subject: [PATCH 07/41] Updating documentation ready for formal release of 0.4.1 --- README.md | 11 +++++++---- setup.py | 25 ++++++++++++++----------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index dceb31b..8aef73c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ -A HTTPS client implementation for httplib and urllib2 based on -PyOpenSSL. PyOpenSSL provides a more fully featured SSL implementation over the -default provided with Python and importantly enables full verification of the -SSL peer. +A HTTPS client implementation for + * ``httplib`` (Python 2), ``http.client`` (Python 3) and + * ``urllib2`` (Python 2) and ``urllib`` (Python 3) + +... based on PyOpenSSL. PyOpenSSL provides a more fully featured SSL implementation +over the default provided with Python and importantly enables full verification +of the SSL peer using ``pyasn1``. Releases ======== diff --git a/setup.py b/setup.py index ff5134e..31a5f7b 100644 --- a/setup.py +++ b/setup.py @@ -8,10 +8,13 @@ NAMESPACE_PKGS = ['ndg'] _long_description = ''' -This is a HTTPS client implementation for httplib and urllib2 based on -PyOpenSSL. PyOpenSSL provides a more fully featured SSL implementation over the -default provided with Python and importantly enables full verification of the -SSL peer. +A HTTPS client implementation for + * ``httplib`` (Python 2), ``http.client`` (Python 3) and + * ``urllib2`` (Python 2) and ``urllib`` (Python 3) + +... based on PyOpenSSL. PyOpenSSL provides a more fully featured SSL implementation +over the default provided with Python and importantly enables full verification +of the SSL peer using ``pyasn1``. Releases ======== @@ -41,13 +44,13 @@ 0.3.1 ----- - * extended utils functions to support keyword for passing additional urllib2 + * extended utils functions to support keyword for passing additional ``urllib2`` handlers. 0.3.0 ----- - * Added ndg.httpsclient.utils.fetch_stream_from_url function and added - parameter for data to post in open_url and fetch_* methods. + * Added ``ndg.httpsclient.utils.fetch_stream_from_url`` function and added + parameter for data to post in ``open_url`` and ``fetch_*`` methods. * fix to ndg.httpsclient.utils module _should_use_proxy and open_url functions 0.2.0 @@ -61,10 +64,10 @@ Prerequisites ============= -This has been developed and tested for Python 2.6 and 2.7 with pyOpenSSL 0.13 and 0.14. -Version 0.4.0 tested with pyOpenSSL 0.15.1 and Python 2.7 and 3.4. Note that proxy support -is only available from Python 2.6.2 onwards. pyasn1 is required for correct SSL -verification with subjectAltNames. +This has been developed and tested for Python 2.6 and 2.7 with pyOpenSSL 0.13 +and 0.14. Version 0.4.0 tested with ``pyOpenSSL`` 0.15.1 and Python 2.7 and +3.4. Note that proxy support is only available from Python 2.6.2 onwards. +``pyasn1`` is required for correct SSL verification with ``subjectAltNames``. Installation ============ From a24ae9c58059142edef46cff5d78a2165be4fb63 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 15 Apr 2016 13:28:42 +0100 Subject: [PATCH 08/41] updated dev status classifier --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 31a5f7b..86b6844 100644 --- a/setup.py +++ b/setup.py @@ -135,7 +135,7 @@ install_requires=['PyOpenSSL'], extras_require={'subjectAltName_support': 'pyasn1'}, classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Environment :: Web Environment', 'Intended Audience :: End Users/Desktop', From cd5349e6b6c1ba6c4b4879d493e092bd9dbdb312 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Mon, 6 Jun 2016 17:02:06 +0100 Subject: [PATCH 09/41] Preparing new PyPI release --- .pydevproject | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pydevproject b/.pydevproject index b26aeb6..5f4661d 100644 --- a/.pydevproject +++ b/.pydevproject @@ -1,6 +1,6 @@ -ndg-https-client-py3.4 +Default python 3.0 /ndg_httpsclient From 3bc5725d99c3544b797f13d0518a725b86c037e0 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Mon, 11 Jul 2016 16:38:14 +0100 Subject: [PATCH 10/41] Removed duplicate open call in `open_url` --- ndg/httpsclient/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/ndg/httpsclient/utils.py b/ndg/httpsclient/utils.py index d55d8f6..7e43a73 100644 --- a/ndg/httpsclient/utils.py +++ b/ndg/httpsclient/utils.py @@ -217,9 +217,6 @@ def open_url(url, config, data=None, handlers=None): return_message = '' response = None - # FIXME - response = opener.open(request) - try: response = opener.open(request) return_message = response.msg From e5e36bca13e0501af3c22eda1d529460b8a11477 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Tue, 19 Jul 2016 14:22:31 +0100 Subject: [PATCH 11/41] Preparing 0.4.2 release. Fix to bug in ``ndg.httpsclient.utils.open_url`` - duplicate open call. Nb. This bug and the fix DO NOT affect the ``httplib``and ``urllib2`` interfaces that this package provides. --- README.md | 6 ++++++ setup.py | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8aef73c..01adc11 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ of the SSL peer using ``pyasn1``. Releases ======== +0.4.2 +----- + * Fix to bug in ``ndg.httpsclient.utils.open_url`` - duplicate open call. + Nb. This bug and the fix DO NOT affect the ``httplib``and ``urllib2`` + interfaces that this package provides. + 0.4.1 ----- * Added explicit ref to Python 3 in classifier text for Python 3 checking tools. diff --git a/setup.py b/setup.py index 86b6844..5b51756 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,12 @@ Releases ======== +0.4.2 +----- + * Fix to bug in ``ndg.httpsclient.utils.open_url`` - duplicate open call. + Nb. This bug and the fix DO NOT affect the ``httplib``and ``urllib2`` + interfaces that this package provides. + 0.4.1 ----- * Added explicit ref to Python 3 in classifier text for Python 3 checking tools. @@ -112,7 +118,7 @@ setup( name='ndg_httpsclient', - version="0.4.1", + version="0.4.2", description='Provides enhanced HTTPS support for httplib and urllib2 using ' 'PyOpenSSL', author='Richard Wilkinson and Philip Kershaw', From d81c78b82849163b21e43e9be2159c8671aab65c Mon Sep 17 00:00:00 2001 From: pjkersha Date: Thu, 1 Sep 2016 09:38:16 +0100 Subject: [PATCH 12/41] Setting minimum version for ``pyasn1`` to address conflicts with Ubuntu install (https://github.com/cedadev/ndg_httpsclient/issues/5). --- .pydevproject | 2 +- README.md | 5 +++++ setup.py | 9 ++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.pydevproject b/.pydevproject index 5f4661d..7a5cfa1 100644 --- a/.pydevproject +++ b/.pydevproject @@ -1,6 +1,6 @@ -Default +ndg-httpsclient-py3 python 3.0 /ndg_httpsclient diff --git a/README.md b/README.md index 01adc11..5c71c88 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,11 @@ of the SSL peer using ``pyasn1``. Releases ======== +0.4.3 +----- + * Minor fix for installation: set minimum release for ``pyasn1`` to avoid confl +icts with Ubuntu install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and https://github.com/cedadev/ndg_httpsclient/pull/10. + 0.4.2 ----- * Fix to bug in ``ndg.httpsclient.utils.open_url`` - duplicate open call. diff --git a/setup.py b/setup.py index 5b51756..cd258bc 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,10 @@ Releases ======== +0.4.3 +----- + * Minor fix for installation: set minimum release for ``pyasn1`` to avoid conflicts with Ubuntu install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and https://github.com/cedadev/ndg_httpsclient/pull/10. + 0.4.2 ----- * Fix to bug in ``ndg.httpsclient.utils.open_url`` - duplicate open call. @@ -118,7 +122,7 @@ setup( name='ndg_httpsclient', - version="0.4.2", + version="0.4.3", description='Provides enhanced HTTPS support for httplib and urllib2 using ' 'PyOpenSSL', author='Richard Wilkinson and Philip Kershaw', @@ -128,7 +132,6 @@ license='BSD - See ndg/httpsclient/LICENCE file for details', packages=find_packages(), namespace_packages=NAMESPACE_PKGS, -# package_dir={'ndg.httpsclient': 'ndg/httpsclient'}, package_data={ 'ndg.httpsclient': [ 'LICENSE', @@ -139,7 +142,7 @@ ], }, install_requires=['PyOpenSSL'], - extras_require={'subjectAltName_support': 'pyasn1'}, + extras_require={'subjectAltName_support': 'pyasn1>=0.1.1'}, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', From 02e7d6e1bea98c02afcc65da9a4803eb2d0da721 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 2 Sep 2016 09:33:17 +0100 Subject: [PATCH 13/41] Make pyasn1 a compulsory package. cryptography, a dependency for PyOpenSSL>=0.14 needs it anyway so the optional install is superfluous. --- README.md | 7 +++++-- setup.py | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5c71c88..f4c36c2 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,11 @@ Releases ======== 0.4.3 ----- - * Minor fix for installation: set minimum release for ``pyasn1`` to avoid confl -icts with Ubuntu install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and https://github.com/cedadev/ndg_httpsclient/pull/10. + * Minor fix for installation: set minimum release for ``pyasn1`` to avoid conflicts with Ubuntu + install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and + https://github.com/cedadev/ndg_httpsclient/pull/10. ``pyasn1`` also becomes mandatory rather + than optional package for install. - It required by ``cryptography`` anyway which is a + dependency for ``pyOpenSSL`` from version 0.14. 0.4.2 ----- diff --git a/setup.py b/setup.py index cd258bc..0f52dd0 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,11 @@ ======== 0.4.3 ----- - * Minor fix for installation: set minimum release for ``pyasn1`` to avoid conflicts with Ubuntu install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and https://github.com/cedadev/ndg_httpsclient/pull/10. + * Minor fix for installation: set minimum release for ``pyasn1`` to avoid conflicts with Ubuntu + install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and + https://github.com/cedadev/ndg_httpsclient/pull/10. ``pyasn1`` also becomes mandatory rather + than optional package for install. - It required by ``cryptography`` anyway which is a + dependency for ``pyOpenSSL`` from version 0.14. 0.4.2 ----- @@ -141,8 +145,7 @@ 'test/pki/ca/*.0' ], }, - install_requires=['PyOpenSSL'], - extras_require={'subjectAltName_support': 'pyasn1>=0.1.1'}, + install_requires=['PyOpenSSL', 'pyasn1>=0.1.1'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', From 741d3f6086845ab3d2fac77cf60eec74031f6e54 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 2 Sep 2016 09:37:24 +0100 Subject: [PATCH 14/41] Relabel for release candidate --- README.md | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f4c36c2..458201e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ of the SSL peer using ``pyasn1``. Releases ======== -0.4.3 +0.4.3 (Candidate) ----- * Minor fix for installation: set minimum release for ``pyasn1`` to avoid conflicts with Ubuntu install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and diff --git a/setup.py b/setup.py index 0f52dd0..dc44b8b 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ Releases ======== -0.4.3 +0.4.3 (Candidate) ----- * Minor fix for installation: set minimum release for ``pyasn1`` to avoid conflicts with Ubuntu install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and From e9b27028ecf443221f2012a6675983c91a69785e Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Tue, 7 Jun 2016 17:45:59 +0700 Subject: [PATCH 15/41] Add Travis CI testing --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ba51298 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: python +python: + - 2.6 + - 2.7 + - 3.3 + - 3.4 + - 3.5 + - nightly + - pypy-5.3 + - pypy3.3-5.2-alpha1 +script: + - python setup.py install From f7378ed1f22047c471600895249e4da56154949c Mon Sep 17 00:00:00 2001 From: Max Mautner Date: Tue, 29 Aug 2017 15:28:30 -0700 Subject: [PATCH 16/41] resolve packaging problems leading to warnings closes https://github.com/cedadev/ndg_httpsclient/issues/3 --- .gitignore | 2 ++ Dockerfile | 6 ++++++ ndg/__init__.py | 12 +----------- setup.py | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) create mode 100644 Dockerfile diff --git a/.gitignore b/.gitignore index 6e921c6..9c528bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /ndg_httpsclient.egg-info/ /dist/ +*.pyc +.eggs/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9ba08a2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM ubuntu:14.04 +RUN apt-get -yqq update && apt-get -yqq install python-pip python-dev libffi-dev libxmlsec1-openssl libssl-dev +WORKDIR /app/ +ADD . /app/ +RUN pip install -e . +CMD ["python", "setup.py", "test"] diff --git a/ndg/__init__.py b/ndg/__init__.py index a9cf1da..69e3be5 100644 --- a/ndg/__init__.py +++ b/ndg/__init__.py @@ -1,11 +1 @@ -try: - import pkg_resources - pkg_resources.declare_namespace(__name__) -except ImportError: - # don't prevent use if pkg_resources isn't installed - from pkgutil import extend_path - __path__ = extend_path(__path__, __name__) - -import modulefinder -for p in __path__: - modulefinder.AddPackagePath(__name__, p) \ No newline at end of file +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/setup.py b/setup.py index 5b51756..23ae1f4 100644 --- a/setup.py +++ b/setup.py @@ -127,7 +127,7 @@ long_description=_long_description, license='BSD - See ndg/httpsclient/LICENCE file for details', packages=find_packages(), - namespace_packages=NAMESPACE_PKGS, +# namespace_packages=NAMESPACE_PKGS, # package_dir={'ndg.httpsclient': 'ndg/httpsclient'}, package_data={ 'ndg.httpsclient': [ From 36092f9fe3b9924f04c6a8e3f6e9d0e4920f8cfb Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 1 Sep 2017 13:46:34 +0100 Subject: [PATCH 17/41] Updating readme and setup.py for github issue 3 fix. --- README.md | 3 +++ setup.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 458201e..dd41207 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ Releases ======== 0.4.3 (Candidate) ----- + * Fix to ``ndg`` namespace package warning issue (https://github.com/cedadev/ndg_httpsclient/issues/3). + ``__init__.py`` file now included in ``ndg`` directory so that there are no longer warnings with imports + when using Python 2.x. Thanks to Max Mauntner for fix. * Minor fix for installation: set minimum release for ``pyasn1`` to avoid conflicts with Ubuntu install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and https://github.com/cedadev/ndg_httpsclient/pull/10. ``pyasn1`` also becomes mandatory rather diff --git a/setup.py b/setup.py index 2176c26..8eef9d1 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,9 @@ ======== 0.4.3 (Candidate) ----- + * Fix to ``ndg`` namespace package warning issue (https://github.com/cedadev/ndg_httpsclient/issues/3). + ``__init__.py`` file now included in ``ndg`` directory so that there are no longer warnings with imports + when using Python 2.x. Thanks to Max Mauntner for fix. * Minor fix for installation: set minimum release for ``pyasn1`` to avoid conflicts with Ubuntu install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and https://github.com/cedadev/ndg_httpsclient/pull/10. ``pyasn1`` also becomes mandatory rather From c6e7b401d25c65450955fbb4b73140915647d04c Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 1 Sep 2017 15:14:16 +0100 Subject: [PATCH 18/41] Updated the Python releases to try --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ba51298..bcc7fbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,7 @@ python: - 3.3 - 3.4 - 3.5 + - 3.6 - nightly - - pypy-5.3 - - pypy3.3-5.2-alpha1 script: - python setup.py install From a639a2661e9a4a3fc735107a9f7af11086a2708e Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 1 Sep 2017 15:20:41 +0100 Subject: [PATCH 19/41] Move docker file into test area --- Dockerfile => ndg/httpsclient/test/docker/Dockerfile | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Dockerfile => ndg/httpsclient/test/docker/Dockerfile (100%) diff --git a/Dockerfile b/ndg/httpsclient/test/docker/Dockerfile similarity index 100% rename from Dockerfile rename to ndg/httpsclient/test/docker/Dockerfile From 42bf75fb9154112bc284f369294da482d8abe465 Mon Sep 17 00:00:00 2001 From: pjkersha Date: Fri, 1 Sep 2017 15:24:13 +0100 Subject: [PATCH 20/41] 0.4.3 to be live release --- README.md | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd41207..d2f56fb 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ of the SSL peer using ``pyasn1``. Releases ======== -0.4.3 (Candidate) +0.4.3 ----- * Fix to ``ndg`` namespace package warning issue (https://github.com/cedadev/ndg_httpsclient/issues/3). ``__init__.py`` file now included in ``ndg`` directory so that there are no longer warnings with imports diff --git a/setup.py b/setup.py index 8eef9d1..4cfc86b 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ Releases ======== -0.4.3 (Candidate) +0.4.3 ----- * Fix to ``ndg`` namespace package warning issue (https://github.com/cedadev/ndg_httpsclient/issues/3). ``__init__.py`` file now included in ``ndg`` directory so that there are no longer warnings with imports From 0a48ec5ee09a19f9d9c6c6667664889f674ee44e Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Fri, 26 Jan 2018 12:13:32 +0000 Subject: [PATCH 21/41] Updated certificate files. --- README.md | 4 + build/lib/ndg/__init__.py | 1 + build/lib/ndg/httpsclient/LICENSE | 26 ++ build/lib/ndg/httpsclient/__init__.py | 9 + build/lib/ndg/httpsclient/https.py | 131 ++++++ build/lib/ndg/httpsclient/ssl_context_util.py | 98 +++++ .../ndg/httpsclient/ssl_peer_verification.py | 238 ++++++++++ build/lib/ndg/httpsclient/ssl_socket.py | 282 ++++++++++++ build/lib/ndg/httpsclient/subj_alt_name.py | 153 +++++++ build/lib/ndg/httpsclient/test/README | 26 ++ build/lib/ndg/httpsclient/test/__init__.py | 29 ++ .../ndg}/httpsclient/test/pki/ca/08bd99c7.0 | 0 .../ndg}/httpsclient/test/pki/ca/ade0138a.0 | 0 .../ndg/httpsclient/test/pki/localhost.crt | 82 ++++ .../ndg/httpsclient/test/pki/localhost.key | 27 ++ .../test/scripts/openssl_https_server.sh | 2 + build/lib/ndg/httpsclient/test/test_https.py | 122 ++++++ .../lib/ndg/httpsclient/test/test_urllib2.py | 56 +++ build/lib/ndg/httpsclient/test/test_utils.py | 62 +++ .../ndg/httpsclient/urllib2_build_opener.py | 78 ++++ build/lib/ndg/httpsclient/utils.py | 411 ++++++++++++++++++ ndg/httpsclient/test/pki/ca/7e15277f.0 | 20 + ndg/httpsclient/test/pki/ca/ndg-test-ca.jks | Bin 882 -> 0 bytes ndg/httpsclient/test/pki/localhost.crt | 97 +---- ndg/httpsclient/test/pki/localhost.key | 50 +-- setup.py | 48 +- 26 files changed, 1925 insertions(+), 127 deletions(-) create mode 100644 build/lib/ndg/__init__.py create mode 100644 build/lib/ndg/httpsclient/LICENSE create mode 100644 build/lib/ndg/httpsclient/__init__.py create mode 100644 build/lib/ndg/httpsclient/https.py create mode 100644 build/lib/ndg/httpsclient/ssl_context_util.py create mode 100644 build/lib/ndg/httpsclient/ssl_peer_verification.py create mode 100644 build/lib/ndg/httpsclient/ssl_socket.py create mode 100644 build/lib/ndg/httpsclient/subj_alt_name.py create mode 100644 build/lib/ndg/httpsclient/test/README create mode 100644 build/lib/ndg/httpsclient/test/__init__.py rename {ndg => build/lib/ndg}/httpsclient/test/pki/ca/08bd99c7.0 (100%) rename {ndg => build/lib/ndg}/httpsclient/test/pki/ca/ade0138a.0 (100%) create mode 100644 build/lib/ndg/httpsclient/test/pki/localhost.crt create mode 100644 build/lib/ndg/httpsclient/test/pki/localhost.key create mode 100755 build/lib/ndg/httpsclient/test/scripts/openssl_https_server.sh create mode 100644 build/lib/ndg/httpsclient/test/test_https.py create mode 100644 build/lib/ndg/httpsclient/test/test_urllib2.py create mode 100644 build/lib/ndg/httpsclient/test/test_utils.py create mode 100644 build/lib/ndg/httpsclient/urllib2_build_opener.py create mode 100644 build/lib/ndg/httpsclient/utils.py create mode 100644 ndg/httpsclient/test/pki/ca/7e15277f.0 delete mode 100644 ndg/httpsclient/test/pki/ca/ndg-test-ca.jks diff --git a/README.md b/README.md index d2f56fb..f7e7093 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ of the SSL peer using ``pyasn1``. Releases ======== +0.4.4 +----- + * Updated test certificates + 0.4.3 ----- * Fix to ``ndg`` namespace package warning issue (https://github.com/cedadev/ndg_httpsclient/issues/3). diff --git a/build/lib/ndg/__init__.py b/build/lib/ndg/__init__.py new file mode 100644 index 0000000..69e3be5 --- /dev/null +++ b/build/lib/ndg/__init__.py @@ -0,0 +1 @@ +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/build/lib/ndg/httpsclient/LICENSE b/build/lib/ndg/httpsclient/LICENSE new file mode 100644 index 0000000..4098929 --- /dev/null +++ b/build/lib/ndg/httpsclient/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2012, Science & Technology Facilities Council (STFC) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Science & Technology Facilities Council (STFC) + nor the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/build/lib/ndg/httpsclient/__init__.py b/build/lib/ndg/httpsclient/__init__.py new file mode 100644 index 0000000..83f2087 --- /dev/null +++ b/build/lib/ndg/httpsclient/__init__.py @@ -0,0 +1,9 @@ +"""ndg_httpsclient - PyOpenSSL utility to make a httplib-like interface suitable +for use with urllib2 +""" +__author__ = "P J Kershaw (STFC) and Richard Wilkinson (Tessella)" +__date__ = "09/12/11" +__copyright__ = "(C) 2011 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' diff --git a/build/lib/ndg/httpsclient/https.py b/build/lib/ndg/httpsclient/https.py new file mode 100644 index 0000000..41ad1c6 --- /dev/null +++ b/build/lib/ndg/httpsclient/https.py @@ -0,0 +1,131 @@ +"""ndg_httpsclient HTTPS module containing PyOpenSSL implementation of +httplib.HTTPSConnection + +PyOpenSSL utility to make a httplib-like interface suitable for use with +urllib2 +""" +__author__ = "P J Kershaw (STFC)" +__date__ = "09/12/11" +__copyright__ = "(C) 2012 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' +import logging +import socket +import sys + +if sys.version_info[0] > 2: + from http.client import HTTPS_PORT + from http.client import HTTPConnection + + from urllib.request import AbstractHTTPHandler +else: + from httplib import HTTPS_PORT + from httplib import HTTPConnection + + from urllib2 import AbstractHTTPHandler + + +from OpenSSL import SSL + +from ndg.httpsclient.ssl_socket import SSLSocket + +log = logging.getLogger(__name__) + + +class HTTPSConnection(HTTPConnection): + """This class allows communication via SSL using PyOpenSSL. + It is based on httplib.HTTPSConnection, modified to use PyOpenSSL. + + Note: This uses the constructor inherited from HTTPConnection to allow it to + be used with httplib and HTTPSContextHandler. To use the class directly with + an SSL context set ssl_context after construction. + + @cvar default_port: default port for this class (443) + @type default_port: int + @cvar default_ssl_method: default SSL method used if no SSL context is + explicitly set - defaults to version 2/3. + @type default_ssl_method: int + """ + default_port = HTTPS_PORT + default_ssl_method = SSL.SSLv23_METHOD + + def __init__(self, host, port=None, strict=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, ssl_context=None): + HTTPConnection.__init__(self, host, port, strict, timeout) + if not hasattr(self, 'ssl_context'): + self.ssl_context = None + + if ssl_context is not None: + if not isinstance(ssl_context, SSL.Context): + raise TypeError('Expecting OpenSSL.SSL.Context type for "' + 'ssl_context" keyword; got %r instead' % + ssl_context) + + self.ssl_context = ssl_context + + def connect(self): + """Create SSL socket and connect to peer + """ + if getattr(self, 'ssl_context', None): + if not isinstance(self.ssl_context, SSL.Context): + raise TypeError('Expecting OpenSSL.SSL.Context type for "' + 'ssl_context" attribute; got %r instead' % + self.ssl_context) + ssl_context = self.ssl_context + else: + ssl_context = SSL.Context(self.__class__.default_ssl_method) + + sock = socket.create_connection((self.host, self.port), self.timeout) + + # Tunnel if using a proxy - ONLY available for Python 2.6.2 and above + if getattr(self, '_tunnel_host', None): + self.sock = sock + self._tunnel() + + self.sock = SSLSocket(ssl_context, sock) + + # Go to client mode. + self.sock.set_connect_state() + + def close(self): + """Close socket and shut down SSL connection""" + if hasattr(self.sock, "close"): + self.sock.close() + + +class HTTPSContextHandler(AbstractHTTPHandler): + '''HTTPS handler that allows a SSL context to be set for the SSL + connections. + ''' + https_request = AbstractHTTPHandler.do_request_ + + def __init__(self, ssl_context, debuglevel=0): + """ + @param ssl_context:SSL context + @type ssl_context: OpenSSL.SSL.Context + @param debuglevel: debug level for HTTPSHandler + @type debuglevel: int + """ + AbstractHTTPHandler.__init__(self, debuglevel) + + if ssl_context is not None: + if not isinstance(ssl_context, SSL.Context): + raise TypeError('Expecting OpenSSL.SSL.Context type for "' + 'ssl_context" keyword; got %r instead' % + ssl_context) + self.ssl_context = ssl_context + else: + self.ssl_context = SSL.Context(SSL.TLSv1_METHOD) + + def https_open(self, req): + """Opens HTTPS request + @param req: HTTP request + @return: HTTP Response object + """ + # Make a custom class extending HTTPSConnection, with the SSL context + # set as a class variable so that it is available to the connect method. + customHTTPSContextConnection = type('CustomHTTPSContextConnection', + (HTTPSConnection, object), + {'ssl_context': self.ssl_context}) + return self.do_open(customHTTPSContextConnection, req) diff --git a/build/lib/ndg/httpsclient/ssl_context_util.py b/build/lib/ndg/httpsclient/ssl_context_util.py new file mode 100644 index 0000000..0ed1d32 --- /dev/null +++ b/build/lib/ndg/httpsclient/ssl_context_util.py @@ -0,0 +1,98 @@ +"""ndg_httpsclient SSL Context utilities module containing convenience routines +for setting SSL context configuration. + +""" +__author__ = "P J Kershaw (STFC)" +__date__ = "09/12/11" +__copyright__ = "(C) 2012 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' +import sys + +if sys.version_info[0] > 2: + import urllib.parse as urlparse_ +else: + import urlparse as urlparse_ + +from OpenSSL import SSL + +from ndg.httpsclient.ssl_peer_verification import ServerSSLCertVerification + + +class SSlContextConfig(object): + """ + Holds configuration options for creating a SSL context. This is used as a + template to create the contexts with specific verification callbacks. + """ + def __init__(self, key_file=None, cert_file=None, pem_file=None, ca_dir=None, + verify_peer=False): + self.key_file = key_file + self.cert_file = cert_file + self.pem_file = pem_file + self.ca_dir = ca_dir + self.verify_peer = verify_peer + + +def make_ssl_context_from_config(ssl_config=False, url=None): + return make_ssl_context(ssl_config.key_file, ssl_config.cert_file, + ssl_config.pem_file, ssl_config.ca_dir, + ssl_config.verify_peer, url) + + +def make_ssl_context(key_file=None, cert_file=None, pem_file=None, ca_dir=None, + verify_peer=False, url=None, method=SSL.TLSv1_METHOD, + key_file_passphrase=None): + """ + Creates SSL context containing certificate and key file locations. + """ + ssl_context = SSL.Context(method) + + # Key file defaults to certificate file if present. + if cert_file: + ssl_context.use_certificate_file(cert_file) + + if key_file_passphrase: + passwd_cb = lambda max_passphrase_len, set_prompt, userdata: \ + key_file_passphrase + ssl_context.set_passwd_cb(passwd_cb) + + if key_file: + ssl_context.use_privatekey_file(key_file) + elif cert_file: + ssl_context.use_privatekey_file(cert_file) + + if pem_file or ca_dir: + ssl_context.load_verify_locations(pem_file, ca_dir) + + def _callback(conn, x509, errnum, errdepth, preverify_ok): + """Default certification verification callback. + Performs no checks and returns the status passed in. + """ + return preverify_ok + + verify_callback = _callback + + if verify_peer: + ssl_context.set_verify_depth(9) + if url: + set_peer_verification_for_url_hostname(ssl_context, url) + else: + ssl_context.set_verify(SSL.VERIFY_PEER, verify_callback) + else: + ssl_context.set_verify(SSL.VERIFY_NONE, verify_callback) + + return ssl_context + + +def set_peer_verification_for_url_hostname(ssl_context, url, + if_verify_enabled=False): + '''Convenience routine to set peer verification callback based on + ServerSSLCertVerification class''' + if not if_verify_enabled or (ssl_context.get_verify_mode() & SSL.VERIFY_PEER): + urlObj = urlparse_.urlparse(url) + hostname = urlObj.hostname + server_ssl_cert_verif = ServerSSLCertVerification(hostname=hostname) + verify_callback_ = server_ssl_cert_verif.get_verify_server_cert_func() + ssl_context.set_verify(SSL.VERIFY_PEER, verify_callback_) + diff --git a/build/lib/ndg/httpsclient/ssl_peer_verification.py b/build/lib/ndg/httpsclient/ssl_peer_verification.py new file mode 100644 index 0000000..2de390b --- /dev/null +++ b/build/lib/ndg/httpsclient/ssl_peer_verification.py @@ -0,0 +1,238 @@ +"""ndg_httpsclient - module containing SSL peer verification class. +""" +__author__ = "P J Kershaw (STFC)" +__date__ = "09/12/11" +__copyright__ = "(C) 2012 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' +import re +import logging +log = logging.getLogger(__name__) + +try: + from ndg.httpsclient.subj_alt_name import SubjectAltName + from pyasn1.codec.der import decoder as der_decoder + SUBJ_ALT_NAME_SUPPORT = True + +except ImportError as e: + SUBJ_ALT_NAME_SUPPORT = False + SUBJ_ALT_NAME_SUPPORT_MSG = ( + 'SubjectAltName support is disabled - check pyasn1 package ' + 'installation to enable' + ) + import warnings + warnings.warn(SUBJ_ALT_NAME_SUPPORT_MSG) + + +class ServerSSLCertVerification(object): + """Check server identity. If hostname doesn't match, allow match of + host's Distinguished Name against server DN setting""" + DN_LUT = { + 'commonName': 'CN', + 'organisationalUnitName': 'OU', + 'organisation': 'O', + 'countryName': 'C', + 'emailAddress': 'EMAILADDRESS', + 'localityName': 'L', + 'stateOrProvinceName': 'ST', + 'streetAddress': 'STREET', + 'domainComponent': 'DC', + 'userid': 'UID' + } + SUBJ_ALT_NAME_EXT_NAME = 'subjectAltName' + PARSER_RE_STR = '/(%s)=' % '|'.join(list(DN_LUT.keys()) + \ + list(DN_LUT.values())) + PARSER_RE = re.compile(PARSER_RE_STR) + + __slots__ = ('__hostname', '__certDN', '__subj_alt_name_match') + + def __init__(self, certDN=None, hostname=None, subj_alt_name_match=True): + """Override parent class __init__ to enable setting of certDN + setting + + @type certDN: string + @param certDN: Set the expected Distinguished Name of the + server to avoid errors matching hostnames. This is useful + where the hostname is not fully qualified + @type hostname: string + @param hostname: hostname to match against peer certificate + subjectAltNames or subject common name + @type subj_alt_name_match: bool + @param subj_alt_name_match: flag to enable/disable matching of hostname + against peer certificate subjectAltNames. Nb. A setting of True will + be ignored if the pyasn1 package is not installed + """ + self.__certDN = None + self.__hostname = None + + if certDN is not None: + self.certDN = certDN + + if hostname is not None: + self.hostname = hostname + + if subj_alt_name_match: + if not SUBJ_ALT_NAME_SUPPORT: + log.warning('Overriding "subj_alt_name_match" keyword setting: ' + 'peer verification with subjectAltNames is disabled') + self.__subj_alt_name_match = False + else: + self.__subj_alt_name_match = True + else: + log.debug('Disabling peer verification with subject ' + 'subjectAltNames!') + self.__subj_alt_name_match = False + + def __call__(self, connection, peerCert, errorStatus, errorDepth, + preverifyOK): + """Verify server certificate + + @type connection: OpenSSL.SSL.Connection + @param connection: SSL connection object + @type peerCert: basestring + @param peerCert: server host certificate as OpenSSL.crypto.X509 + instance + @type errorStatus: int + @param errorStatus: error status passed from caller. This is the value + returned by the OpenSSL C function X509_STORE_CTX_get_error(). Look-up + x509_vfy.h in the OpenSSL source to get the meanings of the different + codes. PyOpenSSL doesn't help you! + @type errorDepth: int + @param errorDepth: a non-negative integer representing where in the + certificate chain the error occurred. If it is zero it occured in the + end entity certificate, one if it is the certificate which signed the + end entity certificate and so on. + + @type preverifyOK: int + @param preverifyOK: the error status - 0 = Error, 1 = OK of the current + SSL context irrespective of any verification checks done here. If this + function yields an OK status, it should enforce the preverifyOK value + so that any error set upstream overrides and is honoured. + @rtype: int + @return: status code - 0/False = Error, 1/True = OK + """ + if peerCert.has_expired(): + # Any expired certificate in the chain should result in an error + log.error('Certificate %r in peer certificate chain has expired', + peerCert.get_subject()) + + return False + + elif errorDepth == 0: + # Only interested in DN of last certificate in the chain - this must + # match the expected Server DN setting + peerCertSubj = peerCert.get_subject() + peerCertDN = peerCertSubj.get_components() + peerCertDN.sort() + + if self.certDN is None: + # Check hostname against peer certificate CN field instead: + if self.hostname is None: + log.error('No "hostname" or "certDN" set to check peer ' + 'certificate against') + return False + + # Check for subject alternative names + if self.__subj_alt_name_match: + dns_names = self._get_subj_alt_name(peerCert) + if self.hostname in dns_names: + return preverifyOK + + # If no subjectAltNames, default to check of subject Common Name + if peerCertSubj.commonName == self.hostname: + return preverifyOK + else: + log.error('Peer certificate CN %r doesn\'t match the ' + 'expected CN %r', peerCertSubj.commonName, + self.hostname) + return False + else: + if peerCertDN == self.certDN: + return preverifyOK + else: + log.error('Peer certificate DN %r doesn\'t match the ' + 'expected DN %r', peerCertDN, self.certDN) + return False + else: + return preverifyOK + + def get_verify_server_cert_func(self): + def verify_server_cert(connection, peerCert, errorStatus, errorDepth, + preverifyOK): + return self.__call__(connection, peerCert, errorStatus, + errorDepth, preverifyOK) + + return verify_server_cert + + @classmethod + def _get_subj_alt_name(cls, peer_cert): + '''Extract subjectAltName DNS name settings from certificate extensions + + @param peer_cert: peer certificate in SSL connection. subjectAltName + settings if any will be extracted from this + @type peer_cert: OpenSSL.crypto.X509 + ''' + # Search through extensions + dns_name = [] + general_names = SubjectAltName() + for i in range(peer_cert.get_extension_count()): + ext = peer_cert.get_extension(i) + ext_name = ext.get_short_name() + if ext_name == cls.SUBJ_ALT_NAME_EXT_NAME: + # PyOpenSSL returns extension data in ASN.1 encoded form + ext_dat = ext.get_data() + decoded_dat = der_decoder.decode(ext_dat, + asn1Spec=general_names) + + for name in decoded_dat: + if isinstance(name, SubjectAltName): + for entry in range(len(name)): + component = name.getComponentByPosition(entry) + dns_name.append(str(component.getComponent())) + + return dns_name + + def _getCertDN(self): + return self.__certDN + + def _setCertDN(self, val): + if isinstance(val, str): + # Allow for quoted DN + certDN = val.strip('"') + + dnFields = self.__class__.PARSER_RE.split(certDN) + if len(dnFields) < 2: + raise TypeError('Error parsing DN string: "%s"' % certDN) + + self.__certDN = list(zip(dnFields[1::2], dnFields[2::2])) + self.__certDN.sort() + + elif not isinstance(val, list): + for i in val: + if not len(i) == 2: + raise TypeError('Expecting list of two element DN field, ' + 'DN field value pairs for "certDN" ' + 'attribute') + self.__certDN = val + else: + raise TypeError('Expecting list or string type for "certDN" ' + 'attribute') + + certDN = property(fget=_getCertDN, + fset=_setCertDN, + doc="Distinguished Name for Server Certificate") + + # Get/Set Property methods + def _getHostname(self): + return self.__hostname + + def _setHostname(self, val): + if not isinstance(val, str): + raise TypeError("Expecting string type for hostname " + "attribute") + self.__hostname = val + + hostname = property(fget=_getHostname, + fset=_setHostname, + doc="hostname of server") diff --git a/build/lib/ndg/httpsclient/ssl_socket.py b/build/lib/ndg/httpsclient/ssl_socket.py new file mode 100644 index 0000000..7780314 --- /dev/null +++ b/build/lib/ndg/httpsclient/ssl_socket.py @@ -0,0 +1,282 @@ +"""PyOpenSSL utilities including HTTPSSocket class which wraps PyOpenSSL +SSL connection into a httplib-like interface suitable for use with urllib2 + +""" +__author__ = "P J Kershaw" +__date__ = "21/12/10" +__copyright__ = "(C) 2012 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' + +from datetime import datetime +import logging +import socket +from io import BytesIO + +from OpenSSL import SSL + +log = logging.getLogger(__name__) + + +class SSLSocket(object): + """SSL Socket class wraps pyOpenSSL's SSL.Connection class implementing + the makefile method so that it is compatible with the standard socket + interface and usable with httplib. + + @cvar default_buf_size: default buffer size for recv operations in the + makefile method + @type default_buf_size: int + """ + default_buf_size = 8192 + + def __init__(self, ctx, sock=None): + """Create SSL socket object + + @param ctx: SSL context + @type ctx: OpenSSL.SSL.Context + @param sock: underlying socket object + @type sock: socket.socket + """ + if sock is not None: + self.socket = sock + else: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + self.__ssl_conn = SSL.Connection(ctx, self.socket) + self.buf_size = self.__class__.default_buf_size + self._makefile_refs = 0 + + def __del__(self): + """Close underlying socket when this object goes out of scope + """ + self.close() + + @property + def buf_size(self): + """Buffer size for makefile method recv() operations""" + return self.__buf_size + + @buf_size.setter + def buf_size(self, value): + """Buffer size for makefile method recv() operations""" + if not isinstance(value, int): + raise TypeError('Expecting int type for "buf_size"; ' + 'got %r instead' % type(value)) + self.__buf_size = value + + def close(self): + """Shutdown the SSL connection and call the close method of the + underlying socket""" + if self._makefile_refs < 1: + try: + self.__ssl_conn.shutdown() + except (SSL.Error, SSL.SysCallError): + # Make errors on shutdown non-fatal + pass + else: + self._makefile_refs -= 1 + + def set_shutdown(self, mode): + """Set the shutdown state of the Connection. + @param mode: bit vector of either or both of SENT_SHUTDOWN and + RECEIVED_SHUTDOWN + """ + self.__ssl_conn.set_shutdown(mode) + + def get_shutdown(self): + """Get the shutdown state of the Connection. + @return: bit vector of either or both of SENT_SHUTDOWN and + RECEIVED_SHUTDOWN + """ + return self.__ssl_conn.get_shutdown() + + def bind(self, addr): + """bind to the given address - calls method of the underlying socket + @param addr: address/port number tuple + @type addr: tuple""" + self.__ssl_conn.bind(addr) + + def listen(self, backlog): + """Listen for connections made to the socket. + + @param backlog: specifies the maximum number of queued connections and + should be at least 1; the maximum value is system-dependent (usually 5). + @param backlog: int + """ + self.__ssl_conn.listen(backlog) + + def set_accept_state(self): + """Set the connection to work in server mode. The handshake will be + handled automatically by read/write""" + self.__ssl_conn.set_accept_state() + + def accept(self): + """Accept an SSL connection. + + @return: pair (ssl, addr) where ssl is a new SSL connection object and + addr is the address bound to the other end of the SSL connection. + @rtype: tuple + """ + return self.__ssl_conn.accept() + + def set_connect_state(self): + """Set the connection to work in client mode. The handshake will be + handled automatically by read/write""" + self.__ssl_conn.set_connect_state() + + def connect(self, addr): + """Call the connect method of the underlying socket and set up SSL on + the socket, using the Context object supplied to this Connection object + at creation. + + @param addr: address/port number pair + @type addr: tuple + """ + self.__ssl_conn.connect(addr) + + def shutdown(self, how): + """Send the shutdown message to the Connection. + + @param how: for socket.socket this flag determines whether read, write + or both type operations are supported. OpenSSL.SSL.Connection doesn't + support this so this parameter is IGNORED + @return: true if the shutdown message exchange is completed and false + otherwise (in which case you call recv() or send() when the connection + becomes readable/writeable. + @rtype: bool + """ + return self.__ssl_conn.shutdown() + + def renegotiate(self): + """Renegotiate this connection's SSL parameters.""" + return self.__ssl_conn.renegotiate() + + def pending(self): + """@return: numbers of bytes that can be safely read from the SSL + buffer. + @rtype: int + """ + return self.__ssl_conn.pending() + + def send(self, data, *flags_arg): + """Send data to the socket. Nb. The optional flags argument is ignored. + - retained for compatibility with socket.socket interface + + @param data: data to send down the socket + @type data: string + """ + return self.__ssl_conn.send(data) + + def sendall(self, data): + self.__ssl_conn.sendall(data) + + def recv(self, size=default_buf_size): + """Receive data from the Connection. + + @param size: The maximum amount of data to be received at once + @type size: int + @return: data received. + @rtype: string + """ + return self.__ssl_conn.recv(size) + + def setblocking(self, mode): + """Set this connection's underlying socket blocking _mode_. + + @param mode: blocking mode + @type mode: int + """ + self.__ssl_conn.setblocking(mode) + + def fileno(self): + """ + @return: file descriptor number for the underlying socket + @rtype: int + """ + return self.__ssl_conn.fileno() + + def getsockopt(self, *args): + """See socket.socket.getsockopt + """ + return self.__ssl_conn.getsockopt(*args) + + def setsockopt(self, *args): + """See socket.socket.setsockopt + + @return: value of the given socket option + @rtype: int/string + """ + return self.__ssl_conn.setsockopt(*args) + + def state_string(self): + """Return the SSL state of this connection.""" + return self.__ssl_conn.state_string() + + def makefile(self, *args): + """Specific to Python socket API and required by httplib: convert + response into a file-like object. This implementation reads using recv + and copies the output into a StringIO buffer to simulate a file object + for consumption by httplib + + Nb. Ignoring optional file open mode (StringIO is generic and will + open for read and write unless a string is passed to the constructor) + and buffer size - httplib set a zero buffer size which results in recv + reading nothing + + @return: file object for data returned from socket + @rtype: cStringIO.StringO + """ + self._makefile_refs += 1 + + # Optimisation + _buf_size = self.buf_size + + i=0 + stream = BytesIO() + startTime = datetime.utcnow() + try: + dat = self.__ssl_conn.recv(_buf_size) + while dat: + i+=1 + stream.write(dat) + dat = self.__ssl_conn.recv(_buf_size) + + except (SSL.ZeroReturnError, SSL.SysCallError): + # Connection is closed - assuming here that all is well and full + # response has been received. httplib will catch an error in + # incomplete content since it checks the content-length header + # against the actual length of data received + pass + + if log.getEffectiveLevel() <= logging.DEBUG: + log.debug("Socket.makefile %d recv calls completed in %s", i, + datetime.utcnow() - startTime) + + # Make sure to rewind the buffer otherwise consumers of the content will + # read from the end of the buffer + stream.seek(0) + + return stream + + def getsockname(self): + """ + @return: the socket's own address + @rtype: + """ + return self.__ssl_conn.getsockname() + + def getpeername(self): + """ + @return: remote address to which the socket is connected + """ + return self.__ssl_conn.getpeername() + + def get_context(self): + '''Retrieve the Context object associated with this Connection. ''' + return self.__ssl_conn.get_context() + + def get_peer_certificate(self): + '''Retrieve the other side's certificate (if any) ''' + return self.__ssl_conn.get_peer_certificate() diff --git a/build/lib/ndg/httpsclient/subj_alt_name.py b/build/lib/ndg/httpsclient/subj_alt_name.py new file mode 100644 index 0000000..b2c1918 --- /dev/null +++ b/build/lib/ndg/httpsclient/subj_alt_name.py @@ -0,0 +1,153 @@ +"""NDG HTTPS Client package + +Use pyasn1 to provide support for parsing ASN.1 formatted subjectAltName +content for SSL peer verification. Code based on: + +http://stackoverflow.com/questions/5519958/how-do-i-parse-subjectaltname-extension-data-using-pyasn1 +""" +__author__ = "P J Kershaw" +__date__ = "01/02/12" +__copyright__ = "(C) 2012 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' +try: + from pyasn1.type import univ, constraint, char, namedtype, tag + +except ImportError as e: + import_error_msg = ('Error importing pyasn1, subjectAltName check for SSL ' + 'peer verification will be disabled. Import error ' + 'is: %s' % e) + import warnings + warnings.warn(import_error_msg) + class Pyasn1ImportError(ImportError): + "Raise for pyasn1 import error" + raise Pyasn1ImportError(import_error_msg) + + +MAX = 64 + + +class DirectoryString(univ.Choice): + """ASN.1 Directory string class""" + componentType = namedtype.NamedTypes( + namedtype.NamedType( + 'teletexString', char.TeletexString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'printableString', char.PrintableString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'universalString', char.UniversalString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'utf8String', char.UTF8String().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'bmpString', char.BMPString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'ia5String', char.IA5String().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + ) + + +class AttributeValue(DirectoryString): + """ASN.1 Attribute value""" + + +class AttributeType(univ.ObjectIdentifier): + """ASN.1 Attribute type""" + + +class AttributeTypeAndValue(univ.Sequence): + """ASN.1 Attribute type and value class""" + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('value', AttributeValue()), + ) + + +class RelativeDistinguishedName(univ.SetOf): + '''ASN.1 Realtive distinguished name''' + componentType = AttributeTypeAndValue() + +class RDNSequence(univ.SequenceOf): + '''ASN.1 RDN sequence class''' + componentType = RelativeDistinguishedName() + + +class Name(univ.Choice): + '''ASN.1 name class''' + componentType = namedtype.NamedTypes( + namedtype.NamedType('', RDNSequence()), + ) + + +class Extension(univ.Sequence): + '''ASN.1 extension class''' + componentType = namedtype.NamedTypes( + namedtype.NamedType('extnID', univ.ObjectIdentifier()), + namedtype.DefaultedNamedType('critical', univ.Boolean('False')), + namedtype.NamedType('extnValue', univ.OctetString()), + ) + + +class Extensions(univ.SequenceOf): + '''ASN.1 extensions class''' + componentType = Extension() + sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) + + +class AnotherName(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type-id', univ.ObjectIdentifier()), + namedtype.NamedType('value', univ.Any().subtype( + explicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 0))) + ) + + +class GeneralName(univ.Choice): + '''ASN.1 configuration for X.509 certificate subjectAltNames fields''' + componentType = namedtype.NamedTypes( + namedtype.NamedType('otherName', AnotherName().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 0))), + namedtype.NamedType('rfc822Name', char.IA5String().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 1))), + namedtype.NamedType('dNSName', char.IA5String().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 2))), +# namedtype.NamedType('x400Address', ORAddress().subtype( +# implicitTag=tag.Tag(tag.tagClassContext, +# tag.tagFormatSimple, 3))), + namedtype.NamedType('directoryName', Name().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 4))), +# namedtype.NamedType('ediPartyName', EDIPartyName().subtype( +# implicitTag=tag.Tag(tag.tagClassContext, +# tag.tagFormatSimple, 5))), + namedtype.NamedType('uniformResourceIdentifier', char.IA5String().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 6))), + namedtype.NamedType('iPAddress', univ.OctetString().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 7))), + namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 8))), + ) + + +class GeneralNames(univ.SequenceOf): + '''Sequence of names for ASN.1 subjectAltNames settings''' + componentType = GeneralName() + sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) + + +class SubjectAltName(GeneralNames): + '''ASN.1 implementation for subjectAltNames support''' + + diff --git a/build/lib/ndg/httpsclient/test/README b/build/lib/ndg/httpsclient/test/README new file mode 100644 index 0000000..cc10f87 --- /dev/null +++ b/build/lib/ndg/httpsclient/test/README @@ -0,0 +1,26 @@ +NDG HTTPS Client Unit tests directory +===================================== +The unit tests expect to connect to a simple HTTPS server listening on port +4443. An OpenSSL script is provided for this purpose in scripts/. To run, + +$ ./scripts/openssl_https_server.sh + +Unit tests +---------- +Run for example, + +$ python ./test_urllib2.py + +Troubleshooting +--------------- + * Run the openssl script from *this* directory. + * Also ensure it is has execute bits set. e.g. + + $ chmod 755 ./scripts/openssl_https_server.sh + + * You may need to set the no_proxy environment variable if you have a HTTPS + proxy in place: + + $ export no_proxy=localhost + + diff --git a/build/lib/ndg/httpsclient/test/__init__.py b/build/lib/ndg/httpsclient/test/__init__.py new file mode 100644 index 0000000..4e8196a --- /dev/null +++ b/build/lib/ndg/httpsclient/test/__init__.py @@ -0,0 +1,29 @@ +"""Unit tests package for ndg_httpsclient + +PyOpenSSL utility to make a httplib-like interface suitable for use with +urllib2 +""" +__author__ = "P J Kershaw (STFC)" +__date__ = "05/01/12" +__copyright__ = "(C) 2012 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' +import os +import unittest + +class Constants(object): + '''Convenience base class from which other unit tests can extend. Its + sets the generic data directory path''' + PORT = 4443 + PORT2 = 4444 + HOSTNAME = 'localhost' + TEST_URI = 'https://%s:%d' % (HOSTNAME, PORT) + TEST_URI2 = 'https://%s:%d' % (HOSTNAME, PORT2) + + UNITTEST_DIR = os.path.dirname(os.path.abspath(__file__)) + CACERT_DIR = os.path.join(UNITTEST_DIR, 'pki', 'ca') + SSL_CERT_FILENAME = 'localhost.crt' + SSL_CERT_FILEPATH = os.path.join(UNITTEST_DIR, 'pki', SSL_CERT_FILENAME) + SSL_PRIKEY_FILENAME = 'localhost.key' + SSL_PRIKEY_FILEPATH = os.path.join(UNITTEST_DIR, 'pki', SSL_PRIKEY_FILENAME) diff --git a/ndg/httpsclient/test/pki/ca/08bd99c7.0 b/build/lib/ndg/httpsclient/test/pki/ca/08bd99c7.0 similarity index 100% rename from ndg/httpsclient/test/pki/ca/08bd99c7.0 rename to build/lib/ndg/httpsclient/test/pki/ca/08bd99c7.0 diff --git a/ndg/httpsclient/test/pki/ca/ade0138a.0 b/build/lib/ndg/httpsclient/test/pki/ca/ade0138a.0 similarity index 100% rename from ndg/httpsclient/test/pki/ca/ade0138a.0 rename to build/lib/ndg/httpsclient/test/pki/ca/ade0138a.0 diff --git a/build/lib/ndg/httpsclient/test/pki/localhost.crt b/build/lib/ndg/httpsclient/test/pki/localhost.crt new file mode 100644 index 0000000..953c7ef --- /dev/null +++ b/build/lib/ndg/httpsclient/test/pki/localhost.crt @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: OU=Security, CN=NDG Test CA, O=NDG + Validity + Not Before: Jan 21 14:45:01 2015 GMT + Not After : Jan 20 14:45:01 2018 GMT + Subject: O=NDG, OU=Security, CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:e3:29:45:fc:56:2d:a2:21:b6:49:c6:6a:ef:b3: + ed:d3:32:47:77:d9:85:ef:e5:6a:db:70:8b:5d:41: + 4d:b5:76:f5:96:42:5a:f7:82:a5:bb:b3:e1:f9:ac: + c6:b7:71:61:f4:4d:dd:28:f6:b1:ef:65:dc:5a:8c: + 47:b1:17:38:e1:8a:5f:40:b0:bb:a0:87:61:a7:72: + f8:c1:a1:5f:3b:f7:94:b5:cb:c3:50:84:ef:a8:13: + d4:92:ff:af:3a:d1:31:42:90:4b:58:4c:84:47:a6: + 3a:a3:3d:c1:9a:43:3c:10:f6:8a:b5:97:11:b7:74: + ab:32:92:be:9a:fc:ef:5e:45:78:30:61:67:10:63: + 09:ef:61:b7:1c:47:cc:69:c9:e7:27:8f:4d:97:33: + 59:33:b8:47:89:86:4c:cd:a4:38:7c:d0:60:ee:52: + c8:e1:2a:f1:3b:9b:e9:7c:d5:af:88:33:91:9c:10: + 63:89:01:03:fb:26:5e:3f:61:c3:b4:f0:fb:1f:ad: + e8:d2:49:8e:2f:16:81:bb:9c:d6:a5:48:91:58:7d: + ac:ac:2c:02:8a:f2:f4:22:80:1f:8c:32:5b:b5:77: + d0:36:e9:27:9a:9f:31:67:d5:4e:32:8d:cf:ce:73: + ef:88:86:e9:3c:53:e6:09:55:02:2b:86:7d:91:8d: + fb:53 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + A3:77:23:B5:1C:98:85:C8:6D:31:40:1C:2F:20:57:CD:C9:36:74:1B + X509v3 Authority Key Identifier: + keyid:90:4B:D0:8C:63:8F:3A:3E:43:64:4B:EC:9B:DE:80:76:28:85:5D:68 + + X509v3 Subject Alternative Name: + DNS:localhost + Signature Algorithm: sha1WithRSAEncryption + 33:b4:87:0e:2c:71:88:6d:ab:cc:14:c8:3f:1e:8d:e5:ed:26: + 6b:b8:76:93:29:b1:0b:c5:e7:41:6f:14:62:8f:e1:81:bb:02: + 13:5c:b2:34:b7:94:f2:7d:1e:fe:e7:89:0b:2e:56:30:58:eb: + 90:d4:05:5b:18:d9:c5:68:61:c0:f6:f7:1a:0f:14:d8:89:8e: + ee:ec:59:b8:48:96:58:33:2e:98:95:56:c3:02:e9:93:cd:3f: + 4c:0d:b5:b5:b6:6e:6f:95:5f:65:eb:1a:ce:56:20:e2:72:d4: + f7:58:5f:c0:7b:49:5f:ac:6b:01:7b:c8:f0:13:19:03:dd:4e: + 05:55:f9:31:52:ea:45:eb:54:b9:4b:a2:3f:22:c7:11:47:8a: + 94:b4:e9:9e:c0:09:96:72:66:ba:01:d3:f3:00:6e:24:ca:a9: + 6d:8e:7f:0b:a0:fd:f9:c9:4f:3a:36:07:c7:4a:c7:c7:1f:c7: + e0:2d:c3:21:d0:44:68:81:38:af:ce:cb:38:be:db:02:3d:ba: + 62:00:43:94:22:c8:d7:43:cd:db:73:23:9d:28:aa:d6:4c:08: + 45:8f:b5:1d:04:c7:2b:8e:22:12:e6:af:cd:9c:13:db:c9:76: + f4:0c:10:25:fa:5c:46:77:7d:e5:ee:16:b4:f1:24:94:22:06: + 85:40:0c:5f +-----BEGIN CERTIFICATE----- +MIIDejCCAmKgAwIBAgIBATANBgkqhkiG9w0BAQUFADA3MREwDwYDVQQLDAhTZWN1 +cml0eTEUMBIGA1UEAwwLTkRHIFRlc3QgQ0ExDDAKBgNVBAoMA05ERzAeFw0xNTAx +MjExNDQ1MDFaFw0xODAxMjAxNDQ1MDFaMDUxDDAKBgNVBAoTA05ERzERMA8GA1UE +CxMIU2VjdXJpdHkxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAOMpRfxWLaIhtknGau+z7dMyR3fZhe/lattwi11BTbV2 +9ZZCWveCpbuz4fmsxrdxYfRN3Sj2se9l3FqMR7EXOOGKX0Cwu6CHYady+MGhXzv3 +lLXLw1CE76gT1JL/rzrRMUKQS1hMhEemOqM9wZpDPBD2irWXEbd0qzKSvpr8715F +eDBhZxBjCe9htxxHzGnJ5yePTZczWTO4R4mGTM2kOHzQYO5SyOEq8Tub6XzVr4gz +kZwQY4kBA/smXj9hw7Tw+x+t6NJJji8Wgbuc1qVIkVh9rKwsAory9CKAH4wyW7V3 +0DbpJ5qfMWfVTjKNz85z74iG6TxT5glVAiuGfZGN+1MCAwEAAaOBkjCBjzAJBgNV +HRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZp +Y2F0ZTAdBgNVHQ4EFgQUo3cjtRyYhchtMUAcLyBXzck2dBswHwYDVR0jBBgwFoAU +kEvQjGOPOj5DZEvsm96AdiiFXWgwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqG +SIb3DQEBBQUAA4IBAQAztIcOLHGIbavMFMg/Ho3l7SZruHaTKbELxedBbxRij+GB +uwITXLI0t5TyfR7+54kLLlYwWOuQ1AVbGNnFaGHA9vcaDxTYiY7u7Fm4SJZYMy6Y +lVbDAumTzT9MDbW1tm5vlV9l6xrOViDictT3WF/Ae0lfrGsBe8jwExkD3U4FVfkx +UupF61S5S6I/IscRR4qUtOmewAmWcma6AdPzAG4kyqltjn8LoP35yU86NgfHSsfH +H8fgLcMh0ERogTivzss4vtsCPbpiAEOUIsjXQ83bcyOdKKrWTAhFj7UdBMcrjiIS +5q/NnBPbyXb0DBAl+lxGd33l7ha08SSUIgaFQAxf +-----END CERTIFICATE----- diff --git a/build/lib/ndg/httpsclient/test/pki/localhost.key b/build/lib/ndg/httpsclient/test/pki/localhost.key new file mode 100644 index 0000000..6a11b49 --- /dev/null +++ b/build/lib/ndg/httpsclient/test/pki/localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA4ylF/FYtoiG2ScZq77Pt0zJHd9mF7+Vq23CLXUFNtXb1lkJa +94Klu7Ph+azGt3Fh9E3dKPax72XcWoxHsRc44YpfQLC7oIdhp3L4waFfO/eUtcvD +UITvqBPUkv+vOtExQpBLWEyER6Y6oz3BmkM8EPaKtZcRt3SrMpK+mvzvXkV4MGFn +EGMJ72G3HEfMacnnJ49NlzNZM7hHiYZMzaQ4fNBg7lLI4SrxO5vpfNWviDORnBBj +iQED+yZeP2HDtPD7H63o0kmOLxaBu5zWpUiRWH2srCwCivL0IoAfjDJbtXfQNukn +mp8xZ9VOMo3PznPviIbpPFPmCVUCK4Z9kY37UwIDAQABAoIBAF29Fmg+l64kAzkG +a/JmaWmRgfRvCton7aSIGLpuzEZpxIYw86DXhNSkMkf5iDcCzs0lpHHW+y4y9m9X +G+50CsDnfM8RHxvrQdz7kLM2iDoSvIcYgoyjjtHo/Pt8Dy9SS+WP7ceOK7f1XJUo +Us/5lrvZQPwWTvVJa6v+6jDC13Qqp34qNXCBZvU2WJpjM8Yau3C6ixP2ifJMmoOV +5BT7bUPwn9QT49PFDLSbKKUnvr8ClhXF2hF2B4ztm3SIjhMe7kwuU+i8yWlFiGT+ +RzSvKGGA7QtDeww5vrMEpaudQaU0MvcKbsolk/MDh0Kcy3fKKz1OSZEvvZ1hCzlr +4flLOOECgYEA+YG32ohJ5QZnaQ+DXOl/QSzAlL5ZZeSSny25tkJetLKthTcSfHNH ++gWRFujONATuA7EmgkzNC+d+3JjYJIcVODb/22yoAVQFVXvz+Ihe5UyM14r/ZV/b +4w/dLvLpWnw17BaqDwl216xnNXa/Y4IzTXwgw2G8noTKlby6You0NMcCgYEA6RKu +95+y1xMIVdfMEExRcwUDFxFA9f1WFArq2D/GYiJhOQtjXJm5nQpsiczcF0xHvXjA +6YiwFBF8L6d77ZlYb1AoKeE2me/wtRqaZtAGqnzqS7fx06hgFD8FAGxtHYXW2Ozj +rKYEb3Xqkpko+XzuLIXaXSP/TcE2PuWMRa9IIRUCgYBNYx2KS3FX9seGP4iH/eg5 +Z88Hk46Y2u9bLcyHKkjDlRPa2L0EGqF9e9KHn4etfMXyITUHfxiuE4w2kbWghsFf +ITf0b9wgJVZOMFb4hBui1T5t8C/M2pGR+K6qzC7yoMn8wv7WESJqPI/6di1giNau +tsxWrW7aX+eRz+qjfB9VqwKBgQCOfEaMyYuqok8JM7jkCdQNTfYapwigmbSC4j25 +4BsmqT/ogMbIuI3ZrKK4B45Ri+ebtHOzEUYbrqjN9UT09zcyLb2wBKe9qgrsnIvh +6LD6jw0pJxXmwFukZPZo0OBQGR9EVGXHiWLSxTKXVpzPEQoGG/pn0HbmkQTZpLmB +bGvbFQKBgQDKNboMRllLx6adl5vI8aEvX5M4BI/6RIFuMI+6glO8mf2MrC46w0a7 +jo/Z5G2GLfAZ2GXUW3PoWxWYGjxyX12LvOg+R137uzD/itqij9IRgv91X+Go27Co +ch20cYyr3Sblp2hMH9gDL+4fvtKGZKc1Bm+uI3wO61RRBl0JEYT3Ww== +-----END RSA PRIVATE KEY----- diff --git a/build/lib/ndg/httpsclient/test/scripts/openssl_https_server.sh b/build/lib/ndg/httpsclient/test/scripts/openssl_https_server.sh new file mode 100755 index 0000000..4f9c339 --- /dev/null +++ b/build/lib/ndg/httpsclient/test/scripts/openssl_https_server.sh @@ -0,0 +1,2 @@ +#!/bin/sh +openssl s_server -www -cert pki/localhost.crt -key pki/localhost.key -accept 4443 diff --git a/build/lib/ndg/httpsclient/test/test_https.py b/build/lib/ndg/httpsclient/test/test_https.py new file mode 100644 index 0000000..86e0c51 --- /dev/null +++ b/build/lib/ndg/httpsclient/test/test_https.py @@ -0,0 +1,122 @@ +"""unit tests module for ndg.httpsclient.https.HTTPSconnection class + +PyOpenSSL utility to make a httplib-like interface suitable for use with +urllib2 +""" +__author__ = "P J Kershaw (STFC)" +__date__ = "06/01/12" +__copyright__ = "(C) 2012 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' +import logging +logging.basicConfig(level=logging.DEBUG) +log = logging.getLogger(__name__) +import unittest +import socket + +from OpenSSL import SSL + +from ndg.httpsclient.test import Constants +from ndg.httpsclient.https import HTTPSConnection +from ndg.httpsclient.ssl_peer_verification import ServerSSLCertVerification + + +class TestHTTPSConnection(unittest.TestCase): + '''Test ndg HTTPS client HTTPSConnection class''' + + def test01_open(self): + conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT) + conn.connect() + conn.request('GET', '/') + resp = conn.getresponse() + print('Response = %s' % resp.read()) + conn.close() + + def test02_open_fails(self): + conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT2) + self.assertRaises(socket.error, conn.connect) + + def test03_ssl_verification_of_peer_fails(self): + ctx = SSL.Context(SSL.TLSv1_METHOD) + + def verify_callback(conn, x509, errnum, errdepth, preverify_ok): + log.debug('SSL peer certificate verification failed for %r', + x509.get_subject()) + return preverify_ok + + ctx.set_verify(SSL.VERIFY_PEER, verify_callback) + ctx.set_verify_depth(9) + + # Set bad location - unit test dir has no CA certs to verify with + ctx.load_verify_locations(None, Constants.UNITTEST_DIR) + + conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT, + ssl_context=ctx) + conn.connect() + self.assertRaises(SSL.Error, conn.request, 'GET', '/') + + def test03_ssl_verification_of_peer_succeeds(self): + ctx = SSL.Context(SSL.TLSv1_METHOD) + + verify_callback = lambda conn, x509, errnum, errdepth, preverify_ok: \ + preverify_ok + + ctx.set_verify(SSL.VERIFY_PEER, verify_callback) + ctx.set_verify_depth(9) + + # Set correct location for CA certs to verify with + ctx.load_verify_locations(None, Constants.CACERT_DIR) + + conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT, + ssl_context=ctx) + conn.connect() + conn.request('GET', '/') + resp = conn.getresponse() + print('Response = %s' % resp.read()) + + def test04_ssl_verification_with_subj_alt_name(self): + ctx = SSL.Context(SSL.TLSv1_METHOD) + + verification = ServerSSLCertVerification(hostname='localhost') + verify_callback = verification.get_verify_server_cert_func() + + ctx.set_verify(SSL.VERIFY_PEER, verify_callback) + ctx.set_verify_depth(9) + + # Set correct location for CA certs to verify with + ctx.load_verify_locations(None, Constants.CACERT_DIR) + + conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT, + ssl_context=ctx) + conn.connect() + conn.request('GET', '/') + resp = conn.getresponse() + print('Response = %s' % resp.read()) + + def test04_ssl_verification_with_subj_common_name(self): + ctx = SSL.Context(SSL.TLSv1_METHOD) + + # Explicitly set verification of peer hostname using peer certificate + # subject common name + verification = ServerSSLCertVerification(hostname='localhost', + subj_alt_name_match=False) + + verify_callback = verification.get_verify_server_cert_func() + + ctx.set_verify(SSL.VERIFY_PEER, verify_callback) + ctx.set_verify_depth(9) + + # Set correct location for CA certs to verify with + ctx.load_verify_locations(None, Constants.CACERT_DIR) + + conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT, + ssl_context=ctx) + conn.connect() + conn.request('GET', '/') + resp = conn.getresponse() + print('Response = %s' % resp.read()) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/build/lib/ndg/httpsclient/test/test_urllib2.py b/build/lib/ndg/httpsclient/test/test_urllib2.py new file mode 100644 index 0000000..366c2ca --- /dev/null +++ b/build/lib/ndg/httpsclient/test/test_urllib2.py @@ -0,0 +1,56 @@ +"""unit tests module for ndg.httpsclient.urllib2_build_opener module + +PyOpenSSL utility to make a httplib-like interface suitable for use with +urllib2 +""" +__author__ = "P J Kershaw (STFC)" +__date__ = "06/01/12" +__copyright__ = "(C) 2012 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' +import sys + +if sys.version_info[0] > 2: + from urllib.error import URLError as URLError_ +else: + from urllib2 import URLError as URLError_ + +import unittest + +from OpenSSL import SSL +from ndg.httpsclient.test import Constants +from ndg.httpsclient.urllib2_build_opener import build_opener + + +class Urllib2TestCase(unittest.TestCase): + """Unit tests for urllib2 functionality""" + + def test01_urllib2_build_opener(self): + opener = build_opener() + self.assertTrue(opener) + + def test02_open(self): + opener = build_opener() + res = opener.open(Constants.TEST_URI) + self.assertTrue(res) + print("res = %s" % res.read()) + + def test03_open_fails_unknown_loc(self): + opener = build_opener() + self.assertRaises(URLError_, opener.open, Constants.TEST_URI2) + + def test04_open_peer_cert_verification_fails(self): + # Explicitly set empty CA directory to make verification fail + ctx = SSL.Context(SSL.TLSv1_METHOD) + verify_callback = lambda conn, x509, errnum, errdepth, preverify_ok: \ + preverify_ok + + ctx.set_verify(SSL.VERIFY_PEER, verify_callback) + ctx.load_verify_locations(None, './') + opener = build_opener(ssl_context=ctx) + self.assertRaises(SSL.Error, opener.open, Constants.TEST_URI) + + +if __name__ == "__main__": + unittest.main() diff --git a/build/lib/ndg/httpsclient/test/test_utils.py b/build/lib/ndg/httpsclient/test/test_utils.py new file mode 100644 index 0000000..44382d0 --- /dev/null +++ b/build/lib/ndg/httpsclient/test/test_utils.py @@ -0,0 +1,62 @@ +"""unit tests module for ndg.httpsclient.utils module + +PyOpenSSL utility to make a httplib-like interface suitable for use with +urllib2 +""" +__author__ = "P J Kershaw (STFC)" +__date__ = "06/01/12" +__copyright__ = "(C) 2012 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' +import unittest +import os + +from OpenSSL import SSL + +from ndg.httpsclient.test import Constants +from ndg.httpsclient.utils import (Configuration, fetch_from_url, open_url, + _should_use_proxy) + + +class TestUtilsModule(unittest.TestCase): + '''Test ndg.httpsclient.utils module''' + + def test01_configuration(self): + config = Configuration(SSL.Context(SSL.TLSv1_METHOD), True) + self.assertTrue(config.ssl_context) + self.assertEqual(config.debug, True) + + def test02_fetch_from_url(self): + config = Configuration(SSL.Context(SSL.TLSv1_METHOD), True) + res = fetch_from_url(Constants.TEST_URI, config) + self.assertTrue(res) + + def test03_open_url(self): + config = Configuration(SSL.Context(SSL.TLSv1_METHOD), True) + res = open_url(Constants.TEST_URI, config) + self.assertEqual(res[0], 200, + 'open_url for %r failed' % Constants.TEST_URI) + + def test04__should_use_proxy(self): + if 'no_proxy' in os.environ: + no_proxy = os.environ['no_proxy'] + del os.environ['no_proxy'] + else: + no_proxy = None + + self.assertTrue(_should_use_proxy(Constants.TEST_URI), + 'Expecting use proxy = True') + + os.environ['no_proxy'] = 'localhost,localhost.localdomain' + self.assertFalse(_should_use_proxy(Constants.TEST_URI), + 'Expecting use proxy = False') + + if no_proxy is not None: + os.environ['no_proxy'] = no_proxy + else: + del os.environ['no_proxy'] + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/build/lib/ndg/httpsclient/urllib2_build_opener.py b/build/lib/ndg/httpsclient/urllib2_build_opener.py new file mode 100644 index 0000000..55d8632 --- /dev/null +++ b/build/lib/ndg/httpsclient/urllib2_build_opener.py @@ -0,0 +1,78 @@ +"""urllib2 style build opener integrates with HTTPSConnection class from this +package. +""" +__author__ = "P J Kershaw" +__date__ = "21/12/10" +__copyright__ = "(C) 2011 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' +import logging +import sys + +# Py 2 <=> 3 compatibility for class type checking +if sys.version_info[0] > 2: + class_type_ = type + from urllib.request import (ProxyHandler, UnknownHandler, + HTTPDefaultErrorHandler, FTPHandler, + FileHandler, HTTPErrorProcessor, + HTTPHandler, OpenerDirector, + HTTPRedirectHandler) +else: + import types + class_type_ = types.ClassType + + from urllib2 import (ProxyHandler, UnknownHandler, HTTPDefaultErrorHandler, + FTPHandler, FileHandler, HTTPErrorProcessor, + HTTPHandler, OpenerDirector, HTTPRedirectHandler) + +from ndg.httpsclient.https import HTTPSContextHandler + +log = logging.getLogger(__name__) + + +# Copied from urllib2 with modifications for ssl +def build_opener(*handlers, **kw): + """Create an opener object from a list of handlers. + + The opener will use several default handlers, including support + for HTTP and FTP. + + If any of the handlers passed as arguments are subclasses of the + default handlers, the default handlers will not be used. + """ + def isclass(obj): + return isinstance(obj, class_type_) or hasattr(obj, "__bases__") + + opener = OpenerDirector() + default_classes = [ProxyHandler, UnknownHandler, HTTPHandler, + HTTPDefaultErrorHandler, HTTPRedirectHandler, + FTPHandler, FileHandler, HTTPErrorProcessor] + check_classes = list(default_classes) + check_classes.append(HTTPSContextHandler) + skip = [] + for klass in check_classes: + for check in handlers: + if isclass(check): + if issubclass(check, klass): + skip.append(klass) + elif isinstance(check, klass): + skip.append(klass) + + for klass in default_classes: + if klass not in skip: + opener.add_handler(klass()) + + # Pick up SSL context from keyword settings + ssl_context = kw.get('ssl_context') + + # Add the HTTPS handler with ssl_context + if HTTPSContextHandler not in skip: + opener.add_handler(HTTPSContextHandler(ssl_context)) + + for h in handlers: + if isclass(h): + h = h() + opener.add_handler(h) + + return opener diff --git a/build/lib/ndg/httpsclient/utils.py b/build/lib/ndg/httpsclient/utils.py new file mode 100644 index 0000000..7e43a73 --- /dev/null +++ b/build/lib/ndg/httpsclient/utils.py @@ -0,0 +1,411 @@ +"""Utilities using NDG HTTPS Client, including a main module that can be used to +fetch from a URL. +""" +__author__ = "R B Wilkinson" +__date__ = "09/12/11" +__copyright__ = "(C) 2011 Science and Technology Facilities Council" +__license__ = "BSD - see LICENSE file in top-level directory" +__contact__ = "Philip.Kershaw@stfc.ac.uk" +__revision__ = '$Id$' + +import logging +from optparse import OptionParser +import os +import sys + +if sys.version_info[0] > 2: + import http.cookiejar as cookiejar_ + import http.client as http_client_ + from urllib.request import Request as Request_ + from urllib.request import HTTPHandler as HTTPHandler_ + from urllib.request import HTTPCookieProcessor as HTTPCookieProcessor_ + from urllib.request import HTTPBasicAuthHandler as HTTPBasicAuthHandler_ + from urllib.request import HTTPPasswordMgrWithDefaultRealm as \ + HTTPPasswordMgrWithDefaultRealm_ + from urllib.request import ProxyHandler as ProxyHandler_ + from urllib.error import HTTPError as HTTPError_ + import urllib.parse as urlparse_ +else: + import cookielib as cookiejar_ + import httplib as http_client_ + from urllib2 import Request as Request_ + from urllib2 import HTTPHandler as HTTPHandler_ + from urllib2 import HTTPCookieProcessor as HTTPCookieProcessor_ + from urllib2 import HTTPBasicAuthHandler as HTTPBasicAuthHandler_ + from urllib2 import HTTPPasswordMgrWithDefaultRealm as \ + HTTPPasswordMgrWithDefaultRealm_ + from urllib2 import ProxyHandler as ProxyHandler_ + from urllib2 import HTTPError as HTTPError_ + import urlparse as urlparse_ + +from ndg.httpsclient.urllib2_build_opener import build_opener +from ndg.httpsclient.https import HTTPSContextHandler +from ndg.httpsclient import ssl_context_util + +log = logging.getLogger(__name__) + +class AccumulatingHTTPCookieProcessor(HTTPCookieProcessor_): + """Cookie processor that adds new cookies (instead of replacing the existing + ones as HTTPCookieProcessor does) + """ + def http_request(self, request): + """Processes cookies for a HTTP request. + @param request: request to process + @type request: urllib2.Request + @return: request + @rtype: urllib2.Request + """ + COOKIE_HEADER_NAME = "Cookie" + tmp_request = Request_(request.get_full_url(), request.data, {}, + request.origin_req_host, + request.unverifiable) + self.cookiejar.add_cookie_header(tmp_request) + # Combine existing and new cookies. + new_cookies = tmp_request.get_header(COOKIE_HEADER_NAME) + if new_cookies: + if request.has_header(COOKIE_HEADER_NAME): + # Merge new cookies with existing ones. + old_cookies = request.get_header(COOKIE_HEADER_NAME) + merged_cookies = '; '.join([old_cookies, new_cookies]) + request.add_unredirected_header(COOKIE_HEADER_NAME, + merged_cookies) + else: + # No existing cookies so just set new ones. + request.add_unredirected_header(COOKIE_HEADER_NAME, new_cookies) + return request + + # Process cookies for HTTPS in the same way. + https_request = http_request + + +class URLFetchError(Exception): + """Error fetching content from URL""" + + +def fetch_from_url(url, config, data=None, handlers=None): + """Returns data retrieved from a URL. + @param url: URL to attempt to open + @type url: basestring + @param config: SSL context configuration + @type config: Configuration + @return data retrieved from URL or None + """ + return_code, return_message, response = open_url(url, config, data=data, + handlers=handlers) + if return_code and return_code == http_client_.OK: + return_data = response.read() + response.close() + return return_data + else: + raise URLFetchError(return_message) + +def fetch_from_url_to_file(url, config, output_file, data=None, handlers=None): + """Writes data retrieved from a URL to a file. + @param url: URL to attempt to open + @type url: basestring + @param config: SSL context configuration + @type config: Configuration + @param output_file: output file + @type output_file: basestring + @return: tuple ( + returned HTTP status code or 0 if an error occurred + returned message + boolean indicating whether access was successful) + """ + return_code, return_message, response = open_url(url, config, data=data, + handlers=handlers) + if return_code == http_client_.OK: + return_data = response.read() + response.close() + outfile = open(output_file, "w") + outfile.write(return_data) + outfile.close() + + return return_code, return_message, return_code == http_client_.OK + + +def fetch_stream_from_url(url, config, data=None, handlers=None): + """Returns data retrieved from a URL. + @param url: URL to attempt to open + @type url: basestring + @param config: SSL context configuration + @type config: Configuration + @param data: HTTP POST data + @type data: str + @param handlers: list of custom urllib2 handlers to add to the request + @type handlers: iterable + @return: data retrieved from URL or None + @rtype: file derived type + """ + return_code, return_message, response = open_url(url, config, data=data, + handlers=handlers) + if return_code and return_code == http_client_.OK: + return response + else: + raise URLFetchError(return_message) + + +def open_url(url, config, data=None, handlers=None): + """Attempts to open a connection to a specified URL. + @param url: URL to attempt to open + @param config: SSL context configuration + @type config: Configuration + @param data: HTTP POST data + @type data: str + @param handlers: list of custom urllib2 handlers to add to the request + @type handlers: iterable + @return: tuple ( + returned HTTP status code or 0 if an error occurred + returned message or error description + response object) + """ + debuglevel = 1 if config.debug else 0 + + # Set up handlers for URL opener. + if config.cookie: + cj = config.cookie + else: + cj = cookiejar_.CookieJar() + + # Use a cookie processor that accumulates cookies when redirects occur so + # that an application can redirect for authentication and retain both any + # cookies for the application and the security system (c.f., + # urllib2.HTTPCookieProcessor which replaces cookies). + cookie_handler = AccumulatingHTTPCookieProcessor(cj) + + if not handlers: + handlers = [] + + handlers.append(cookie_handler) + + if config.debug: + http_handler = HTTPHandler_(debuglevel=debuglevel) + https_handler = HTTPSContextHandler(config.ssl_context, + debuglevel=debuglevel) + handlers.extend([http_handler, https_handler]) + + if config.http_basicauth: + # currently only supports http basic auth + auth_handler = HTTPBasicAuthHandler_(HTTPPasswordMgrWithDefaultRealm_()) + auth_handler.add_password(realm=None, uri=url, + user=config.http_basicauth[0], + passwd=config.http_basicauth[1]) + handlers.append(auth_handler) + + + # Explicitly remove proxy handling if the host is one listed in the value of + # the no_proxy environment variable because urllib2 does use proxy settings + # set via http_proxy and https_proxy, but does not take the no_proxy value + # into account. + if not _should_use_proxy(url, config.no_proxy): + handlers.append(ProxyHandler_({})) + log.debug("Not using proxy") + elif config.proxies: + handlers.append(ProxyHandler_(config.proxies)) + log.debug("Configuring proxies: %s" % config.proxies) + + opener = build_opener(*handlers, ssl_context=config.ssl_context) + + headers = config.headers + if headers is None: + headers = {} + + request = Request_(url, data, headers) + + # Open the URL and check the response. + return_code = 0 + return_message = '' + response = None + + try: + response = opener.open(request) + return_message = response.msg + return_code = response.code + if log.isEnabledFor(logging.DEBUG): + for index, cookie in enumerate(cj): + log.debug("%s : %s", index, cookie) + + except HTTPError_ as exc: + return_code = exc.code + return_message = "Error: %s" % exc.msg + if log.isEnabledFor(logging.DEBUG): + log.debug("%s %s", exc.code, exc.msg) + + except Exception as exc: + return_message = "Error: %s" % exc.__str__() + if log.isEnabledFor(logging.DEBUG): + import traceback + log.debug(traceback.format_exc()) + + return (return_code, return_message, response) + + +def _should_use_proxy(url, no_proxy=None): + """Determines whether a proxy should be used to open a connection to the + specified URL, based on the value of the no_proxy environment variable. + @param url: URL + @type url: basestring or urllib2.Request + """ + if no_proxy is None: + no_proxy_effective = os.environ.get('no_proxy', '') + else: + no_proxy_effective = no_proxy + + urlObj = urlparse_.urlparse(_url_as_string(url)) + for np in [h.strip() for h in no_proxy_effective.split(',')]: + if urlObj.hostname == np: + return False + + return True + +def _url_as_string(url): + """Returns the URL string from a URL value that is either a string or + urllib2.Request.. + @param url: URL + @type url: basestring or urllib2.Request + @return: URL string + @rtype: basestring + """ + if isinstance(url, Request_): + return url.get_full_url() + elif isinstance(url, str): + return url + else: + raise TypeError("Expected type %r or %r" % + (str, Request_)) + + +class Configuration(object): + """Connection configuration. + """ + def __init__(self, ssl_context, debug=False, proxies=None, no_proxy=None, + cookie=None, http_basicauth=None, headers=None): + """ + @param ssl_context: SSL context to use with this configuration + @type ssl_context: OpenSSL.SSL.Context + @param debug: if True, output debugging information + @type debug: bool + @param proxies: proxies to use for + @type proxies: dict with basestring keys and values + @param no_proxy: hosts for which a proxy should not be used + @type no_proxy: basestring + @param cookie: cookies to set for request + @type cookie: cookielib.CookieJar (python 3 - http.cookiejar) + @param http_basicauth: http authentication, or None + @type http_basicauth: tuple of (username,password) + @param headers: http headers + @type headers: dict + """ + self.ssl_context = ssl_context + self.debug = debug + self.proxies = proxies + self.no_proxy = no_proxy + self.cookie = cookie + self.http_basicauth = http_basicauth + self.headers = headers + + +def main(): + '''Utility to fetch data using HTTP or HTTPS GET from a specified URL. + ''' + parser = OptionParser(usage="%prog [options] url") + parser.add_option("-c", "--certificate", dest="cert_file", metavar="FILE", + default=os.path.expanduser("~/credentials.pem"), + help="Certificate file - defaults to $HOME/credentials.pem") + parser.add_option("-k", "--private-key", dest="key_file", metavar="FILE", + default=None, + help="Private key file - defaults to the certificate file") + parser.add_option("-t", "--ca-certificate-dir", dest="ca_dir", + metavar="PATH", + default=None, + help="Trusted CA certificate file directory") + parser.add_option("-d", "--debug", action="store_true", dest="debug", + default=False, + help="Print debug information.") + parser.add_option("-p", "--post-data-file", dest="data_file", + metavar="FILE", default=None, + help="POST data file") + parser.add_option("-f", "--fetch", dest="output_file", metavar="FILE", + default=None, help="Output file") + parser.add_option("-n", "--no-verify-peer", action="store_true", + dest="no_verify_peer", default=False, + help="Skip verification of peer certificate.") + parser.add_option("-a", "--basicauth", dest="basicauth", + metavar="USER:PASSWD", + default=None, + help="HTTP authentication credentials") + parser.add_option("--header", action="append", dest="headers", + metavar="HEADER: VALUE", + help="Add HTTP header to request") + (options, args) = parser.parse_args() + if len(args) != 1: + parser.error("Incorrect number of arguments") + + url = args[0] + + if options.debug: + logging.getLogger().setLevel(logging.DEBUG) + + if options.key_file and os.path.exists(options.key_file): + key_file = options.key_file + else: + key_file = None + + if options.cert_file and os.path.exists(options.cert_file): + cert_file = options.cert_file + else: + cert_file = None + + if options.ca_dir and os.path.exists(options.ca_dir): + ca_dir = options.ca_dir + else: + ca_dir = None + + verify_peer = not options.no_verify_peer + + if options.data_file and os.path.exists(options.data_file): + data_file = open(options.data_file) + data = data_file.read() + data_file.close() + else: + data = None + + if options.basicauth: + http_basicauth = options.basicauth.split(':', 1) + else: + http_basicauth = None + + headers = {} + if options.headers: + for h in options.headers: + key, val = h.split(':', 1) + headers[key.strip()] = val.lstrip() + + # If a private key file is not specified, the key is assumed to be stored in + # the certificate file. + ssl_context = ssl_context_util.make_ssl_context(key_file, + cert_file, + None, + ca_dir, + verify_peer, + url) + + config = Configuration(ssl_context, + options.debug, + http_basicauth=http_basicauth, + headers=headers) + if options.output_file: + return_code, return_message = fetch_from_url_to_file( + url, + config, + options.output_file, + data)[:2] + raise SystemExit(return_code, return_message) + else: + data = fetch_from_url(url, config) + print(data) + + +if __name__=='__main__': + logging.basicConfig() + main() diff --git a/ndg/httpsclient/test/pki/ca/7e15277f.0 b/ndg/httpsclient/test/pki/ca/7e15277f.0 new file mode 100644 index 0000000..b9b6ac9 --- /dev/null +++ b/ndg/httpsclient/test/pki/ca/7e15277f.0 @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLjCCAhagAwIBAgIBATANBgkqhkiG9w0BAQsFADA3MRQwEgYDVQQDDAtOREcg +VGVzdCBDQTEMMAoGA1UECgwDTkRHMREwDwYDVQQLDAhTZWN1cml0eTAeFw0xNjEy +MTUyMTMyNDNaFw0yMTEyMTQyMTMyNDNaMDcxFDASBgNVBAMMC05ERyBUZXN0IENB +MQwwCgYDVQQKDANOREcxETAPBgNVBAsMCFNlY3VyaXR5MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAv8L7md7A3NvKIYndyjHV7f1xVH6gJ8JPrecxAiVs +nidsYUm8saBV/dFf1jPMQ6hdjIuxLXlopLxThkHlCes/OTxLzBFLvFQQaxLExTsX +OmgeM4q20a2JLTqkmItPIYDVTYgi0EaF+I9Z1BwWGEfYr3uTizpr48rnzpXgRwrZ +rjRc53zLKpeqyBs7Qmg5Jlzmk/A+UWJ2ryCWLY9KFE3uPstybYpwq7YayD6upVwc +vedhh+zpXU3E9r/cnlZ4sKSP2M1QSOTb2l5XnlOU/MLODYW70uItNqs1j1XPuHYh +/ikO+jmjPUtL0IE47+LcaMU6BpaOCLzcjsSn/DDMYcDB7wIDAQABo0UwQzASBgNV +HRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU9S+bGqsW +uQzR/yHDdHoU6afNTE0wDQYJKoZIhvcNAQELBQADggEBAF2scAHd7xjgnJqAUX0b +7f7QKjpuYHrt48tH+pFOh5j3IgdOqTgRLc/N8lEqoH4M0lKZKbBerM9FiQ4eXCwy +3Rswn3wXPvi3HJGgvZYUt8J6KY5+syHU4iuzuSHOjznC0lLZkRz6kfZHX+paqro4 +1CsHhCt3ew17QMWEP6UtfZfBCg+kiEfBtSsTUIth0HgdH033PWh5v+nOzGN+3o2t +ORlzpttV+0RLlsw54l51I6rCEnfKOKtEy491JUs2whUwzp9v8tG4jev3PDVWhSSS +6jaSNvW4v7SDH0pnw52ZWzaRtA/pV/dCQOlqJ87wDNPdoVGpwqtYF/7Zdw2ty4gU +oSM= +-----END CERTIFICATE----- diff --git a/ndg/httpsclient/test/pki/ca/ndg-test-ca.jks b/ndg/httpsclient/test/pki/ca/ndg-test-ca.jks deleted file mode 100644 index 28cdbb2e105d06983a5679dc924a570988e88c4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 882 zcmezO_TO6u1_mY|W(3phX_-aECCQ0EzBkWX@pTNW5qhQumJAHcMg~pHdIn8QVhfm= z7@3$D84Y;ZIJMe5+P?ELGP1HV7?>Lh8t}6*hq7?$C~;mR10dG`$|c|6CPpP> zM>4W9FgG#sGZ-{6axpbAGBT`NC!pzaadlrhhe`eN$4h0Jct5{h5V)hM?)t2=TaK-* z*H`3sKdQa?zw}3c{+0_X7#_BC-Bj|lZ&Bj1^wDhD?si|}{#rM)n-^!kvRti@Cvjx` z`_j|bUa#7fp%6G{b(^G4kxO;6vg@3s;b&(YaQMD*qpcyEwdD>6mX$@tjQ>~PZcz6x zmUNSQuvt;=-m&EJeJQCiGHWxk7oO>wa$w^7JuT~B_dl9pSK_S5bExZBSS$Awf1_@{ zrJ4KRu)L7lxG1|$%q!h+-{TEOUUEDZ6nm8uX1Fw%MfFPi(%Jt#_a8r;|26v3O$Vn( z2Ub2fwUlqpq@!HHS0%JtjvSeFm9KrS>cfYM>AO|!sH2?#e zks;yBLk-$KUmA*Y7;np&Xx~WOPi}UN#q_o zv%mD?sjnLYX3W{X(v(ri-X9iNbkx0)q0Y9nsG zV^BzFpTA1 Date: Sat, 27 Jan 2018 08:36:10 +0000 Subject: [PATCH 22/41] Make sure to exclude build dir --- .gitignore | 1 + build/lib/ndg/__init__.py | 1 - build/lib/ndg/httpsclient/LICENSE | 26 -- build/lib/ndg/httpsclient/__init__.py | 9 - build/lib/ndg/httpsclient/https.py | 131 ------ build/lib/ndg/httpsclient/ssl_context_util.py | 98 ----- .../ndg/httpsclient/ssl_peer_verification.py | 238 ---------- build/lib/ndg/httpsclient/ssl_socket.py | 282 ------------ build/lib/ndg/httpsclient/subj_alt_name.py | 153 ------- build/lib/ndg/httpsclient/test/README | 26 -- build/lib/ndg/httpsclient/test/__init__.py | 29 -- .../ndg/httpsclient/test/pki/ca/08bd99c7.0 | 20 - .../ndg/httpsclient/test/pki/ca/ade0138a.0 | 20 - .../ndg/httpsclient/test/pki/localhost.crt | 82 ---- .../ndg/httpsclient/test/pki/localhost.key | 27 -- .../test/scripts/openssl_https_server.sh | 2 - build/lib/ndg/httpsclient/test/test_https.py | 122 ------ .../lib/ndg/httpsclient/test/test_urllib2.py | 56 --- build/lib/ndg/httpsclient/test/test_utils.py | 62 --- .../ndg/httpsclient/urllib2_build_opener.py | 78 ---- build/lib/ndg/httpsclient/utils.py | 411 ------------------ 21 files changed, 1 insertion(+), 1873 deletions(-) delete mode 100644 build/lib/ndg/__init__.py delete mode 100644 build/lib/ndg/httpsclient/LICENSE delete mode 100644 build/lib/ndg/httpsclient/__init__.py delete mode 100644 build/lib/ndg/httpsclient/https.py delete mode 100644 build/lib/ndg/httpsclient/ssl_context_util.py delete mode 100644 build/lib/ndg/httpsclient/ssl_peer_verification.py delete mode 100644 build/lib/ndg/httpsclient/ssl_socket.py delete mode 100644 build/lib/ndg/httpsclient/subj_alt_name.py delete mode 100644 build/lib/ndg/httpsclient/test/README delete mode 100644 build/lib/ndg/httpsclient/test/__init__.py delete mode 100644 build/lib/ndg/httpsclient/test/pki/ca/08bd99c7.0 delete mode 100644 build/lib/ndg/httpsclient/test/pki/ca/ade0138a.0 delete mode 100644 build/lib/ndg/httpsclient/test/pki/localhost.crt delete mode 100644 build/lib/ndg/httpsclient/test/pki/localhost.key delete mode 100755 build/lib/ndg/httpsclient/test/scripts/openssl_https_server.sh delete mode 100644 build/lib/ndg/httpsclient/test/test_https.py delete mode 100644 build/lib/ndg/httpsclient/test/test_urllib2.py delete mode 100644 build/lib/ndg/httpsclient/test/test_utils.py delete mode 100644 build/lib/ndg/httpsclient/urllib2_build_opener.py delete mode 100644 build/lib/ndg/httpsclient/utils.py diff --git a/.gitignore b/.gitignore index 9c528bb..a5d5de7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /dist/ *.pyc .eggs/ +build/ diff --git a/build/lib/ndg/__init__.py b/build/lib/ndg/__init__.py deleted file mode 100644 index 69e3be5..0000000 --- a/build/lib/ndg/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/build/lib/ndg/httpsclient/LICENSE b/build/lib/ndg/httpsclient/LICENSE deleted file mode 100644 index 4098929..0000000 --- a/build/lib/ndg/httpsclient/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2012, Science & Technology Facilities Council (STFC) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of the Science & Technology Facilities Council (STFC) - nor the names of its contributors may be used to endorse or promote - products derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/build/lib/ndg/httpsclient/__init__.py b/build/lib/ndg/httpsclient/__init__.py deleted file mode 100644 index 83f2087..0000000 --- a/build/lib/ndg/httpsclient/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""ndg_httpsclient - PyOpenSSL utility to make a httplib-like interface suitable -for use with urllib2 -""" -__author__ = "P J Kershaw (STFC) and Richard Wilkinson (Tessella)" -__date__ = "09/12/11" -__copyright__ = "(C) 2011 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' diff --git a/build/lib/ndg/httpsclient/https.py b/build/lib/ndg/httpsclient/https.py deleted file mode 100644 index 41ad1c6..0000000 --- a/build/lib/ndg/httpsclient/https.py +++ /dev/null @@ -1,131 +0,0 @@ -"""ndg_httpsclient HTTPS module containing PyOpenSSL implementation of -httplib.HTTPSConnection - -PyOpenSSL utility to make a httplib-like interface suitable for use with -urllib2 -""" -__author__ = "P J Kershaw (STFC)" -__date__ = "09/12/11" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' -import logging -import socket -import sys - -if sys.version_info[0] > 2: - from http.client import HTTPS_PORT - from http.client import HTTPConnection - - from urllib.request import AbstractHTTPHandler -else: - from httplib import HTTPS_PORT - from httplib import HTTPConnection - - from urllib2 import AbstractHTTPHandler - - -from OpenSSL import SSL - -from ndg.httpsclient.ssl_socket import SSLSocket - -log = logging.getLogger(__name__) - - -class HTTPSConnection(HTTPConnection): - """This class allows communication via SSL using PyOpenSSL. - It is based on httplib.HTTPSConnection, modified to use PyOpenSSL. - - Note: This uses the constructor inherited from HTTPConnection to allow it to - be used with httplib and HTTPSContextHandler. To use the class directly with - an SSL context set ssl_context after construction. - - @cvar default_port: default port for this class (443) - @type default_port: int - @cvar default_ssl_method: default SSL method used if no SSL context is - explicitly set - defaults to version 2/3. - @type default_ssl_method: int - """ - default_port = HTTPS_PORT - default_ssl_method = SSL.SSLv23_METHOD - - def __init__(self, host, port=None, strict=None, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT, ssl_context=None): - HTTPConnection.__init__(self, host, port, strict, timeout) - if not hasattr(self, 'ssl_context'): - self.ssl_context = None - - if ssl_context is not None: - if not isinstance(ssl_context, SSL.Context): - raise TypeError('Expecting OpenSSL.SSL.Context type for "' - 'ssl_context" keyword; got %r instead' % - ssl_context) - - self.ssl_context = ssl_context - - def connect(self): - """Create SSL socket and connect to peer - """ - if getattr(self, 'ssl_context', None): - if not isinstance(self.ssl_context, SSL.Context): - raise TypeError('Expecting OpenSSL.SSL.Context type for "' - 'ssl_context" attribute; got %r instead' % - self.ssl_context) - ssl_context = self.ssl_context - else: - ssl_context = SSL.Context(self.__class__.default_ssl_method) - - sock = socket.create_connection((self.host, self.port), self.timeout) - - # Tunnel if using a proxy - ONLY available for Python 2.6.2 and above - if getattr(self, '_tunnel_host', None): - self.sock = sock - self._tunnel() - - self.sock = SSLSocket(ssl_context, sock) - - # Go to client mode. - self.sock.set_connect_state() - - def close(self): - """Close socket and shut down SSL connection""" - if hasattr(self.sock, "close"): - self.sock.close() - - -class HTTPSContextHandler(AbstractHTTPHandler): - '''HTTPS handler that allows a SSL context to be set for the SSL - connections. - ''' - https_request = AbstractHTTPHandler.do_request_ - - def __init__(self, ssl_context, debuglevel=0): - """ - @param ssl_context:SSL context - @type ssl_context: OpenSSL.SSL.Context - @param debuglevel: debug level for HTTPSHandler - @type debuglevel: int - """ - AbstractHTTPHandler.__init__(self, debuglevel) - - if ssl_context is not None: - if not isinstance(ssl_context, SSL.Context): - raise TypeError('Expecting OpenSSL.SSL.Context type for "' - 'ssl_context" keyword; got %r instead' % - ssl_context) - self.ssl_context = ssl_context - else: - self.ssl_context = SSL.Context(SSL.TLSv1_METHOD) - - def https_open(self, req): - """Opens HTTPS request - @param req: HTTP request - @return: HTTP Response object - """ - # Make a custom class extending HTTPSConnection, with the SSL context - # set as a class variable so that it is available to the connect method. - customHTTPSContextConnection = type('CustomHTTPSContextConnection', - (HTTPSConnection, object), - {'ssl_context': self.ssl_context}) - return self.do_open(customHTTPSContextConnection, req) diff --git a/build/lib/ndg/httpsclient/ssl_context_util.py b/build/lib/ndg/httpsclient/ssl_context_util.py deleted file mode 100644 index 0ed1d32..0000000 --- a/build/lib/ndg/httpsclient/ssl_context_util.py +++ /dev/null @@ -1,98 +0,0 @@ -"""ndg_httpsclient SSL Context utilities module containing convenience routines -for setting SSL context configuration. - -""" -__author__ = "P J Kershaw (STFC)" -__date__ = "09/12/11" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' -import sys - -if sys.version_info[0] > 2: - import urllib.parse as urlparse_ -else: - import urlparse as urlparse_ - -from OpenSSL import SSL - -from ndg.httpsclient.ssl_peer_verification import ServerSSLCertVerification - - -class SSlContextConfig(object): - """ - Holds configuration options for creating a SSL context. This is used as a - template to create the contexts with specific verification callbacks. - """ - def __init__(self, key_file=None, cert_file=None, pem_file=None, ca_dir=None, - verify_peer=False): - self.key_file = key_file - self.cert_file = cert_file - self.pem_file = pem_file - self.ca_dir = ca_dir - self.verify_peer = verify_peer - - -def make_ssl_context_from_config(ssl_config=False, url=None): - return make_ssl_context(ssl_config.key_file, ssl_config.cert_file, - ssl_config.pem_file, ssl_config.ca_dir, - ssl_config.verify_peer, url) - - -def make_ssl_context(key_file=None, cert_file=None, pem_file=None, ca_dir=None, - verify_peer=False, url=None, method=SSL.TLSv1_METHOD, - key_file_passphrase=None): - """ - Creates SSL context containing certificate and key file locations. - """ - ssl_context = SSL.Context(method) - - # Key file defaults to certificate file if present. - if cert_file: - ssl_context.use_certificate_file(cert_file) - - if key_file_passphrase: - passwd_cb = lambda max_passphrase_len, set_prompt, userdata: \ - key_file_passphrase - ssl_context.set_passwd_cb(passwd_cb) - - if key_file: - ssl_context.use_privatekey_file(key_file) - elif cert_file: - ssl_context.use_privatekey_file(cert_file) - - if pem_file or ca_dir: - ssl_context.load_verify_locations(pem_file, ca_dir) - - def _callback(conn, x509, errnum, errdepth, preverify_ok): - """Default certification verification callback. - Performs no checks and returns the status passed in. - """ - return preverify_ok - - verify_callback = _callback - - if verify_peer: - ssl_context.set_verify_depth(9) - if url: - set_peer_verification_for_url_hostname(ssl_context, url) - else: - ssl_context.set_verify(SSL.VERIFY_PEER, verify_callback) - else: - ssl_context.set_verify(SSL.VERIFY_NONE, verify_callback) - - return ssl_context - - -def set_peer_verification_for_url_hostname(ssl_context, url, - if_verify_enabled=False): - '''Convenience routine to set peer verification callback based on - ServerSSLCertVerification class''' - if not if_verify_enabled or (ssl_context.get_verify_mode() & SSL.VERIFY_PEER): - urlObj = urlparse_.urlparse(url) - hostname = urlObj.hostname - server_ssl_cert_verif = ServerSSLCertVerification(hostname=hostname) - verify_callback_ = server_ssl_cert_verif.get_verify_server_cert_func() - ssl_context.set_verify(SSL.VERIFY_PEER, verify_callback_) - diff --git a/build/lib/ndg/httpsclient/ssl_peer_verification.py b/build/lib/ndg/httpsclient/ssl_peer_verification.py deleted file mode 100644 index 2de390b..0000000 --- a/build/lib/ndg/httpsclient/ssl_peer_verification.py +++ /dev/null @@ -1,238 +0,0 @@ -"""ndg_httpsclient - module containing SSL peer verification class. -""" -__author__ = "P J Kershaw (STFC)" -__date__ = "09/12/11" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' -import re -import logging -log = logging.getLogger(__name__) - -try: - from ndg.httpsclient.subj_alt_name import SubjectAltName - from pyasn1.codec.der import decoder as der_decoder - SUBJ_ALT_NAME_SUPPORT = True - -except ImportError as e: - SUBJ_ALT_NAME_SUPPORT = False - SUBJ_ALT_NAME_SUPPORT_MSG = ( - 'SubjectAltName support is disabled - check pyasn1 package ' - 'installation to enable' - ) - import warnings - warnings.warn(SUBJ_ALT_NAME_SUPPORT_MSG) - - -class ServerSSLCertVerification(object): - """Check server identity. If hostname doesn't match, allow match of - host's Distinguished Name against server DN setting""" - DN_LUT = { - 'commonName': 'CN', - 'organisationalUnitName': 'OU', - 'organisation': 'O', - 'countryName': 'C', - 'emailAddress': 'EMAILADDRESS', - 'localityName': 'L', - 'stateOrProvinceName': 'ST', - 'streetAddress': 'STREET', - 'domainComponent': 'DC', - 'userid': 'UID' - } - SUBJ_ALT_NAME_EXT_NAME = 'subjectAltName' - PARSER_RE_STR = '/(%s)=' % '|'.join(list(DN_LUT.keys()) + \ - list(DN_LUT.values())) - PARSER_RE = re.compile(PARSER_RE_STR) - - __slots__ = ('__hostname', '__certDN', '__subj_alt_name_match') - - def __init__(self, certDN=None, hostname=None, subj_alt_name_match=True): - """Override parent class __init__ to enable setting of certDN - setting - - @type certDN: string - @param certDN: Set the expected Distinguished Name of the - server to avoid errors matching hostnames. This is useful - where the hostname is not fully qualified - @type hostname: string - @param hostname: hostname to match against peer certificate - subjectAltNames or subject common name - @type subj_alt_name_match: bool - @param subj_alt_name_match: flag to enable/disable matching of hostname - against peer certificate subjectAltNames. Nb. A setting of True will - be ignored if the pyasn1 package is not installed - """ - self.__certDN = None - self.__hostname = None - - if certDN is not None: - self.certDN = certDN - - if hostname is not None: - self.hostname = hostname - - if subj_alt_name_match: - if not SUBJ_ALT_NAME_SUPPORT: - log.warning('Overriding "subj_alt_name_match" keyword setting: ' - 'peer verification with subjectAltNames is disabled') - self.__subj_alt_name_match = False - else: - self.__subj_alt_name_match = True - else: - log.debug('Disabling peer verification with subject ' - 'subjectAltNames!') - self.__subj_alt_name_match = False - - def __call__(self, connection, peerCert, errorStatus, errorDepth, - preverifyOK): - """Verify server certificate - - @type connection: OpenSSL.SSL.Connection - @param connection: SSL connection object - @type peerCert: basestring - @param peerCert: server host certificate as OpenSSL.crypto.X509 - instance - @type errorStatus: int - @param errorStatus: error status passed from caller. This is the value - returned by the OpenSSL C function X509_STORE_CTX_get_error(). Look-up - x509_vfy.h in the OpenSSL source to get the meanings of the different - codes. PyOpenSSL doesn't help you! - @type errorDepth: int - @param errorDepth: a non-negative integer representing where in the - certificate chain the error occurred. If it is zero it occured in the - end entity certificate, one if it is the certificate which signed the - end entity certificate and so on. - - @type preverifyOK: int - @param preverifyOK: the error status - 0 = Error, 1 = OK of the current - SSL context irrespective of any verification checks done here. If this - function yields an OK status, it should enforce the preverifyOK value - so that any error set upstream overrides and is honoured. - @rtype: int - @return: status code - 0/False = Error, 1/True = OK - """ - if peerCert.has_expired(): - # Any expired certificate in the chain should result in an error - log.error('Certificate %r in peer certificate chain has expired', - peerCert.get_subject()) - - return False - - elif errorDepth == 0: - # Only interested in DN of last certificate in the chain - this must - # match the expected Server DN setting - peerCertSubj = peerCert.get_subject() - peerCertDN = peerCertSubj.get_components() - peerCertDN.sort() - - if self.certDN is None: - # Check hostname against peer certificate CN field instead: - if self.hostname is None: - log.error('No "hostname" or "certDN" set to check peer ' - 'certificate against') - return False - - # Check for subject alternative names - if self.__subj_alt_name_match: - dns_names = self._get_subj_alt_name(peerCert) - if self.hostname in dns_names: - return preverifyOK - - # If no subjectAltNames, default to check of subject Common Name - if peerCertSubj.commonName == self.hostname: - return preverifyOK - else: - log.error('Peer certificate CN %r doesn\'t match the ' - 'expected CN %r', peerCertSubj.commonName, - self.hostname) - return False - else: - if peerCertDN == self.certDN: - return preverifyOK - else: - log.error('Peer certificate DN %r doesn\'t match the ' - 'expected DN %r', peerCertDN, self.certDN) - return False - else: - return preverifyOK - - def get_verify_server_cert_func(self): - def verify_server_cert(connection, peerCert, errorStatus, errorDepth, - preverifyOK): - return self.__call__(connection, peerCert, errorStatus, - errorDepth, preverifyOK) - - return verify_server_cert - - @classmethod - def _get_subj_alt_name(cls, peer_cert): - '''Extract subjectAltName DNS name settings from certificate extensions - - @param peer_cert: peer certificate in SSL connection. subjectAltName - settings if any will be extracted from this - @type peer_cert: OpenSSL.crypto.X509 - ''' - # Search through extensions - dns_name = [] - general_names = SubjectAltName() - for i in range(peer_cert.get_extension_count()): - ext = peer_cert.get_extension(i) - ext_name = ext.get_short_name() - if ext_name == cls.SUBJ_ALT_NAME_EXT_NAME: - # PyOpenSSL returns extension data in ASN.1 encoded form - ext_dat = ext.get_data() - decoded_dat = der_decoder.decode(ext_dat, - asn1Spec=general_names) - - for name in decoded_dat: - if isinstance(name, SubjectAltName): - for entry in range(len(name)): - component = name.getComponentByPosition(entry) - dns_name.append(str(component.getComponent())) - - return dns_name - - def _getCertDN(self): - return self.__certDN - - def _setCertDN(self, val): - if isinstance(val, str): - # Allow for quoted DN - certDN = val.strip('"') - - dnFields = self.__class__.PARSER_RE.split(certDN) - if len(dnFields) < 2: - raise TypeError('Error parsing DN string: "%s"' % certDN) - - self.__certDN = list(zip(dnFields[1::2], dnFields[2::2])) - self.__certDN.sort() - - elif not isinstance(val, list): - for i in val: - if not len(i) == 2: - raise TypeError('Expecting list of two element DN field, ' - 'DN field value pairs for "certDN" ' - 'attribute') - self.__certDN = val - else: - raise TypeError('Expecting list or string type for "certDN" ' - 'attribute') - - certDN = property(fget=_getCertDN, - fset=_setCertDN, - doc="Distinguished Name for Server Certificate") - - # Get/Set Property methods - def _getHostname(self): - return self.__hostname - - def _setHostname(self, val): - if not isinstance(val, str): - raise TypeError("Expecting string type for hostname " - "attribute") - self.__hostname = val - - hostname = property(fget=_getHostname, - fset=_setHostname, - doc="hostname of server") diff --git a/build/lib/ndg/httpsclient/ssl_socket.py b/build/lib/ndg/httpsclient/ssl_socket.py deleted file mode 100644 index 7780314..0000000 --- a/build/lib/ndg/httpsclient/ssl_socket.py +++ /dev/null @@ -1,282 +0,0 @@ -"""PyOpenSSL utilities including HTTPSSocket class which wraps PyOpenSSL -SSL connection into a httplib-like interface suitable for use with urllib2 - -""" -__author__ = "P J Kershaw" -__date__ = "21/12/10" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' - -from datetime import datetime -import logging -import socket -from io import BytesIO - -from OpenSSL import SSL - -log = logging.getLogger(__name__) - - -class SSLSocket(object): - """SSL Socket class wraps pyOpenSSL's SSL.Connection class implementing - the makefile method so that it is compatible with the standard socket - interface and usable with httplib. - - @cvar default_buf_size: default buffer size for recv operations in the - makefile method - @type default_buf_size: int - """ - default_buf_size = 8192 - - def __init__(self, ctx, sock=None): - """Create SSL socket object - - @param ctx: SSL context - @type ctx: OpenSSL.SSL.Context - @param sock: underlying socket object - @type sock: socket.socket - """ - if sock is not None: - self.socket = sock - else: - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - - self.__ssl_conn = SSL.Connection(ctx, self.socket) - self.buf_size = self.__class__.default_buf_size - self._makefile_refs = 0 - - def __del__(self): - """Close underlying socket when this object goes out of scope - """ - self.close() - - @property - def buf_size(self): - """Buffer size for makefile method recv() operations""" - return self.__buf_size - - @buf_size.setter - def buf_size(self, value): - """Buffer size for makefile method recv() operations""" - if not isinstance(value, int): - raise TypeError('Expecting int type for "buf_size"; ' - 'got %r instead' % type(value)) - self.__buf_size = value - - def close(self): - """Shutdown the SSL connection and call the close method of the - underlying socket""" - if self._makefile_refs < 1: - try: - self.__ssl_conn.shutdown() - except (SSL.Error, SSL.SysCallError): - # Make errors on shutdown non-fatal - pass - else: - self._makefile_refs -= 1 - - def set_shutdown(self, mode): - """Set the shutdown state of the Connection. - @param mode: bit vector of either or both of SENT_SHUTDOWN and - RECEIVED_SHUTDOWN - """ - self.__ssl_conn.set_shutdown(mode) - - def get_shutdown(self): - """Get the shutdown state of the Connection. - @return: bit vector of either or both of SENT_SHUTDOWN and - RECEIVED_SHUTDOWN - """ - return self.__ssl_conn.get_shutdown() - - def bind(self, addr): - """bind to the given address - calls method of the underlying socket - @param addr: address/port number tuple - @type addr: tuple""" - self.__ssl_conn.bind(addr) - - def listen(self, backlog): - """Listen for connections made to the socket. - - @param backlog: specifies the maximum number of queued connections and - should be at least 1; the maximum value is system-dependent (usually 5). - @param backlog: int - """ - self.__ssl_conn.listen(backlog) - - def set_accept_state(self): - """Set the connection to work in server mode. The handshake will be - handled automatically by read/write""" - self.__ssl_conn.set_accept_state() - - def accept(self): - """Accept an SSL connection. - - @return: pair (ssl, addr) where ssl is a new SSL connection object and - addr is the address bound to the other end of the SSL connection. - @rtype: tuple - """ - return self.__ssl_conn.accept() - - def set_connect_state(self): - """Set the connection to work in client mode. The handshake will be - handled automatically by read/write""" - self.__ssl_conn.set_connect_state() - - def connect(self, addr): - """Call the connect method of the underlying socket and set up SSL on - the socket, using the Context object supplied to this Connection object - at creation. - - @param addr: address/port number pair - @type addr: tuple - """ - self.__ssl_conn.connect(addr) - - def shutdown(self, how): - """Send the shutdown message to the Connection. - - @param how: for socket.socket this flag determines whether read, write - or both type operations are supported. OpenSSL.SSL.Connection doesn't - support this so this parameter is IGNORED - @return: true if the shutdown message exchange is completed and false - otherwise (in which case you call recv() or send() when the connection - becomes readable/writeable. - @rtype: bool - """ - return self.__ssl_conn.shutdown() - - def renegotiate(self): - """Renegotiate this connection's SSL parameters.""" - return self.__ssl_conn.renegotiate() - - def pending(self): - """@return: numbers of bytes that can be safely read from the SSL - buffer. - @rtype: int - """ - return self.__ssl_conn.pending() - - def send(self, data, *flags_arg): - """Send data to the socket. Nb. The optional flags argument is ignored. - - retained for compatibility with socket.socket interface - - @param data: data to send down the socket - @type data: string - """ - return self.__ssl_conn.send(data) - - def sendall(self, data): - self.__ssl_conn.sendall(data) - - def recv(self, size=default_buf_size): - """Receive data from the Connection. - - @param size: The maximum amount of data to be received at once - @type size: int - @return: data received. - @rtype: string - """ - return self.__ssl_conn.recv(size) - - def setblocking(self, mode): - """Set this connection's underlying socket blocking _mode_. - - @param mode: blocking mode - @type mode: int - """ - self.__ssl_conn.setblocking(mode) - - def fileno(self): - """ - @return: file descriptor number for the underlying socket - @rtype: int - """ - return self.__ssl_conn.fileno() - - def getsockopt(self, *args): - """See socket.socket.getsockopt - """ - return self.__ssl_conn.getsockopt(*args) - - def setsockopt(self, *args): - """See socket.socket.setsockopt - - @return: value of the given socket option - @rtype: int/string - """ - return self.__ssl_conn.setsockopt(*args) - - def state_string(self): - """Return the SSL state of this connection.""" - return self.__ssl_conn.state_string() - - def makefile(self, *args): - """Specific to Python socket API and required by httplib: convert - response into a file-like object. This implementation reads using recv - and copies the output into a StringIO buffer to simulate a file object - for consumption by httplib - - Nb. Ignoring optional file open mode (StringIO is generic and will - open for read and write unless a string is passed to the constructor) - and buffer size - httplib set a zero buffer size which results in recv - reading nothing - - @return: file object for data returned from socket - @rtype: cStringIO.StringO - """ - self._makefile_refs += 1 - - # Optimisation - _buf_size = self.buf_size - - i=0 - stream = BytesIO() - startTime = datetime.utcnow() - try: - dat = self.__ssl_conn.recv(_buf_size) - while dat: - i+=1 - stream.write(dat) - dat = self.__ssl_conn.recv(_buf_size) - - except (SSL.ZeroReturnError, SSL.SysCallError): - # Connection is closed - assuming here that all is well and full - # response has been received. httplib will catch an error in - # incomplete content since it checks the content-length header - # against the actual length of data received - pass - - if log.getEffectiveLevel() <= logging.DEBUG: - log.debug("Socket.makefile %d recv calls completed in %s", i, - datetime.utcnow() - startTime) - - # Make sure to rewind the buffer otherwise consumers of the content will - # read from the end of the buffer - stream.seek(0) - - return stream - - def getsockname(self): - """ - @return: the socket's own address - @rtype: - """ - return self.__ssl_conn.getsockname() - - def getpeername(self): - """ - @return: remote address to which the socket is connected - """ - return self.__ssl_conn.getpeername() - - def get_context(self): - '''Retrieve the Context object associated with this Connection. ''' - return self.__ssl_conn.get_context() - - def get_peer_certificate(self): - '''Retrieve the other side's certificate (if any) ''' - return self.__ssl_conn.get_peer_certificate() diff --git a/build/lib/ndg/httpsclient/subj_alt_name.py b/build/lib/ndg/httpsclient/subj_alt_name.py deleted file mode 100644 index b2c1918..0000000 --- a/build/lib/ndg/httpsclient/subj_alt_name.py +++ /dev/null @@ -1,153 +0,0 @@ -"""NDG HTTPS Client package - -Use pyasn1 to provide support for parsing ASN.1 formatted subjectAltName -content for SSL peer verification. Code based on: - -http://stackoverflow.com/questions/5519958/how-do-i-parse-subjectaltname-extension-data-using-pyasn1 -""" -__author__ = "P J Kershaw" -__date__ = "01/02/12" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' -try: - from pyasn1.type import univ, constraint, char, namedtype, tag - -except ImportError as e: - import_error_msg = ('Error importing pyasn1, subjectAltName check for SSL ' - 'peer verification will be disabled. Import error ' - 'is: %s' % e) - import warnings - warnings.warn(import_error_msg) - class Pyasn1ImportError(ImportError): - "Raise for pyasn1 import error" - raise Pyasn1ImportError(import_error_msg) - - -MAX = 64 - - -class DirectoryString(univ.Choice): - """ASN.1 Directory string class""" - componentType = namedtype.NamedTypes( - namedtype.NamedType( - 'teletexString', char.TeletexString().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'printableString', char.PrintableString().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'universalString', char.UniversalString().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'utf8String', char.UTF8String().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'bmpString', char.BMPString().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'ia5String', char.IA5String().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - ) - - -class AttributeValue(DirectoryString): - """ASN.1 Attribute value""" - - -class AttributeType(univ.ObjectIdentifier): - """ASN.1 Attribute type""" - - -class AttributeTypeAndValue(univ.Sequence): - """ASN.1 Attribute type and value class""" - componentType = namedtype.NamedTypes( - namedtype.NamedType('type', AttributeType()), - namedtype.NamedType('value', AttributeValue()), - ) - - -class RelativeDistinguishedName(univ.SetOf): - '''ASN.1 Realtive distinguished name''' - componentType = AttributeTypeAndValue() - -class RDNSequence(univ.SequenceOf): - '''ASN.1 RDN sequence class''' - componentType = RelativeDistinguishedName() - - -class Name(univ.Choice): - '''ASN.1 name class''' - componentType = namedtype.NamedTypes( - namedtype.NamedType('', RDNSequence()), - ) - - -class Extension(univ.Sequence): - '''ASN.1 extension class''' - componentType = namedtype.NamedTypes( - namedtype.NamedType('extnID', univ.ObjectIdentifier()), - namedtype.DefaultedNamedType('critical', univ.Boolean('False')), - namedtype.NamedType('extnValue', univ.OctetString()), - ) - - -class Extensions(univ.SequenceOf): - '''ASN.1 extensions class''' - componentType = Extension() - sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) - - -class AnotherName(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('type-id', univ.ObjectIdentifier()), - namedtype.NamedType('value', univ.Any().subtype( - explicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 0))) - ) - - -class GeneralName(univ.Choice): - '''ASN.1 configuration for X.509 certificate subjectAltNames fields''' - componentType = namedtype.NamedTypes( - namedtype.NamedType('otherName', AnotherName().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 0))), - namedtype.NamedType('rfc822Name', char.IA5String().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 1))), - namedtype.NamedType('dNSName', char.IA5String().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 2))), -# namedtype.NamedType('x400Address', ORAddress().subtype( -# implicitTag=tag.Tag(tag.tagClassContext, -# tag.tagFormatSimple, 3))), - namedtype.NamedType('directoryName', Name().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 4))), -# namedtype.NamedType('ediPartyName', EDIPartyName().subtype( -# implicitTag=tag.Tag(tag.tagClassContext, -# tag.tagFormatSimple, 5))), - namedtype.NamedType('uniformResourceIdentifier', char.IA5String().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 6))), - namedtype.NamedType('iPAddress', univ.OctetString().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 7))), - namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 8))), - ) - - -class GeneralNames(univ.SequenceOf): - '''Sequence of names for ASN.1 subjectAltNames settings''' - componentType = GeneralName() - sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) - - -class SubjectAltName(GeneralNames): - '''ASN.1 implementation for subjectAltNames support''' - - diff --git a/build/lib/ndg/httpsclient/test/README b/build/lib/ndg/httpsclient/test/README deleted file mode 100644 index cc10f87..0000000 --- a/build/lib/ndg/httpsclient/test/README +++ /dev/null @@ -1,26 +0,0 @@ -NDG HTTPS Client Unit tests directory -===================================== -The unit tests expect to connect to a simple HTTPS server listening on port -4443. An OpenSSL script is provided for this purpose in scripts/. To run, - -$ ./scripts/openssl_https_server.sh - -Unit tests ----------- -Run for example, - -$ python ./test_urllib2.py - -Troubleshooting ---------------- - * Run the openssl script from *this* directory. - * Also ensure it is has execute bits set. e.g. - - $ chmod 755 ./scripts/openssl_https_server.sh - - * You may need to set the no_proxy environment variable if you have a HTTPS - proxy in place: - - $ export no_proxy=localhost - - diff --git a/build/lib/ndg/httpsclient/test/__init__.py b/build/lib/ndg/httpsclient/test/__init__.py deleted file mode 100644 index 4e8196a..0000000 --- a/build/lib/ndg/httpsclient/test/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Unit tests package for ndg_httpsclient - -PyOpenSSL utility to make a httplib-like interface suitable for use with -urllib2 -""" -__author__ = "P J Kershaw (STFC)" -__date__ = "05/01/12" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' -import os -import unittest - -class Constants(object): - '''Convenience base class from which other unit tests can extend. Its - sets the generic data directory path''' - PORT = 4443 - PORT2 = 4444 - HOSTNAME = 'localhost' - TEST_URI = 'https://%s:%d' % (HOSTNAME, PORT) - TEST_URI2 = 'https://%s:%d' % (HOSTNAME, PORT2) - - UNITTEST_DIR = os.path.dirname(os.path.abspath(__file__)) - CACERT_DIR = os.path.join(UNITTEST_DIR, 'pki', 'ca') - SSL_CERT_FILENAME = 'localhost.crt' - SSL_CERT_FILEPATH = os.path.join(UNITTEST_DIR, 'pki', SSL_CERT_FILENAME) - SSL_PRIKEY_FILENAME = 'localhost.key' - SSL_PRIKEY_FILEPATH = os.path.join(UNITTEST_DIR, 'pki', SSL_PRIKEY_FILENAME) diff --git a/build/lib/ndg/httpsclient/test/pki/ca/08bd99c7.0 b/build/lib/ndg/httpsclient/test/pki/ca/08bd99c7.0 deleted file mode 100644 index 63ef7c3..0000000 --- a/build/lib/ndg/httpsclient/test/pki/ca/08bd99c7.0 +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDLjCCAhagAwIBAgIBATANBgkqhkiG9w0BAQUFADA3MREwDwYDVQQLDAhTZWN1 -cml0eTEUMBIGA1UEAwwLTkRHIFRlc3QgQ0ExDDAKBgNVBAoMA05ERzAeFw0xNTAx -MjExNDMzMThaFw0yMDAxMjAxNDMzMThaMDcxETAPBgNVBAsMCFNlY3VyaXR5MRQw -EgYDVQQDDAtOREcgVGVzdCBDQTEMMAoGA1UECgwDTkRHMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEArq4QKUTRq45nCDR/p+OlHIIN8+ugUbiCfteazbTG -rX8vIQ9HxSuz/xvxTw+E0KgA4YSK2SJJP4QiCjlMKYS3Rt8o361GNtnRmeo5qyBu -GMSv73XL1uuqumggUZyrhhksckR7gyNFnKVXzZjAQPepsT0xBjs5uEAEqXJzAf+r -24AnT3MZRh7gsyEe3sZjd75kZVwcrWhrocyKlMCR77yEr+uP4pg+dEMhDMKKxlaF -C5RPMotOpWm/7AToHrGia34WSmcxvuOwxOkI4xEW6mxWMaVTBCXUh6Wb/0m/x8Nv -9VvS2UBC4sCp4MqlDpySxQpT1RgrhMTEmtUOh50l4eEhdwIDAQABo0UwQzASBgNV -HRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUkEvQjGOP -Oj5DZEvsm96AdiiFXWgwDQYJKoZIhvcNAQEFBQADggEBAGD0kQASmNzvtYL+JUGf -gTPyJhADl9Ai9GvZJsY/wX0IRTxRl5y08Dqlg3qyGG3GzL918cr1sVCYnLepNQES -T0MIz50DCKGryNSc74JHPDxpYaSV6whmNH5iwh8fy6tmJwF3FWbGXD2ddc+ofJqP -WPPJtzqxuuJ6iXQIFqD9mEn3iXVcvFuSzpdpH9paORTKB0j4gya9zctB8LP0ZXIE -//wREc+4msnmoTn+qkFAOPBg9WnvoipfyCXPgbTagxlofVjZ7gAgYIefqhXBTQdd -5tnYdyQQBRcUXQS2bBX03q8ftcxOjc3SvXI4MvrqofuFPwu4GnrspnC0KQYlXwEI -7ds= ------END CERTIFICATE----- diff --git a/build/lib/ndg/httpsclient/test/pki/ca/ade0138a.0 b/build/lib/ndg/httpsclient/test/pki/ca/ade0138a.0 deleted file mode 100644 index 63ef7c3..0000000 --- a/build/lib/ndg/httpsclient/test/pki/ca/ade0138a.0 +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDLjCCAhagAwIBAgIBATANBgkqhkiG9w0BAQUFADA3MREwDwYDVQQLDAhTZWN1 -cml0eTEUMBIGA1UEAwwLTkRHIFRlc3QgQ0ExDDAKBgNVBAoMA05ERzAeFw0xNTAx -MjExNDMzMThaFw0yMDAxMjAxNDMzMThaMDcxETAPBgNVBAsMCFNlY3VyaXR5MRQw -EgYDVQQDDAtOREcgVGVzdCBDQTEMMAoGA1UECgwDTkRHMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEArq4QKUTRq45nCDR/p+OlHIIN8+ugUbiCfteazbTG -rX8vIQ9HxSuz/xvxTw+E0KgA4YSK2SJJP4QiCjlMKYS3Rt8o361GNtnRmeo5qyBu -GMSv73XL1uuqumggUZyrhhksckR7gyNFnKVXzZjAQPepsT0xBjs5uEAEqXJzAf+r -24AnT3MZRh7gsyEe3sZjd75kZVwcrWhrocyKlMCR77yEr+uP4pg+dEMhDMKKxlaF -C5RPMotOpWm/7AToHrGia34WSmcxvuOwxOkI4xEW6mxWMaVTBCXUh6Wb/0m/x8Nv -9VvS2UBC4sCp4MqlDpySxQpT1RgrhMTEmtUOh50l4eEhdwIDAQABo0UwQzASBgNV -HRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUkEvQjGOP -Oj5DZEvsm96AdiiFXWgwDQYJKoZIhvcNAQEFBQADggEBAGD0kQASmNzvtYL+JUGf -gTPyJhADl9Ai9GvZJsY/wX0IRTxRl5y08Dqlg3qyGG3GzL918cr1sVCYnLepNQES -T0MIz50DCKGryNSc74JHPDxpYaSV6whmNH5iwh8fy6tmJwF3FWbGXD2ddc+ofJqP -WPPJtzqxuuJ6iXQIFqD9mEn3iXVcvFuSzpdpH9paORTKB0j4gya9zctB8LP0ZXIE -//wREc+4msnmoTn+qkFAOPBg9WnvoipfyCXPgbTagxlofVjZ7gAgYIefqhXBTQdd -5tnYdyQQBRcUXQS2bBX03q8ftcxOjc3SvXI4MvrqofuFPwu4GnrspnC0KQYlXwEI -7ds= ------END CERTIFICATE----- diff --git a/build/lib/ndg/httpsclient/test/pki/localhost.crt b/build/lib/ndg/httpsclient/test/pki/localhost.crt deleted file mode 100644 index 953c7ef..0000000 --- a/build/lib/ndg/httpsclient/test/pki/localhost.crt +++ /dev/null @@ -1,82 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1 (0x1) - Signature Algorithm: sha1WithRSAEncryption - Issuer: OU=Security, CN=NDG Test CA, O=NDG - Validity - Not Before: Jan 21 14:45:01 2015 GMT - Not After : Jan 20 14:45:01 2018 GMT - Subject: O=NDG, OU=Security, CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (2048 bit) - Modulus (2048 bit): - 00:e3:29:45:fc:56:2d:a2:21:b6:49:c6:6a:ef:b3: - ed:d3:32:47:77:d9:85:ef:e5:6a:db:70:8b:5d:41: - 4d:b5:76:f5:96:42:5a:f7:82:a5:bb:b3:e1:f9:ac: - c6:b7:71:61:f4:4d:dd:28:f6:b1:ef:65:dc:5a:8c: - 47:b1:17:38:e1:8a:5f:40:b0:bb:a0:87:61:a7:72: - f8:c1:a1:5f:3b:f7:94:b5:cb:c3:50:84:ef:a8:13: - d4:92:ff:af:3a:d1:31:42:90:4b:58:4c:84:47:a6: - 3a:a3:3d:c1:9a:43:3c:10:f6:8a:b5:97:11:b7:74: - ab:32:92:be:9a:fc:ef:5e:45:78:30:61:67:10:63: - 09:ef:61:b7:1c:47:cc:69:c9:e7:27:8f:4d:97:33: - 59:33:b8:47:89:86:4c:cd:a4:38:7c:d0:60:ee:52: - c8:e1:2a:f1:3b:9b:e9:7c:d5:af:88:33:91:9c:10: - 63:89:01:03:fb:26:5e:3f:61:c3:b4:f0:fb:1f:ad: - e8:d2:49:8e:2f:16:81:bb:9c:d6:a5:48:91:58:7d: - ac:ac:2c:02:8a:f2:f4:22:80:1f:8c:32:5b:b5:77: - d0:36:e9:27:9a:9f:31:67:d5:4e:32:8d:cf:ce:73: - ef:88:86:e9:3c:53:e6:09:55:02:2b:86:7d:91:8d: - fb:53 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - Netscape Comment: - OpenSSL Generated Certificate - X509v3 Subject Key Identifier: - A3:77:23:B5:1C:98:85:C8:6D:31:40:1C:2F:20:57:CD:C9:36:74:1B - X509v3 Authority Key Identifier: - keyid:90:4B:D0:8C:63:8F:3A:3E:43:64:4B:EC:9B:DE:80:76:28:85:5D:68 - - X509v3 Subject Alternative Name: - DNS:localhost - Signature Algorithm: sha1WithRSAEncryption - 33:b4:87:0e:2c:71:88:6d:ab:cc:14:c8:3f:1e:8d:e5:ed:26: - 6b:b8:76:93:29:b1:0b:c5:e7:41:6f:14:62:8f:e1:81:bb:02: - 13:5c:b2:34:b7:94:f2:7d:1e:fe:e7:89:0b:2e:56:30:58:eb: - 90:d4:05:5b:18:d9:c5:68:61:c0:f6:f7:1a:0f:14:d8:89:8e: - ee:ec:59:b8:48:96:58:33:2e:98:95:56:c3:02:e9:93:cd:3f: - 4c:0d:b5:b5:b6:6e:6f:95:5f:65:eb:1a:ce:56:20:e2:72:d4: - f7:58:5f:c0:7b:49:5f:ac:6b:01:7b:c8:f0:13:19:03:dd:4e: - 05:55:f9:31:52:ea:45:eb:54:b9:4b:a2:3f:22:c7:11:47:8a: - 94:b4:e9:9e:c0:09:96:72:66:ba:01:d3:f3:00:6e:24:ca:a9: - 6d:8e:7f:0b:a0:fd:f9:c9:4f:3a:36:07:c7:4a:c7:c7:1f:c7: - e0:2d:c3:21:d0:44:68:81:38:af:ce:cb:38:be:db:02:3d:ba: - 62:00:43:94:22:c8:d7:43:cd:db:73:23:9d:28:aa:d6:4c:08: - 45:8f:b5:1d:04:c7:2b:8e:22:12:e6:af:cd:9c:13:db:c9:76: - f4:0c:10:25:fa:5c:46:77:7d:e5:ee:16:b4:f1:24:94:22:06: - 85:40:0c:5f ------BEGIN CERTIFICATE----- -MIIDejCCAmKgAwIBAgIBATANBgkqhkiG9w0BAQUFADA3MREwDwYDVQQLDAhTZWN1 -cml0eTEUMBIGA1UEAwwLTkRHIFRlc3QgQ0ExDDAKBgNVBAoMA05ERzAeFw0xNTAx -MjExNDQ1MDFaFw0xODAxMjAxNDQ1MDFaMDUxDDAKBgNVBAoTA05ERzERMA8GA1UE -CxMIU2VjdXJpdHkxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAOMpRfxWLaIhtknGau+z7dMyR3fZhe/lattwi11BTbV2 -9ZZCWveCpbuz4fmsxrdxYfRN3Sj2se9l3FqMR7EXOOGKX0Cwu6CHYady+MGhXzv3 -lLXLw1CE76gT1JL/rzrRMUKQS1hMhEemOqM9wZpDPBD2irWXEbd0qzKSvpr8715F -eDBhZxBjCe9htxxHzGnJ5yePTZczWTO4R4mGTM2kOHzQYO5SyOEq8Tub6XzVr4gz -kZwQY4kBA/smXj9hw7Tw+x+t6NJJji8Wgbuc1qVIkVh9rKwsAory9CKAH4wyW7V3 -0DbpJ5qfMWfVTjKNz85z74iG6TxT5glVAiuGfZGN+1MCAwEAAaOBkjCBjzAJBgNV -HRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZp -Y2F0ZTAdBgNVHQ4EFgQUo3cjtRyYhchtMUAcLyBXzck2dBswHwYDVR0jBBgwFoAU -kEvQjGOPOj5DZEvsm96AdiiFXWgwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqG -SIb3DQEBBQUAA4IBAQAztIcOLHGIbavMFMg/Ho3l7SZruHaTKbELxedBbxRij+GB -uwITXLI0t5TyfR7+54kLLlYwWOuQ1AVbGNnFaGHA9vcaDxTYiY7u7Fm4SJZYMy6Y -lVbDAumTzT9MDbW1tm5vlV9l6xrOViDictT3WF/Ae0lfrGsBe8jwExkD3U4FVfkx -UupF61S5S6I/IscRR4qUtOmewAmWcma6AdPzAG4kyqltjn8LoP35yU86NgfHSsfH -H8fgLcMh0ERogTivzss4vtsCPbpiAEOUIsjXQ83bcyOdKKrWTAhFj7UdBMcrjiIS -5q/NnBPbyXb0DBAl+lxGd33l7ha08SSUIgaFQAxf ------END CERTIFICATE----- diff --git a/build/lib/ndg/httpsclient/test/pki/localhost.key b/build/lib/ndg/httpsclient/test/pki/localhost.key deleted file mode 100644 index 6a11b49..0000000 --- a/build/lib/ndg/httpsclient/test/pki/localhost.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA4ylF/FYtoiG2ScZq77Pt0zJHd9mF7+Vq23CLXUFNtXb1lkJa -94Klu7Ph+azGt3Fh9E3dKPax72XcWoxHsRc44YpfQLC7oIdhp3L4waFfO/eUtcvD -UITvqBPUkv+vOtExQpBLWEyER6Y6oz3BmkM8EPaKtZcRt3SrMpK+mvzvXkV4MGFn -EGMJ72G3HEfMacnnJ49NlzNZM7hHiYZMzaQ4fNBg7lLI4SrxO5vpfNWviDORnBBj -iQED+yZeP2HDtPD7H63o0kmOLxaBu5zWpUiRWH2srCwCivL0IoAfjDJbtXfQNukn -mp8xZ9VOMo3PznPviIbpPFPmCVUCK4Z9kY37UwIDAQABAoIBAF29Fmg+l64kAzkG -a/JmaWmRgfRvCton7aSIGLpuzEZpxIYw86DXhNSkMkf5iDcCzs0lpHHW+y4y9m9X -G+50CsDnfM8RHxvrQdz7kLM2iDoSvIcYgoyjjtHo/Pt8Dy9SS+WP7ceOK7f1XJUo -Us/5lrvZQPwWTvVJa6v+6jDC13Qqp34qNXCBZvU2WJpjM8Yau3C6ixP2ifJMmoOV -5BT7bUPwn9QT49PFDLSbKKUnvr8ClhXF2hF2B4ztm3SIjhMe7kwuU+i8yWlFiGT+ -RzSvKGGA7QtDeww5vrMEpaudQaU0MvcKbsolk/MDh0Kcy3fKKz1OSZEvvZ1hCzlr -4flLOOECgYEA+YG32ohJ5QZnaQ+DXOl/QSzAlL5ZZeSSny25tkJetLKthTcSfHNH -+gWRFujONATuA7EmgkzNC+d+3JjYJIcVODb/22yoAVQFVXvz+Ihe5UyM14r/ZV/b -4w/dLvLpWnw17BaqDwl216xnNXa/Y4IzTXwgw2G8noTKlby6You0NMcCgYEA6RKu -95+y1xMIVdfMEExRcwUDFxFA9f1WFArq2D/GYiJhOQtjXJm5nQpsiczcF0xHvXjA -6YiwFBF8L6d77ZlYb1AoKeE2me/wtRqaZtAGqnzqS7fx06hgFD8FAGxtHYXW2Ozj -rKYEb3Xqkpko+XzuLIXaXSP/TcE2PuWMRa9IIRUCgYBNYx2KS3FX9seGP4iH/eg5 -Z88Hk46Y2u9bLcyHKkjDlRPa2L0EGqF9e9KHn4etfMXyITUHfxiuE4w2kbWghsFf -ITf0b9wgJVZOMFb4hBui1T5t8C/M2pGR+K6qzC7yoMn8wv7WESJqPI/6di1giNau -tsxWrW7aX+eRz+qjfB9VqwKBgQCOfEaMyYuqok8JM7jkCdQNTfYapwigmbSC4j25 -4BsmqT/ogMbIuI3ZrKK4B45Ri+ebtHOzEUYbrqjN9UT09zcyLb2wBKe9qgrsnIvh -6LD6jw0pJxXmwFukZPZo0OBQGR9EVGXHiWLSxTKXVpzPEQoGG/pn0HbmkQTZpLmB -bGvbFQKBgQDKNboMRllLx6adl5vI8aEvX5M4BI/6RIFuMI+6glO8mf2MrC46w0a7 -jo/Z5G2GLfAZ2GXUW3PoWxWYGjxyX12LvOg+R137uzD/itqij9IRgv91X+Go27Co -ch20cYyr3Sblp2hMH9gDL+4fvtKGZKc1Bm+uI3wO61RRBl0JEYT3Ww== ------END RSA PRIVATE KEY----- diff --git a/build/lib/ndg/httpsclient/test/scripts/openssl_https_server.sh b/build/lib/ndg/httpsclient/test/scripts/openssl_https_server.sh deleted file mode 100755 index 4f9c339..0000000 --- a/build/lib/ndg/httpsclient/test/scripts/openssl_https_server.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -openssl s_server -www -cert pki/localhost.crt -key pki/localhost.key -accept 4443 diff --git a/build/lib/ndg/httpsclient/test/test_https.py b/build/lib/ndg/httpsclient/test/test_https.py deleted file mode 100644 index 86e0c51..0000000 --- a/build/lib/ndg/httpsclient/test/test_https.py +++ /dev/null @@ -1,122 +0,0 @@ -"""unit tests module for ndg.httpsclient.https.HTTPSconnection class - -PyOpenSSL utility to make a httplib-like interface suitable for use with -urllib2 -""" -__author__ = "P J Kershaw (STFC)" -__date__ = "06/01/12" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' -import logging -logging.basicConfig(level=logging.DEBUG) -log = logging.getLogger(__name__) -import unittest -import socket - -from OpenSSL import SSL - -from ndg.httpsclient.test import Constants -from ndg.httpsclient.https import HTTPSConnection -from ndg.httpsclient.ssl_peer_verification import ServerSSLCertVerification - - -class TestHTTPSConnection(unittest.TestCase): - '''Test ndg HTTPS client HTTPSConnection class''' - - def test01_open(self): - conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT) - conn.connect() - conn.request('GET', '/') - resp = conn.getresponse() - print('Response = %s' % resp.read()) - conn.close() - - def test02_open_fails(self): - conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT2) - self.assertRaises(socket.error, conn.connect) - - def test03_ssl_verification_of_peer_fails(self): - ctx = SSL.Context(SSL.TLSv1_METHOD) - - def verify_callback(conn, x509, errnum, errdepth, preverify_ok): - log.debug('SSL peer certificate verification failed for %r', - x509.get_subject()) - return preverify_ok - - ctx.set_verify(SSL.VERIFY_PEER, verify_callback) - ctx.set_verify_depth(9) - - # Set bad location - unit test dir has no CA certs to verify with - ctx.load_verify_locations(None, Constants.UNITTEST_DIR) - - conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT, - ssl_context=ctx) - conn.connect() - self.assertRaises(SSL.Error, conn.request, 'GET', '/') - - def test03_ssl_verification_of_peer_succeeds(self): - ctx = SSL.Context(SSL.TLSv1_METHOD) - - verify_callback = lambda conn, x509, errnum, errdepth, preverify_ok: \ - preverify_ok - - ctx.set_verify(SSL.VERIFY_PEER, verify_callback) - ctx.set_verify_depth(9) - - # Set correct location for CA certs to verify with - ctx.load_verify_locations(None, Constants.CACERT_DIR) - - conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT, - ssl_context=ctx) - conn.connect() - conn.request('GET', '/') - resp = conn.getresponse() - print('Response = %s' % resp.read()) - - def test04_ssl_verification_with_subj_alt_name(self): - ctx = SSL.Context(SSL.TLSv1_METHOD) - - verification = ServerSSLCertVerification(hostname='localhost') - verify_callback = verification.get_verify_server_cert_func() - - ctx.set_verify(SSL.VERIFY_PEER, verify_callback) - ctx.set_verify_depth(9) - - # Set correct location for CA certs to verify with - ctx.load_verify_locations(None, Constants.CACERT_DIR) - - conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT, - ssl_context=ctx) - conn.connect() - conn.request('GET', '/') - resp = conn.getresponse() - print('Response = %s' % resp.read()) - - def test04_ssl_verification_with_subj_common_name(self): - ctx = SSL.Context(SSL.TLSv1_METHOD) - - # Explicitly set verification of peer hostname using peer certificate - # subject common name - verification = ServerSSLCertVerification(hostname='localhost', - subj_alt_name_match=False) - - verify_callback = verification.get_verify_server_cert_func() - - ctx.set_verify(SSL.VERIFY_PEER, verify_callback) - ctx.set_verify_depth(9) - - # Set correct location for CA certs to verify with - ctx.load_verify_locations(None, Constants.CACERT_DIR) - - conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT, - ssl_context=ctx) - conn.connect() - conn.request('GET', '/') - resp = conn.getresponse() - print('Response = %s' % resp.read()) - - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/build/lib/ndg/httpsclient/test/test_urllib2.py b/build/lib/ndg/httpsclient/test/test_urllib2.py deleted file mode 100644 index 366c2ca..0000000 --- a/build/lib/ndg/httpsclient/test/test_urllib2.py +++ /dev/null @@ -1,56 +0,0 @@ -"""unit tests module for ndg.httpsclient.urllib2_build_opener module - -PyOpenSSL utility to make a httplib-like interface suitable for use with -urllib2 -""" -__author__ = "P J Kershaw (STFC)" -__date__ = "06/01/12" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' -import sys - -if sys.version_info[0] > 2: - from urllib.error import URLError as URLError_ -else: - from urllib2 import URLError as URLError_ - -import unittest - -from OpenSSL import SSL -from ndg.httpsclient.test import Constants -from ndg.httpsclient.urllib2_build_opener import build_opener - - -class Urllib2TestCase(unittest.TestCase): - """Unit tests for urllib2 functionality""" - - def test01_urllib2_build_opener(self): - opener = build_opener() - self.assertTrue(opener) - - def test02_open(self): - opener = build_opener() - res = opener.open(Constants.TEST_URI) - self.assertTrue(res) - print("res = %s" % res.read()) - - def test03_open_fails_unknown_loc(self): - opener = build_opener() - self.assertRaises(URLError_, opener.open, Constants.TEST_URI2) - - def test04_open_peer_cert_verification_fails(self): - # Explicitly set empty CA directory to make verification fail - ctx = SSL.Context(SSL.TLSv1_METHOD) - verify_callback = lambda conn, x509, errnum, errdepth, preverify_ok: \ - preverify_ok - - ctx.set_verify(SSL.VERIFY_PEER, verify_callback) - ctx.load_verify_locations(None, './') - opener = build_opener(ssl_context=ctx) - self.assertRaises(SSL.Error, opener.open, Constants.TEST_URI) - - -if __name__ == "__main__": - unittest.main() diff --git a/build/lib/ndg/httpsclient/test/test_utils.py b/build/lib/ndg/httpsclient/test/test_utils.py deleted file mode 100644 index 44382d0..0000000 --- a/build/lib/ndg/httpsclient/test/test_utils.py +++ /dev/null @@ -1,62 +0,0 @@ -"""unit tests module for ndg.httpsclient.utils module - -PyOpenSSL utility to make a httplib-like interface suitable for use with -urllib2 -""" -__author__ = "P J Kershaw (STFC)" -__date__ = "06/01/12" -__copyright__ = "(C) 2012 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' -import unittest -import os - -from OpenSSL import SSL - -from ndg.httpsclient.test import Constants -from ndg.httpsclient.utils import (Configuration, fetch_from_url, open_url, - _should_use_proxy) - - -class TestUtilsModule(unittest.TestCase): - '''Test ndg.httpsclient.utils module''' - - def test01_configuration(self): - config = Configuration(SSL.Context(SSL.TLSv1_METHOD), True) - self.assertTrue(config.ssl_context) - self.assertEqual(config.debug, True) - - def test02_fetch_from_url(self): - config = Configuration(SSL.Context(SSL.TLSv1_METHOD), True) - res = fetch_from_url(Constants.TEST_URI, config) - self.assertTrue(res) - - def test03_open_url(self): - config = Configuration(SSL.Context(SSL.TLSv1_METHOD), True) - res = open_url(Constants.TEST_URI, config) - self.assertEqual(res[0], 200, - 'open_url for %r failed' % Constants.TEST_URI) - - def test04__should_use_proxy(self): - if 'no_proxy' in os.environ: - no_proxy = os.environ['no_proxy'] - del os.environ['no_proxy'] - else: - no_proxy = None - - self.assertTrue(_should_use_proxy(Constants.TEST_URI), - 'Expecting use proxy = True') - - os.environ['no_proxy'] = 'localhost,localhost.localdomain' - self.assertFalse(_should_use_proxy(Constants.TEST_URI), - 'Expecting use proxy = False') - - if no_proxy is not None: - os.environ['no_proxy'] = no_proxy - else: - del os.environ['no_proxy'] - - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/build/lib/ndg/httpsclient/urllib2_build_opener.py b/build/lib/ndg/httpsclient/urllib2_build_opener.py deleted file mode 100644 index 55d8632..0000000 --- a/build/lib/ndg/httpsclient/urllib2_build_opener.py +++ /dev/null @@ -1,78 +0,0 @@ -"""urllib2 style build opener integrates with HTTPSConnection class from this -package. -""" -__author__ = "P J Kershaw" -__date__ = "21/12/10" -__copyright__ = "(C) 2011 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' -import logging -import sys - -# Py 2 <=> 3 compatibility for class type checking -if sys.version_info[0] > 2: - class_type_ = type - from urllib.request import (ProxyHandler, UnknownHandler, - HTTPDefaultErrorHandler, FTPHandler, - FileHandler, HTTPErrorProcessor, - HTTPHandler, OpenerDirector, - HTTPRedirectHandler) -else: - import types - class_type_ = types.ClassType - - from urllib2 import (ProxyHandler, UnknownHandler, HTTPDefaultErrorHandler, - FTPHandler, FileHandler, HTTPErrorProcessor, - HTTPHandler, OpenerDirector, HTTPRedirectHandler) - -from ndg.httpsclient.https import HTTPSContextHandler - -log = logging.getLogger(__name__) - - -# Copied from urllib2 with modifications for ssl -def build_opener(*handlers, **kw): - """Create an opener object from a list of handlers. - - The opener will use several default handlers, including support - for HTTP and FTP. - - If any of the handlers passed as arguments are subclasses of the - default handlers, the default handlers will not be used. - """ - def isclass(obj): - return isinstance(obj, class_type_) or hasattr(obj, "__bases__") - - opener = OpenerDirector() - default_classes = [ProxyHandler, UnknownHandler, HTTPHandler, - HTTPDefaultErrorHandler, HTTPRedirectHandler, - FTPHandler, FileHandler, HTTPErrorProcessor] - check_classes = list(default_classes) - check_classes.append(HTTPSContextHandler) - skip = [] - for klass in check_classes: - for check in handlers: - if isclass(check): - if issubclass(check, klass): - skip.append(klass) - elif isinstance(check, klass): - skip.append(klass) - - for klass in default_classes: - if klass not in skip: - opener.add_handler(klass()) - - # Pick up SSL context from keyword settings - ssl_context = kw.get('ssl_context') - - # Add the HTTPS handler with ssl_context - if HTTPSContextHandler not in skip: - opener.add_handler(HTTPSContextHandler(ssl_context)) - - for h in handlers: - if isclass(h): - h = h() - opener.add_handler(h) - - return opener diff --git a/build/lib/ndg/httpsclient/utils.py b/build/lib/ndg/httpsclient/utils.py deleted file mode 100644 index 7e43a73..0000000 --- a/build/lib/ndg/httpsclient/utils.py +++ /dev/null @@ -1,411 +0,0 @@ -"""Utilities using NDG HTTPS Client, including a main module that can be used to -fetch from a URL. -""" -__author__ = "R B Wilkinson" -__date__ = "09/12/11" -__copyright__ = "(C) 2011 Science and Technology Facilities Council" -__license__ = "BSD - see LICENSE file in top-level directory" -__contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' - -import logging -from optparse import OptionParser -import os -import sys - -if sys.version_info[0] > 2: - import http.cookiejar as cookiejar_ - import http.client as http_client_ - from urllib.request import Request as Request_ - from urllib.request import HTTPHandler as HTTPHandler_ - from urllib.request import HTTPCookieProcessor as HTTPCookieProcessor_ - from urllib.request import HTTPBasicAuthHandler as HTTPBasicAuthHandler_ - from urllib.request import HTTPPasswordMgrWithDefaultRealm as \ - HTTPPasswordMgrWithDefaultRealm_ - from urllib.request import ProxyHandler as ProxyHandler_ - from urllib.error import HTTPError as HTTPError_ - import urllib.parse as urlparse_ -else: - import cookielib as cookiejar_ - import httplib as http_client_ - from urllib2 import Request as Request_ - from urllib2 import HTTPHandler as HTTPHandler_ - from urllib2 import HTTPCookieProcessor as HTTPCookieProcessor_ - from urllib2 import HTTPBasicAuthHandler as HTTPBasicAuthHandler_ - from urllib2 import HTTPPasswordMgrWithDefaultRealm as \ - HTTPPasswordMgrWithDefaultRealm_ - from urllib2 import ProxyHandler as ProxyHandler_ - from urllib2 import HTTPError as HTTPError_ - import urlparse as urlparse_ - -from ndg.httpsclient.urllib2_build_opener import build_opener -from ndg.httpsclient.https import HTTPSContextHandler -from ndg.httpsclient import ssl_context_util - -log = logging.getLogger(__name__) - -class AccumulatingHTTPCookieProcessor(HTTPCookieProcessor_): - """Cookie processor that adds new cookies (instead of replacing the existing - ones as HTTPCookieProcessor does) - """ - def http_request(self, request): - """Processes cookies for a HTTP request. - @param request: request to process - @type request: urllib2.Request - @return: request - @rtype: urllib2.Request - """ - COOKIE_HEADER_NAME = "Cookie" - tmp_request = Request_(request.get_full_url(), request.data, {}, - request.origin_req_host, - request.unverifiable) - self.cookiejar.add_cookie_header(tmp_request) - # Combine existing and new cookies. - new_cookies = tmp_request.get_header(COOKIE_HEADER_NAME) - if new_cookies: - if request.has_header(COOKIE_HEADER_NAME): - # Merge new cookies with existing ones. - old_cookies = request.get_header(COOKIE_HEADER_NAME) - merged_cookies = '; '.join([old_cookies, new_cookies]) - request.add_unredirected_header(COOKIE_HEADER_NAME, - merged_cookies) - else: - # No existing cookies so just set new ones. - request.add_unredirected_header(COOKIE_HEADER_NAME, new_cookies) - return request - - # Process cookies for HTTPS in the same way. - https_request = http_request - - -class URLFetchError(Exception): - """Error fetching content from URL""" - - -def fetch_from_url(url, config, data=None, handlers=None): - """Returns data retrieved from a URL. - @param url: URL to attempt to open - @type url: basestring - @param config: SSL context configuration - @type config: Configuration - @return data retrieved from URL or None - """ - return_code, return_message, response = open_url(url, config, data=data, - handlers=handlers) - if return_code and return_code == http_client_.OK: - return_data = response.read() - response.close() - return return_data - else: - raise URLFetchError(return_message) - -def fetch_from_url_to_file(url, config, output_file, data=None, handlers=None): - """Writes data retrieved from a URL to a file. - @param url: URL to attempt to open - @type url: basestring - @param config: SSL context configuration - @type config: Configuration - @param output_file: output file - @type output_file: basestring - @return: tuple ( - returned HTTP status code or 0 if an error occurred - returned message - boolean indicating whether access was successful) - """ - return_code, return_message, response = open_url(url, config, data=data, - handlers=handlers) - if return_code == http_client_.OK: - return_data = response.read() - response.close() - outfile = open(output_file, "w") - outfile.write(return_data) - outfile.close() - - return return_code, return_message, return_code == http_client_.OK - - -def fetch_stream_from_url(url, config, data=None, handlers=None): - """Returns data retrieved from a URL. - @param url: URL to attempt to open - @type url: basestring - @param config: SSL context configuration - @type config: Configuration - @param data: HTTP POST data - @type data: str - @param handlers: list of custom urllib2 handlers to add to the request - @type handlers: iterable - @return: data retrieved from URL or None - @rtype: file derived type - """ - return_code, return_message, response = open_url(url, config, data=data, - handlers=handlers) - if return_code and return_code == http_client_.OK: - return response - else: - raise URLFetchError(return_message) - - -def open_url(url, config, data=None, handlers=None): - """Attempts to open a connection to a specified URL. - @param url: URL to attempt to open - @param config: SSL context configuration - @type config: Configuration - @param data: HTTP POST data - @type data: str - @param handlers: list of custom urllib2 handlers to add to the request - @type handlers: iterable - @return: tuple ( - returned HTTP status code or 0 if an error occurred - returned message or error description - response object) - """ - debuglevel = 1 if config.debug else 0 - - # Set up handlers for URL opener. - if config.cookie: - cj = config.cookie - else: - cj = cookiejar_.CookieJar() - - # Use a cookie processor that accumulates cookies when redirects occur so - # that an application can redirect for authentication and retain both any - # cookies for the application and the security system (c.f., - # urllib2.HTTPCookieProcessor which replaces cookies). - cookie_handler = AccumulatingHTTPCookieProcessor(cj) - - if not handlers: - handlers = [] - - handlers.append(cookie_handler) - - if config.debug: - http_handler = HTTPHandler_(debuglevel=debuglevel) - https_handler = HTTPSContextHandler(config.ssl_context, - debuglevel=debuglevel) - handlers.extend([http_handler, https_handler]) - - if config.http_basicauth: - # currently only supports http basic auth - auth_handler = HTTPBasicAuthHandler_(HTTPPasswordMgrWithDefaultRealm_()) - auth_handler.add_password(realm=None, uri=url, - user=config.http_basicauth[0], - passwd=config.http_basicauth[1]) - handlers.append(auth_handler) - - - # Explicitly remove proxy handling if the host is one listed in the value of - # the no_proxy environment variable because urllib2 does use proxy settings - # set via http_proxy and https_proxy, but does not take the no_proxy value - # into account. - if not _should_use_proxy(url, config.no_proxy): - handlers.append(ProxyHandler_({})) - log.debug("Not using proxy") - elif config.proxies: - handlers.append(ProxyHandler_(config.proxies)) - log.debug("Configuring proxies: %s" % config.proxies) - - opener = build_opener(*handlers, ssl_context=config.ssl_context) - - headers = config.headers - if headers is None: - headers = {} - - request = Request_(url, data, headers) - - # Open the URL and check the response. - return_code = 0 - return_message = '' - response = None - - try: - response = opener.open(request) - return_message = response.msg - return_code = response.code - if log.isEnabledFor(logging.DEBUG): - for index, cookie in enumerate(cj): - log.debug("%s : %s", index, cookie) - - except HTTPError_ as exc: - return_code = exc.code - return_message = "Error: %s" % exc.msg - if log.isEnabledFor(logging.DEBUG): - log.debug("%s %s", exc.code, exc.msg) - - except Exception as exc: - return_message = "Error: %s" % exc.__str__() - if log.isEnabledFor(logging.DEBUG): - import traceback - log.debug(traceback.format_exc()) - - return (return_code, return_message, response) - - -def _should_use_proxy(url, no_proxy=None): - """Determines whether a proxy should be used to open a connection to the - specified URL, based on the value of the no_proxy environment variable. - @param url: URL - @type url: basestring or urllib2.Request - """ - if no_proxy is None: - no_proxy_effective = os.environ.get('no_proxy', '') - else: - no_proxy_effective = no_proxy - - urlObj = urlparse_.urlparse(_url_as_string(url)) - for np in [h.strip() for h in no_proxy_effective.split(',')]: - if urlObj.hostname == np: - return False - - return True - -def _url_as_string(url): - """Returns the URL string from a URL value that is either a string or - urllib2.Request.. - @param url: URL - @type url: basestring or urllib2.Request - @return: URL string - @rtype: basestring - """ - if isinstance(url, Request_): - return url.get_full_url() - elif isinstance(url, str): - return url - else: - raise TypeError("Expected type %r or %r" % - (str, Request_)) - - -class Configuration(object): - """Connection configuration. - """ - def __init__(self, ssl_context, debug=False, proxies=None, no_proxy=None, - cookie=None, http_basicauth=None, headers=None): - """ - @param ssl_context: SSL context to use with this configuration - @type ssl_context: OpenSSL.SSL.Context - @param debug: if True, output debugging information - @type debug: bool - @param proxies: proxies to use for - @type proxies: dict with basestring keys and values - @param no_proxy: hosts for which a proxy should not be used - @type no_proxy: basestring - @param cookie: cookies to set for request - @type cookie: cookielib.CookieJar (python 3 - http.cookiejar) - @param http_basicauth: http authentication, or None - @type http_basicauth: tuple of (username,password) - @param headers: http headers - @type headers: dict - """ - self.ssl_context = ssl_context - self.debug = debug - self.proxies = proxies - self.no_proxy = no_proxy - self.cookie = cookie - self.http_basicauth = http_basicauth - self.headers = headers - - -def main(): - '''Utility to fetch data using HTTP or HTTPS GET from a specified URL. - ''' - parser = OptionParser(usage="%prog [options] url") - parser.add_option("-c", "--certificate", dest="cert_file", metavar="FILE", - default=os.path.expanduser("~/credentials.pem"), - help="Certificate file - defaults to $HOME/credentials.pem") - parser.add_option("-k", "--private-key", dest="key_file", metavar="FILE", - default=None, - help="Private key file - defaults to the certificate file") - parser.add_option("-t", "--ca-certificate-dir", dest="ca_dir", - metavar="PATH", - default=None, - help="Trusted CA certificate file directory") - parser.add_option("-d", "--debug", action="store_true", dest="debug", - default=False, - help="Print debug information.") - parser.add_option("-p", "--post-data-file", dest="data_file", - metavar="FILE", default=None, - help="POST data file") - parser.add_option("-f", "--fetch", dest="output_file", metavar="FILE", - default=None, help="Output file") - parser.add_option("-n", "--no-verify-peer", action="store_true", - dest="no_verify_peer", default=False, - help="Skip verification of peer certificate.") - parser.add_option("-a", "--basicauth", dest="basicauth", - metavar="USER:PASSWD", - default=None, - help="HTTP authentication credentials") - parser.add_option("--header", action="append", dest="headers", - metavar="HEADER: VALUE", - help="Add HTTP header to request") - (options, args) = parser.parse_args() - if len(args) != 1: - parser.error("Incorrect number of arguments") - - url = args[0] - - if options.debug: - logging.getLogger().setLevel(logging.DEBUG) - - if options.key_file and os.path.exists(options.key_file): - key_file = options.key_file - else: - key_file = None - - if options.cert_file and os.path.exists(options.cert_file): - cert_file = options.cert_file - else: - cert_file = None - - if options.ca_dir and os.path.exists(options.ca_dir): - ca_dir = options.ca_dir - else: - ca_dir = None - - verify_peer = not options.no_verify_peer - - if options.data_file and os.path.exists(options.data_file): - data_file = open(options.data_file) - data = data_file.read() - data_file.close() - else: - data = None - - if options.basicauth: - http_basicauth = options.basicauth.split(':', 1) - else: - http_basicauth = None - - headers = {} - if options.headers: - for h in options.headers: - key, val = h.split(':', 1) - headers[key.strip()] = val.lstrip() - - # If a private key file is not specified, the key is assumed to be stored in - # the certificate file. - ssl_context = ssl_context_util.make_ssl_context(key_file, - cert_file, - None, - ca_dir, - verify_peer, - url) - - config = Configuration(ssl_context, - options.debug, - http_basicauth=http_basicauth, - headers=headers) - if options.output_file: - return_code, return_message = fetch_from_url_to_file( - url, - config, - options.output_file, - data)[:2] - raise SystemExit(return_code, return_message) - else: - data = fetch_from_url(url, config) - print(data) - - -if __name__=='__main__': - logging.basicConfig() - main() From 64dc18def319aa939b7ffc2cc7630d6a5b9f1afc Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Wed, 18 Apr 2018 16:23:32 +0100 Subject: [PATCH 23/41] Fix to subject alt name class variable to make byte type for correct matching for Python 3. --- .pydevproject | 2 +- ndg/httpsclient/ssl_peer_verification.py | 2 +- ndg/httpsclient/subj_alt_name.py | 2 +- ndg/httpsclient/test/__init__.py | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.pydevproject b/.pydevproject index 7a5cfa1..bf5eee5 100644 --- a/.pydevproject +++ b/.pydevproject @@ -1,6 +1,6 @@ -ndg-httpsclient-py3 +ndg-httpsclient-py3.6 python 3.0 /ndg_httpsclient diff --git a/ndg/httpsclient/ssl_peer_verification.py b/ndg/httpsclient/ssl_peer_verification.py index 2de390b..b02b6f8 100644 --- a/ndg/httpsclient/ssl_peer_verification.py +++ b/ndg/httpsclient/ssl_peer_verification.py @@ -40,7 +40,7 @@ class ServerSSLCertVerification(object): 'domainComponent': 'DC', 'userid': 'UID' } - SUBJ_ALT_NAME_EXT_NAME = 'subjectAltName' + SUBJ_ALT_NAME_EXT_NAME = b'subjectAltName' PARSER_RE_STR = '/(%s)=' % '|'.join(list(DN_LUT.keys()) + \ list(DN_LUT.values())) PARSER_RE = re.compile(PARSER_RE_STR) diff --git a/ndg/httpsclient/subj_alt_name.py b/ndg/httpsclient/subj_alt_name.py index b2c1918..7cc3f47 100644 --- a/ndg/httpsclient/subj_alt_name.py +++ b/ndg/httpsclient/subj_alt_name.py @@ -25,7 +25,7 @@ class Pyasn1ImportError(ImportError): raise Pyasn1ImportError(import_error_msg) -MAX = 64 +MAX = 1024 class DirectoryString(univ.Choice): diff --git a/ndg/httpsclient/test/__init__.py b/ndg/httpsclient/test/__init__.py index 4e8196a..d554bb5 100644 --- a/ndg/httpsclient/test/__init__.py +++ b/ndg/httpsclient/test/__init__.py @@ -20,7 +20,9 @@ class Constants(object): HOSTNAME = 'localhost' TEST_URI = 'https://%s:%d' % (HOSTNAME, PORT) TEST_URI2 = 'https://%s:%d' % (HOSTNAME, PORT2) - +# TEST_URI = 'https://pypi.org' +# TEST_URI2 = 'https://www.google.co.uk' + UNITTEST_DIR = os.path.dirname(os.path.abspath(__file__)) CACERT_DIR = os.path.join(UNITTEST_DIR, 'pki', 'ca') SSL_CERT_FILENAME = 'localhost.crt' From e1157d8215f1044b0bc04ce7650ef608117107fd Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Fri, 20 Apr 2018 22:58:12 +0100 Subject: [PATCH 24/41] Updated tests to explicitly include subject alt names test. Tested with remote peer with > 64 subject alt names --- ndg/httpsclient/https.py | 6 ++++-- ndg/httpsclient/test/__init__.py | 6 +++--- ndg/httpsclient/test/test_urllib2.py | 27 +++++++++++++++++++++++++-- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/ndg/httpsclient/https.py b/ndg/httpsclient/https.py index 41ad1c6..679e230 100644 --- a/ndg/httpsclient/https.py +++ b/ndg/httpsclient/https.py @@ -48,7 +48,7 @@ class HTTPSConnection(HTTPConnection): @type default_ssl_method: int """ default_port = HTTPS_PORT - default_ssl_method = SSL.SSLv23_METHOD + default_ssl_method = SSL.TLSv1_2_METHOD def __init__(self, host, port=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, ssl_context=None): @@ -100,6 +100,8 @@ class HTTPSContextHandler(AbstractHTTPHandler): ''' https_request = AbstractHTTPHandler.do_request_ + SSL_METHOD = SSL.TLSv1_2_METHOD + def __init__(self, ssl_context, debuglevel=0): """ @param ssl_context:SSL context @@ -116,7 +118,7 @@ def __init__(self, ssl_context, debuglevel=0): ssl_context) self.ssl_context = ssl_context else: - self.ssl_context = SSL.Context(SSL.TLSv1_METHOD) + self.ssl_context = SSL.Context(self.__class__.SSL_METHOD) def https_open(self, req): """Opens HTTPS request diff --git a/ndg/httpsclient/test/__init__.py b/ndg/httpsclient/test/__init__.py index d554bb5..b5df50a 100644 --- a/ndg/httpsclient/test/__init__.py +++ b/ndg/httpsclient/test/__init__.py @@ -16,12 +16,12 @@ class Constants(object): '''Convenience base class from which other unit tests can extend. Its sets the generic data directory path''' PORT = 4443 +# PORT = 443 PORT2 = 4444 - HOSTNAME = 'localhost' + HOSTNAME = 'localhost' +# HOSTNAME = 'files.pythonhosted.org' TEST_URI = 'https://%s:%d' % (HOSTNAME, PORT) TEST_URI2 = 'https://%s:%d' % (HOSTNAME, PORT2) -# TEST_URI = 'https://pypi.org' -# TEST_URI2 = 'https://www.google.co.uk' UNITTEST_DIR = os.path.dirname(os.path.abspath(__file__)) CACERT_DIR = os.path.join(UNITTEST_DIR, 'pki', 'ca') diff --git a/ndg/httpsclient/test/test_urllib2.py b/ndg/httpsclient/test/test_urllib2.py index 366c2ca..b961bd5 100644 --- a/ndg/httpsclient/test/test_urllib2.py +++ b/ndg/httpsclient/test/test_urllib2.py @@ -21,6 +21,7 @@ from OpenSSL import SSL from ndg.httpsclient.test import Constants from ndg.httpsclient.urllib2_build_opener import build_opener +from ndg.httpsclient.ssl_peer_verification import ServerSSLCertVerification class Urllib2TestCase(unittest.TestCase): @@ -36,13 +37,14 @@ def test02_open(self): self.assertTrue(res) print("res = %s" % res.read()) + @unittest.skipIf(Constants.HOSTNAME != 'localhost', 'Skip non-local host') def test03_open_fails_unknown_loc(self): opener = build_opener() self.assertRaises(URLError_, opener.open, Constants.TEST_URI2) def test04_open_peer_cert_verification_fails(self): # Explicitly set empty CA directory to make verification fail - ctx = SSL.Context(SSL.TLSv1_METHOD) + ctx = SSL.Context(SSL.TLSv1_2_METHOD) verify_callback = lambda conn, x509, errnum, errdepth, preverify_ok: \ preverify_ok @@ -50,7 +52,28 @@ def test04_open_peer_cert_verification_fails(self): ctx.load_verify_locations(None, './') opener = build_opener(ssl_context=ctx) self.assertRaises(SSL.Error, opener.open, Constants.TEST_URI) - + + def test05_open_with_subj_alt_names_verification(self): + ctx = SSL.Context(SSL.TLSv1_2_METHOD) + + # Set wildcard hostname for subject alternative name matching - + # setting a minimum of two name components for hostname + split_hostname = Constants.HOSTNAME.split('.', 1) + if len(split_hostname) > 1: + _hostname = '*.' + split_hostname[-1] + else: + _hostname = Constants.HOSTNAME + + server_ssl_verify = ServerSSLCertVerification(hostname=_hostname) + verify_callback_ = server_ssl_verify.get_verify_server_cert_func() + ctx.set_verify(SSL.VERIFY_PEER, verify_callback_) + ctx.set_default_verify_paths() + + opener = build_opener(ssl_context=ctx) + res = opener.open(Constants.TEST_URI) + self.assertTrue(res) + print("res = %s" % res.read()) + if __name__ == "__main__": unittest.main() From 168ccb5bd39c499f4d231f7773160242d15c8061 Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Tue, 24 Apr 2018 09:37:25 +0100 Subject: [PATCH 25/41] Preparing 0.5.0 release - fix for certs with more than 64 subject alt names --- .pydevproject | 2 +- ndg/httpsclient/ssl_context_util.py | 4 +++- ndg/httpsclient/subj_alt_name.py | 1 + ndg/httpsclient/test/__init__.py | 4 ++-- ndg/httpsclient/test/test_https.py | 8 ++++---- ndg/httpsclient/test/test_urllib2.py | 20 +++++++++++++++----- ndg/httpsclient/test/test_utils.py | 6 +++--- setup.py | 9 +++++++++ 8 files changed, 38 insertions(+), 16 deletions(-) diff --git a/.pydevproject b/.pydevproject index bf5eee5..c7d9361 100644 --- a/.pydevproject +++ b/.pydevproject @@ -1,7 +1,7 @@ ndg-httpsclient-py3.6 -python 3.0 +python 3.6 /ndg_httpsclient diff --git a/ndg/httpsclient/ssl_context_util.py b/ndg/httpsclient/ssl_context_util.py index 0ed1d32..810e3c8 100644 --- a/ndg/httpsclient/ssl_context_util.py +++ b/ndg/httpsclient/ssl_context_util.py @@ -41,7 +41,7 @@ def make_ssl_context_from_config(ssl_config=False, url=None): def make_ssl_context(key_file=None, cert_file=None, pem_file=None, ca_dir=None, - verify_peer=False, url=None, method=SSL.TLSv1_METHOD, + verify_peer=False, url=None, method=SSL.TLSv1_2_METHOD, key_file_passphrase=None): """ Creates SSL context containing certificate and key file locations. @@ -64,6 +64,8 @@ def make_ssl_context(key_file=None, cert_file=None, pem_file=None, ca_dir=None, if pem_file or ca_dir: ssl_context.load_verify_locations(pem_file, ca_dir) + else: + ssl_context.set_default_verify_paths() # Use OS CA bundle def _callback(conn, x509, errnum, errdepth, preverify_ok): """Default certification verification callback. diff --git a/ndg/httpsclient/subj_alt_name.py b/ndg/httpsclient/subj_alt_name.py index 7cc3f47..d30fbdd 100644 --- a/ndg/httpsclient/subj_alt_name.py +++ b/ndg/httpsclient/subj_alt_name.py @@ -20,6 +20,7 @@ 'is: %s' % e) import warnings warnings.warn(import_error_msg) + barp class Pyasn1ImportError(ImportError): "Raise for pyasn1 import error" raise Pyasn1ImportError(import_error_msg) diff --git a/ndg/httpsclient/test/__init__.py b/ndg/httpsclient/test/__init__.py index b5df50a..6d4ee88 100644 --- a/ndg/httpsclient/test/__init__.py +++ b/ndg/httpsclient/test/__init__.py @@ -18,8 +18,8 @@ class Constants(object): PORT = 4443 # PORT = 443 PORT2 = 4444 - HOSTNAME = 'localhost' -# HOSTNAME = 'files.pythonhosted.org' + HOSTNAME = 'localhost' +# HOSTNAME = 'files.pythonhosted.org' TEST_URI = 'https://%s:%d' % (HOSTNAME, PORT) TEST_URI2 = 'https://%s:%d' % (HOSTNAME, PORT2) diff --git a/ndg/httpsclient/test/test_https.py b/ndg/httpsclient/test/test_https.py index 86e0c51..415a6bc 100644 --- a/ndg/httpsclient/test/test_https.py +++ b/ndg/httpsclient/test/test_https.py @@ -38,7 +38,7 @@ def test02_open_fails(self): self.assertRaises(socket.error, conn.connect) def test03_ssl_verification_of_peer_fails(self): - ctx = SSL.Context(SSL.TLSv1_METHOD) + ctx = SSL.Context(SSL.TLSv1_2_METHOD) def verify_callback(conn, x509, errnum, errdepth, preverify_ok): log.debug('SSL peer certificate verification failed for %r', @@ -57,7 +57,7 @@ def verify_callback(conn, x509, errnum, errdepth, preverify_ok): self.assertRaises(SSL.Error, conn.request, 'GET', '/') def test03_ssl_verification_of_peer_succeeds(self): - ctx = SSL.Context(SSL.TLSv1_METHOD) + ctx = SSL.Context(SSL.TLSv1_2_METHOD) verify_callback = lambda conn, x509, errnum, errdepth, preverify_ok: \ preverify_ok @@ -76,7 +76,7 @@ def test03_ssl_verification_of_peer_succeeds(self): print('Response = %s' % resp.read()) def test04_ssl_verification_with_subj_alt_name(self): - ctx = SSL.Context(SSL.TLSv1_METHOD) + ctx = SSL.Context(SSL.TLSv1_2_METHOD) verification = ServerSSLCertVerification(hostname='localhost') verify_callback = verification.get_verify_server_cert_func() @@ -95,7 +95,7 @@ def test04_ssl_verification_with_subj_alt_name(self): print('Response = %s' % resp.read()) def test04_ssl_verification_with_subj_common_name(self): - ctx = SSL.Context(SSL.TLSv1_METHOD) + ctx = SSL.Context(SSL.TLSv1_2_METHOD) # Explicitly set verification of peer hostname using peer certificate # subject common name diff --git a/ndg/httpsclient/test/test_urllib2.py b/ndg/httpsclient/test/test_urllib2.py index b961bd5..5262fe1 100644 --- a/ndg/httpsclient/test/test_urllib2.py +++ b/ndg/httpsclient/test/test_urllib2.py @@ -12,9 +12,9 @@ import sys if sys.version_info[0] > 2: - from urllib.error import URLError as URLError_ + from urllib.error import URLError as URLError else: - from urllib2 import URLError as URLError_ + from urllib2 import URLError as URLError import unittest @@ -37,10 +37,11 @@ def test02_open(self): self.assertTrue(res) print("res = %s" % res.read()) + # Skip this test for remote service as it can take a long time to timeout @unittest.skipIf(Constants.HOSTNAME != 'localhost', 'Skip non-local host') def test03_open_fails_unknown_loc(self): opener = build_opener() - self.assertRaises(URLError_, opener.open, Constants.TEST_URI2) + self.assertRaises(URLError, opener.open, Constants.TEST_URI2) def test04_open_peer_cert_verification_fails(self): # Explicitly set empty CA directory to make verification fail @@ -67,8 +68,17 @@ def test05_open_with_subj_alt_names_verification(self): server_ssl_verify = ServerSSLCertVerification(hostname=_hostname) verify_callback_ = server_ssl_verify.get_verify_server_cert_func() ctx.set_verify(SSL.VERIFY_PEER, verify_callback_) - ctx.set_default_verify_paths() - + + # Set default verify paths if testing with peer that has corresponding + # CA cert in bundle provided with the OS. In this case, load verify + # locations is not needed. + #ctx.set_default_verify_paths() + + ctx.set_verify_depth(9) + + # Set correct location for CA certs to verify with + ctx.load_verify_locations(None, Constants.CACERT_DIR) + opener = build_opener(ssl_context=ctx) res = opener.open(Constants.TEST_URI) self.assertTrue(res) diff --git a/ndg/httpsclient/test/test_utils.py b/ndg/httpsclient/test/test_utils.py index 44382d0..a91045d 100644 --- a/ndg/httpsclient/test/test_utils.py +++ b/ndg/httpsclient/test/test_utils.py @@ -23,17 +23,17 @@ class TestUtilsModule(unittest.TestCase): '''Test ndg.httpsclient.utils module''' def test01_configuration(self): - config = Configuration(SSL.Context(SSL.TLSv1_METHOD), True) + config = Configuration(SSL.Context(SSL.TLSv1_2_METHOD), True) self.assertTrue(config.ssl_context) self.assertEqual(config.debug, True) def test02_fetch_from_url(self): - config = Configuration(SSL.Context(SSL.TLSv1_METHOD), True) + config = Configuration(SSL.Context(SSL.TLSv1_2_METHOD), True) res = fetch_from_url(Constants.TEST_URI, config) self.assertTrue(res) def test03_open_url(self): - config = Configuration(SSL.Context(SSL.TLSv1_METHOD), True) + config = Configuration(SSL.Context(SSL.TLSv1_2_METHOD), True) res = open_url(Constants.TEST_URI, config) self.assertEqual(res[0], 200, 'open_url for %r failed' % Constants.TEST_URI) diff --git a/setup.py b/setup.py index e9dbb0b..bf1aaca 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,15 @@ Releases ======== +0.5.0 +----- + * Fix to Subject Alternative Name verification to handle certificates with up + to 1024 names (previously it was 64). Tested with + https://files.pythonhosted.org. Thanks to Matt Pegler for flagging up + * Fix defaults in SSL context objects and in tests to use TLS 1.2 + * Use byte type for ``subjectAltName`` field matching + * Added additional Subject Alternative Name test - in ``test_urlib2`` + 0.4.4 ----- * Update test certificate files. From 824c0d13990a80759ae77262d1c6f536c60bfad2 Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Tue, 24 Apr 2018 09:43:59 +0100 Subject: [PATCH 26/41] Fix test with subject alt name --- ndg/httpsclient/subj_alt_name.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ndg/httpsclient/subj_alt_name.py b/ndg/httpsclient/subj_alt_name.py index d30fbdd..7cc3f47 100644 --- a/ndg/httpsclient/subj_alt_name.py +++ b/ndg/httpsclient/subj_alt_name.py @@ -20,7 +20,6 @@ 'is: %s' % e) import warnings warnings.warn(import_error_msg) - barp class Pyasn1ImportError(ImportError): "Raise for pyasn1 import error" raise Pyasn1ImportError(import_error_msg) From 836d2dda0683820b00f87f1e56b3bb21b0952fc2 Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Tue, 24 Apr 2018 09:52:08 +0100 Subject: [PATCH 27/41] Write up version 0.5.0 changes --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index f7e7093..cd7de15 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,13 @@ of the SSL peer using ``pyasn1``. Releases ======== +0.5.0 +----- + * Fix to Subject Alternative Name handling to allow for certificates with + more than 64 names (max now 1024). Thanks to Matt Pegler + * Fix to subjectAltName string to use byte type for correct matching + * Updated SSL Context objects to default to TLS 1.2 + 0.4.4 ----- * Updated test certificates From 94f86d31ff7afd2507d35ad747e0a74894d0a42f Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Tue, 24 Apr 2018 09:58:35 +0100 Subject: [PATCH 28/41] Set 0.5.0 release --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bf1aaca..8e29cb8 100644 --- a/setup.py +++ b/setup.py @@ -142,7 +142,7 @@ setup( name='ndg_httpsclient', - version="0.4.4", + version="0.5.0", description='Provides enhanced HTTPS support for httplib and urllib2 using ' 'PyOpenSSL', author='Richard Wilkinson and Philip Kershaw', From 66ace3c11cf1b65e0627dadf28de64c57ad206a4 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Jun 2018 14:46:41 +0300 Subject: [PATCH 29/41] Allow nightly build to fail --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index bcc7fbb..c385e2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,3 +9,6 @@ python: - nightly script: - python setup.py install +matrix: + allow_failures: + - python: nightly From b3725ba12f03dffce775b578e6ff3c1dbaa4b2b4 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Jun 2018 14:47:55 +0300 Subject: [PATCH 30/41] Add version classifiers --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.py b/setup.py index 8e29cb8..7995205 100644 --- a/setup.py +++ b/setup.py @@ -174,7 +174,14 @@ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Security', 'Topic :: Internet', 'Topic :: Scientific/Engineering', From 96eaf64af6e4dc897da936dfaec4d7c4c2a57b48 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Jun 2018 14:48:12 +0300 Subject: [PATCH 31/41] Add python_requires to help pip --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7995205..14d5722 100644 --- a/setup.py +++ b/setup.py @@ -161,6 +161,7 @@ ], }, install_requires=['PyOpenSSL', 'pyasn1>=0.1.1'], + python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', From 719afe3ca16dc43a1a950a51364d5787969b5f85 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Jun 2018 14:51:15 +0300 Subject: [PATCH 32/41] Drop support for EOL Python 3.3 --- .travis.yml | 1 - setup.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c385e2a..d1c2853 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python python: - 2.6 - 2.7 - - 3.3 - 3.4 - 3.5 - 3.6 diff --git a/setup.py b/setup.py index 14d5722..2b75930 100644 --- a/setup.py +++ b/setup.py @@ -161,7 +161,7 @@ ], }, install_requires=['PyOpenSSL', 'pyasn1>=0.1.1'], - python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*', + python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -179,7 +179,6 @@ 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', From 8e7ddc0dc330dc5096c104401131f14609f55e24 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Jun 2018 14:57:24 +0300 Subject: [PATCH 33/41] Drop support for EOL Python 2.6 --- .travis.yml | 1 - README.md | 4 ++-- ndg/httpsclient/https.py | 7 +++---- setup.py | 7 +++---- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index d1c2853..6d7ba85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python python: - - 2.6 - 2.7 - 3.4 - 3.5 diff --git a/README.md b/README.md index cd7de15..402aac4 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,9 @@ Initial release Prerequisites ============= -This has been developed and tested for Python 2.6 and 2.7 with pyOpenSSL 0.13 +This has been developed and tested for Python 2.7 with pyOpenSSL 0.13 and 0.14. Version 0.4.0 tested with ``pyOpenSSL`` 0.15.1 and Python 2.7 and -3.4. Note that proxy support is only available from Python 2.6.2 onwards. +3.4. ``pyasn1`` is required for correct SSL verification with ``subjectAltNames``. Installation diff --git a/ndg/httpsclient/https.py b/ndg/httpsclient/https.py index 679e230..068bbae 100644 --- a/ndg/httpsclient/https.py +++ b/ndg/httpsclient/https.py @@ -78,10 +78,9 @@ def connect(self): sock = socket.create_connection((self.host, self.port), self.timeout) - # Tunnel if using a proxy - ONLY available for Python 2.6.2 and above - if getattr(self, '_tunnel_host', None): - self.sock = sock - self._tunnel() + # Tunnel if using a proxy + self.sock = sock + self._tunnel() self.sock = SSLSocket(ssl_context, sock) diff --git a/setup.py b/setup.py index 2b75930..7b37e58 100644 --- a/setup.py +++ b/setup.py @@ -94,9 +94,9 @@ Prerequisites ============= -This has been developed and tested for Python 2.6 and 2.7 with pyOpenSSL 0.13 +This has been developed and tested for Python 2.7 with pyOpenSSL 0.13 and 0.14. Version 0.4.0 tested with ``pyOpenSSL`` 0.15.1 and Python 2.7 and -3.4. Note that proxy support is only available from Python 2.6.2 onwards. +3.4. ``pyasn1`` is required for correct SSL verification with ``subjectAltNames``. Installation @@ -161,7 +161,7 @@ ], }, install_requires=['PyOpenSSL', 'pyasn1>=0.1.1'], - python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -176,7 +176,6 @@ 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', From 566904ad38bf4bbe0e950395d0c9f0fc2509bd5f Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Jun 2018 15:06:54 +0300 Subject: [PATCH 34/41] Use README as long_description to avoid duplication --- setup.py | 135 ++----------------------------------------------------- 1 file changed, 3 insertions(+), 132 deletions(-) diff --git a/setup.py b/setup.py index 8e29cb8..6f5d389 100644 --- a/setup.py +++ b/setup.py @@ -7,138 +7,8 @@ NAMESPACE_PKGS = ['ndg'] -_long_description = ''' -A HTTPS client implementation for - * ``httplib`` (Python 2), ``http.client`` (Python 3) and - * ``urllib2`` (Python 2) and ``urllib`` (Python 3) - -... based on PyOpenSSL. PyOpenSSL provides a more fully featured SSL implementation -over the default provided with Python and importantly enables full verification -of the SSL peer using ``pyasn1``. - -Releases -======== -0.5.0 ------ - * Fix to Subject Alternative Name verification to handle certificates with up - to 1024 names (previously it was 64). Tested with - https://files.pythonhosted.org. Thanks to Matt Pegler for flagging up - * Fix defaults in SSL context objects and in tests to use TLS 1.2 - * Use byte type for ``subjectAltName`` field matching - * Added additional Subject Alternative Name test - in ``test_urlib2`` - -0.4.4 ------ - * Update test certificate files. - -0.4.3 ------ - * Fix to ``ndg`` namespace package warning issue (https://github.com/cedadev/ndg_httpsclient/issues/3). - ``__init__.py`` file now included in ``ndg`` directory so that there are no longer warnings with imports - when using Python 2.x. Thanks to Max Mauntner for fix. - * Minor fix for installation: set minimum release for ``pyasn1`` to avoid conflicts with Ubuntu - install - see https://github.com/cedadev/ndg_httpsclient/issues/5 and - https://github.com/cedadev/ndg_httpsclient/pull/10. ``pyasn1`` also becomes mandatory rather - than optional package for install. - It required by ``cryptography`` anyway which is a - dependency for ``pyOpenSSL`` from version 0.14. - -0.4.2 ------ - * Fix to bug in ``ndg.httpsclient.utils.open_url`` - duplicate open call. - Nb. This bug and the fix DO NOT affect the ``httplib``and ``urllib2`` - interfaces that this package provides. - -0.4.1 ------ - * Added explicit ref to Python 3 in classifier text for Python 3 checking tools. - * Moved LICENSE file into package - -0.4.0 ------ - * Made dual compatible with Python 2 / 3. - -0.3.3 ------ - * Fix to add in AnotherName for ``subjectAltNames`` field - added for support for CACert issued - certs (thanks to Gu1). - * Fix to HTTP Basic Auth option for ``ndg.httpsclient.utils.main`` - * Fix to ``ServerSSLCertVerification`` so that it can pass a function-based callback instead of using ``__call__``. In newer versions of OpenSSL (>= 0.14) the latter failed because of a request for ``__name__`` attribute. - -0.3.2 ------ - * Fix to SubjectAltNames support check - should only be enabled if pyasn1 is - installed. - * Fix to open_url: HTTP Request object was being created inside if headers is - None block - now corrected to create regardless. - * Added http basic auth support to script. (Thanks to Willem van Engen) - -0.3.1 ------ - * extended utils functions to support keyword for passing additional ``urllib2`` - handlers. - -0.3.0 ------ - * Added ``ndg.httpsclient.utils.fetch_stream_from_url`` function and added - parameter for data to post in ``open_url`` and ``fetch_*`` methods. - * fix to ndg.httpsclient.utils module _should_use_proxy and open_url functions - -0.2.0 ------ - * added support for SSL verification with subjectAltNames using pyasn1 - * fixed minor bug - SSL cert DN prefix matching - -0.1.0 ------ -Initial release - -Prerequisites -============= -This has been developed and tested for Python 2.6 and 2.7 with pyOpenSSL 0.13 -and 0.14. Version 0.4.0 tested with ``pyOpenSSL`` 0.15.1 and Python 2.7 and -3.4. Note that proxy support is only available from Python 2.6.2 onwards. -``pyasn1`` is required for correct SSL verification with ``subjectAltNames``. - -Installation -============ -Installation can be performed using easy_install or pip. - -Running ndg_httpclient -====================== -A simple script for fetching data using HTTP or HTTPS GET from a specified URL. - -Parameter: - -``url`` - The URL of the resource to be fetched - -Options: - -``-h, --help`` - Show help message and exit. - -``-c FILE, --certificate=FILE`` - Certificate file - defaults to ``$HOME/credentials.pem`` - -``-k FILE, --private-key=FILE`` - Private key file - defaults to the certificate file - -``-t DIR, --ca-certificate-dir=DIR`` - Trusted CA certificate file directory. - -``-d, --debug`` - Print debug information - this may be useful in solving problems with HTTP or - HTTPS access to a server. - -``-p FILE, --post-data-file=FILE`` - POST data file - -``-f FILE, --fetch=FILE`` - Output file - -``-n, --no-verify-peer`` - Skip verification of peer certificate. -''' +with open('README.md') as f: + _long_description = f.read() setup( name='ndg_httpsclient', @@ -149,6 +19,7 @@ author_email='Philip.Kershaw@stfc.ac.uk', url='https://github.com/cedadev/ndg_httpsclient/', long_description=_long_description, + long_description_content_type='text/markdown', license='BSD - See ndg/httpsclient/LICENCE file for details', packages=find_packages(), package_data={ From c69c06dbaff4a5008a40b5c5d72cd11531c8853c Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Fri, 20 Jul 2018 10:20:20 +0100 Subject: [PATCH 35/41] Prepare 0.5.1 release --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 402aac4..b36141e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ of the SSL peer using ``pyasn1``. Releases ======== +0.5.1 +----- + * Clean up handling for description file - pull in content from this file into setup() + * Allows the nightly build to fail + * Add Trove version classifiers to make it explicit what is supported + * Add python_requires to help pip + * Drop support for EOL Python 2.6 and 3.3 +Thanks to @hugovk + 0.5.0 ----- * Fix to Subject Alternative Name handling to allow for certificates with From 9cf2d21c721b7d8652e10d801ae83cf94a0fbdc5 Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Fri, 20 Jul 2018 10:22:53 +0100 Subject: [PATCH 36/41] Fix doc text --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b36141e..84d262d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Releases * Allows the nightly build to fail * Add Trove version classifiers to make it explicit what is supported * Add python_requires to help pip - * Drop support for EOL Python 2.6 and 3.3 + * Drop support for EOL Python 2.6 and 3.3 Thanks to @hugovk 0.5.0 From 6a787b054bf81fe8862be21d5329c3a0b00b59b5 Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Fri, 20 Jul 2018 13:13:16 +0100 Subject: [PATCH 37/41] Fix release num --- README.md | 5 +++-- setup.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 84d262d..7193452 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ Releases * Add Trove version classifiers to make it explicit what is supported * Add python_requires to help pip * Drop support for EOL Python 2.6 and 3.3 -Thanks to @hugovk + +Thanks to @hugovk for contributions 0.5.0 ----- @@ -98,7 +99,7 @@ and 0.14. Version 0.4.0 tested with ``pyOpenSSL`` 0.15.1 and Python 2.7 and Installation ============ -Installation can be performed using easy_install or pip. +Installation can be performed using ``easy_install`` or ``pip``. Running ndg_httpclient ====================== diff --git a/setup.py b/setup.py index 9e77d64..90ab546 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name='ndg_httpsclient', - version="0.5.0", + version="0.5.1", description='Provides enhanced HTTPS support for httplib and urllib2 using ' 'PyOpenSSL', author='Richard Wilkinson and Philip Kershaw', From af9fed2c22e283c745135a8d745e36b8540bb8cc Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Fri, 20 Jul 2018 13:32:55 +0100 Subject: [PATCH 38/41] Re-instated proxy test otherwise this breaks the code if no proxy is set. --- ndg/httpsclient/https.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ndg/httpsclient/https.py b/ndg/httpsclient/https.py index 068bbae..aaa0bb3 100644 --- a/ndg/httpsclient/https.py +++ b/ndg/httpsclient/https.py @@ -78,9 +78,10 @@ def connect(self): sock = socket.create_connection((self.host, self.port), self.timeout) - # Tunnel if using a proxy - self.sock = sock - self._tunnel() + # Tunnel if using a proxy - ONLY available for Python 2.6.2 and above + if getattr(self, '_tunnel_host', None): + self.sock = sock + self._tunnel() self.sock = SSLSocket(ssl_context, sock) From 370c1ec9713e68f8d9f2cd9984cb42a269cea384 Mon Sep 17 00:00:00 2001 From: Philip Kershaw Date: Thu, 27 Feb 2020 16:32:26 +0000 Subject: [PATCH 39/41] Updated test CA and issued certificates. Included key file for CA, so that others can update it if they wish or issue other test certificates. This is a TEST CA for testing ONLY. Use accordingly! --- .pydevproject | 16 ++++-- ndg/httpsclient/test/pki/ca/7e15277f.0 | 46 +++++++++------- ndg/httpsclient/test/pki/ca/ca.key | 52 ++++++++++++++++++ ndg/httpsclient/test/pki/gen-csr.sh | 3 ++ ndg/httpsclient/test/pki/localhost.crt | 45 ++++++++++------ ndg/httpsclient/test/pki/localhost.csr | 26 +++++++++ ndg/httpsclient/test/pki/localhost.key | 74 +++++++++++++++++--------- 7 files changed, 197 insertions(+), 65 deletions(-) create mode 100644 ndg/httpsclient/test/pki/ca/ca.key create mode 100755 ndg/httpsclient/test/pki/gen-csr.sh create mode 100644 ndg/httpsclient/test/pki/localhost.csr diff --git a/.pydevproject b/.pydevproject index c7d9361..85f561e 100644 --- a/.pydevproject +++ b/.pydevproject @@ -1,8 +1,14 @@ -ndg-httpsclient-py3.6 -python 3.6 - -/ndg_httpsclient - + + Default + + python 3.6 + + + + /ndg_httpsclient + + + diff --git a/ndg/httpsclient/test/pki/ca/7e15277f.0 b/ndg/httpsclient/test/pki/ca/7e15277f.0 index b9b6ac9..9a4ab56 100644 --- a/ndg/httpsclient/test/pki/ca/7e15277f.0 +++ b/ndg/httpsclient/test/pki/ca/7e15277f.0 @@ -1,20 +1,30 @@ -----BEGIN CERTIFICATE----- -MIIDLjCCAhagAwIBAgIBATANBgkqhkiG9w0BAQsFADA3MRQwEgYDVQQDDAtOREcg -VGVzdCBDQTEMMAoGA1UECgwDTkRHMREwDwYDVQQLDAhTZWN1cml0eTAeFw0xNjEy -MTUyMTMyNDNaFw0yMTEyMTQyMTMyNDNaMDcxFDASBgNVBAMMC05ERyBUZXN0IENB -MQwwCgYDVQQKDANOREcxETAPBgNVBAsMCFNlY3VyaXR5MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAv8L7md7A3NvKIYndyjHV7f1xVH6gJ8JPrecxAiVs -nidsYUm8saBV/dFf1jPMQ6hdjIuxLXlopLxThkHlCes/OTxLzBFLvFQQaxLExTsX -OmgeM4q20a2JLTqkmItPIYDVTYgi0EaF+I9Z1BwWGEfYr3uTizpr48rnzpXgRwrZ -rjRc53zLKpeqyBs7Qmg5Jlzmk/A+UWJ2ryCWLY9KFE3uPstybYpwq7YayD6upVwc -vedhh+zpXU3E9r/cnlZ4sKSP2M1QSOTb2l5XnlOU/MLODYW70uItNqs1j1XPuHYh -/ikO+jmjPUtL0IE47+LcaMU6BpaOCLzcjsSn/DDMYcDB7wIDAQABo0UwQzASBgNV -HRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU9S+bGqsW -uQzR/yHDdHoU6afNTE0wDQYJKoZIhvcNAQELBQADggEBAF2scAHd7xjgnJqAUX0b -7f7QKjpuYHrt48tH+pFOh5j3IgdOqTgRLc/N8lEqoH4M0lKZKbBerM9FiQ4eXCwy -3Rswn3wXPvi3HJGgvZYUt8J6KY5+syHU4iuzuSHOjznC0lLZkRz6kfZHX+paqro4 -1CsHhCt3ew17QMWEP6UtfZfBCg+kiEfBtSsTUIth0HgdH033PWh5v+nOzGN+3o2t -ORlzpttV+0RLlsw54l51I6rCEnfKOKtEy491JUs2whUwzp9v8tG4jev3PDVWhSSS -6jaSNvW4v7SDH0pnw52ZWzaRtA/pV/dCQOlqJ87wDNPdoVGpwqtYF/7Zdw2ty4gU -oSM= +MIIFLjCCAxagAwIBAgIBATANBgkqhkiG9w0BAQsFADA3MRQwEgYDVQQDDAtOREcg +VGVzdCBDQTEMMAoGA1UECgwDTkRHMREwDwYDVQQLDAhTZWN1cml0eTAeFw0yMDAy +MjcxMDM4MDhaFw0yNTAyMjUxMDM4MDhaMDcxFDASBgNVBAMMC05ERyBUZXN0IENB +MQwwCgYDVQQKDANOREcxETAPBgNVBAsMCFNlY3VyaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAoLeKc7StJLjyWuPBc+0pg8y5QJ4VQFzOc1jXbhqo +GugSvV/BpTqAPPCbd5hkET8nMIHkZ6EkXWNlxvYvrqKOKnrTvw3WUjEmsdoHkaXA +hq+v1IHbPbCQbB+RCCObuxlZ9Y72llUrODATb7dc9Odc4LCLPR/9+FjncB1d5b4x +Qt74VolID0dCd5dmMqy+lUYjsyLSycItmWcamdMEEPCop6AXlkAq3Q3eQDxaHtly +IOmyrYv2EWnFH5FRevhJdTnb48FdUTDya3T+BWpRqzF/Rh3NmI05JTbmSutCOs8m +SgSyi0JTNgaOaLlbtYVBLEkETUHPWABWwrXghIMIHaw3BZas2a9nlKE6zQSBxjsC +M/jJopgCDjLIxCw/SZ/Koovye/yvhMKYo71YnQCIv+1pIaL7GMrxMQZzrJiUVlpY +XtCyjx+5uiD4WuKCq6/aEKUd3o6ZdmNhkG8R0uBmivwxAmuMoMssCZC2GG7dBG+a +FlAG0OW+qzez4njjVwT7ePaO/ggRm6yJRNo7s3+SlEppV/orKLH4+O7FJm2HwTIB +Z3HSObrOJtjlcjM5pzhAFC4M0ul9vnCcnaFmhzB7bH7h8X7RaqECuHIhhfWTUiEP +f+riC5C962eGMqJH3zzPaXCKFVgyYXtphRZJYzf3oU4bGn69uumPl47McLd9lseR +uLkCAwEAAaNFMEMwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFIYdooV3qYYhM6aq0LTBYQWA9j1BMA0GCSqGSIb3DQEBCwUAA4IC +AQAzqykz00ztLxbAEVc8QbfxMYVa5QYp4Ljdl7Wb4IPb4WY6fDkkJ1wRNg/KWZov +CkTsiFvQSG5eHEVDLPUjW4WJ+eJI3uPeNjUTB+9i88pjbLqP1VBeiNdpTcOAu4w8 +cosK1wk6K3LNHATXZArD+8mHEFAHinuqlYwkBekIPBgMgbKr/WlaowV7fkIAUpxm +K2W7oLt3ZvWmFrsFcywLsyXmA8EC3d5svQ01YLHE7xJUcjatKSJyc+0dnMNy+ZZN +6NTPCefHwi5ShWWmkwtmvCChzePpYP/tBqkAIHLB7js2nHLZYtJYT1CqOGHdy3bL +DWHYU527Dj949K03/ZE8jkBqzQje5uAw9bqluydXXkAtMO8rMktIsEjZuMsyzD1b +Z8qdeHvwHQjnA1fvMuAO2QDTb8zGc8Mcn2/Jbj9ZD61cej75GWPIDolwSmMIA7fT +HyyD8NiOnqM1tGQZQWGTlwZidH6Z7jUASZd+J/+pVKyFmA6ecgo54/w/wTQSv/6S +ZWGp4ZYdjdpZ/coX2kbulej+Z3m0m4WB66LbvKD8zCYymRu5ULtS59QElliDKERS +U41awYtdgP/R/cz/SBoYxPvY/n1sOWT0mQp1+ctehDf+tDMirjOst71eq6n/85CR +NuP+k4yntJuzOjuxZ2MI8iuGDrqi/rFx2AhXXNCkCFDArw== -----END CERTIFICATE----- diff --git a/ndg/httpsclient/test/pki/ca/ca.key b/ndg/httpsclient/test/pki/ca/ca.key new file mode 100644 index 0000000..4e79919 --- /dev/null +++ b/ndg/httpsclient/test/pki/ca/ca.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCgt4pztK0kuPJa +48Fz7SmDzLlAnhVAXM5zWNduGqga6BK9X8GlOoA88Jt3mGQRPycwgeRnoSRdY2XG +9i+uoo4qetO/DdZSMSax2geRpcCGr6/Ugds9sJBsH5EII5u7GVn1jvaWVSs4MBNv +t1z051zgsIs9H/34WOdwHV3lvjFC3vhWiUgPR0J3l2YyrL6VRiOzItLJwi2ZZxqZ +0wQQ8KinoBeWQCrdDd5APFoe2XIg6bKti/YRacUfkVF6+El1OdvjwV1RMPJrdP4F +alGrMX9GHc2YjTklNuZK60I6zyZKBLKLQlM2Bo5ouVu1hUEsSQRNQc9YAFbCteCE +gwgdrDcFlqzZr2eUoTrNBIHGOwIz+MmimAIOMsjELD9Jn8qii/J7/K+EwpijvVid +AIi/7WkhovsYyvExBnOsmJRWWlhe0LKPH7m6IPha4oKrr9oQpR3ejpl2Y2GQbxHS +4GaK/DECa4ygyywJkLYYbt0Eb5oWUAbQ5b6rN7PieONXBPt49o7+CBGbrIlE2juz +f5KUSmlX+isosfj47sUmbYfBMgFncdI5us4m2OVyMzmnOEAULgzS6X2+cJydoWaH +MHtsfuHxftFqoQK4ciGF9ZNSIQ9/6uILkL3rZ4YyokffPM9pcIoVWDJhe2mFFklj +N/ehThsafr266Y+Xjsxwt32Wx5G4uQIDAQABAoICADoHa08xyl57BM66c4wW8qlr +Dux857FwkP5m8ooHx/DHIJUN8Q5mnAzk102MUX1ErzK9g6nDLi0mCYaYN0k32lsO +NExEg8DzjEQlNBxrm6NF5he1t2KD0PGpSCWHLeCVW7zlHLXVFKDaAvqQW1j1/LwI +Io40ce/sscvyXofob0L03Pfp44HYufASU00ywDGHvLYeebttiCdeaeVmoo43Rr4X +eMzAaHmeW0QYRnXjD2lA2En/Qh6xcQJf5uqeoW9ZL8MBe18obC9Oc7Wi2NAWNB0t +/6D4RMziEK62WRiA2RNatNjBXugf3+WCH9HfxlNmHc6DV4kvviPZpbJS2WhVWRvx +8fgodN3nT6Rbl+bgc1yy8BUHzoBecRELStpZFapsYnEhXzXJkmLtwxVF5gIvVwMR +fKJKTFqpVzfD+XFP2VonbDrhgIHSEK3TtKmks7tkB/swz3ftIQiTvGq3Oca+22so +8P4xVRsmzAoyumeJgK0pW12hg891HKQzrcITiO2XO5TM+3RszOJq7/KSi4d7ebS0 +Cd/+g0F3VrfI32VTQv2eS6YKHyHxzZIwq1OdRDQCl8ZCMUk72MpF80rjJfZCXgII +/Yq65ez60eW+JsF5Ua+jaSFfjAjQiFxLyUUZxY0yIybzNWsDeyy2+kqya+rvvc8c +3LQc0pkJiROaWXr6NOcBAoIBAQDPpv+RdON4DLA5DZjs9nDlh2Q9jxHoRPIcPVUJ +shQUILjCQxeYyeOww7g/p0BhA/3BHhJSxCKkvHbo3OD0S/G2TOmpHjifR9Lvv5vm +pN/t5EJYJHbXUIA7XKq9wHLvg8W1cA+RjPaGGlTIoCZNTXh11p741fUxdcNtOCPH +p+OBE1nVZFotpZllKafL6n4vRurwpt/Ip6pygCMKQ8fllkNmSX4UwXxNPBgJyO+J +mW1Vp2EMHqVOr5mZaj0hH6iddazsBIF2cFbz8bnP06kuhDqKzXQR6T2FelycFyfl +OH0vC1E6F/cr7YfDxcxSBrtHTnxmUk4vJc0nlT78FiLwH/dhAoIBAQDGIvw+t5ue +gG9DIEjd5eJyI9rCOdyMy67gavbkLywzzfAmIpD23zMoPp1yyhB1oXMGjWns5AJz +vZ+9I+aiLoxGHFumq5FnWgxq6GSOb+xgmL8SiwRSjjc850vGjkWRNpx7/hcD0T7m +qAAHxYOowtW4FRg4xmJM3YIK/NQ3gYz0Kqn61BnXwrbJSga91fTybtXqTIPCoK7G +cJQMIXijChl2HahtL2dn+e3VNktLmRqYcG1gGOcS0qZKgLikiZaXJobm87AHVB8J +gnpN7GjeQekilE6zYg1v9mJ4KGtn9jlc+z5in8M9gzxh1UMtJGmo7eLiKpxjnnI4 +MmOqfmfvB7hZAoIBACfLyY8G59FKHTvR70z6rghPOlbR5IyvKfdfibyh9FcnLkGM +uij+CDUITXdlhmERcsBgEwLps/n+ZIucSUCIjXpyoxVWRWPBUBvCjQ5W6tOcu1pJ +Ir2BIBJAYDideWmNx4ZOQ62RZm2s/jK0/qIyBrQOvcjSuHsSJrH34GOTOXazTrQm +12GIKy3IHfBmsGg/Hyef06OQ/omTJSQCeXz2n1QrIY64xinHKpQ1fjLQxLN1sJOy +C9xYqwdL5QtEFfyXYNyawHUCcqet2/6IghP4I4AWf/guyyuZl8YSagL0IImzkY2g +qi4xB6PtdIsD30B7Ojwh6fn/NKIxaDDHzUpX1gECggEBALrBzTsce52J99Wwv54Q +ikIF07PRMDax4jL7/50TbbmiumJnoHZh6elcI7JIDf2iYTZi4t38COepa8Cu7zwd +XfDT1/ugqdxd/2hhhi7QBmECeM90o21hSWWpqj4Majw22obw+CaL+5sT7Xtly/St +OR9bGsqhC5TiESpkJ2/uN7sJ39BiTb2Wdg4DLZmP62L737wJxQQVyseW46UPg5NB ++L/hspi/3tsds/SNSamHNY9MIXw73MpcLYv6CpBkc8Ii+XMFbP1raBnhl4CNfkdI +nwQOAKSKnpnTBfu2NY73ghhHZQU3C6OWaLradog9Uy58k6eUfHZVqdmYfWL95N9k +SnkCggEAQ9oqCgTs1H8pEVZPGGAGMjoFJT69HH+tKI3I/kLTEkJFP5zVz/jlFWqD +E5E935r9WexLSHazZFwb6CAaeJuzuYKfZmOgCKSPmHLkop1jR1yBqvzjXPPqJo9b +Si6igNS7ufEw1uM8ru+/wCYrUA20sVQSeECNL1B1re1Av8AtMvNCJxkclo5o6A5P +mFygoVlpPasKJr8dzMExtcLdtHl6XbpNS55kcO/JpnevhUTVpq3URvpsf21xmFuj +fy27gWvcokG9WLpuPyd90ShzocJyjLRXteFcjHkPtNwUJc3QGh8xlPoqs7MfSA76 +ep2Pg4lXDl4+iO5SDobGse4Ka/Pkkw== +-----END PRIVATE KEY----- diff --git a/ndg/httpsclient/test/pki/gen-csr.sh b/ndg/httpsclient/test/pki/gen-csr.sh new file mode 100755 index 0000000..c008668 --- /dev/null +++ b/ndg/httpsclient/test/pki/gen-csr.sh @@ -0,0 +1,3 @@ +#!/bin/bash +openssl genrsa -out localhost.key 4096 +openssl req -new -out localhost.csr -subj '/O=NDG/OU=Security/CN=localhost' -key localhost.key diff --git a/ndg/httpsclient/test/pki/localhost.crt b/ndg/httpsclient/test/pki/localhost.crt index 99711dc..74a393a 100644 --- a/ndg/httpsclient/test/pki/localhost.crt +++ b/ndg/httpsclient/test/pki/localhost.crt @@ -1,19 +1,30 @@ -----BEGIN CERTIFICATE----- -MIIDDTCCAfWgAwIBAgIBADANBgkqhkiG9w0BAQsFADA3MRQwEgYDVQQDDAtOREcg -VGVzdCBDQTEMMAoGA1UECgwDTkRHMREwDwYDVQQLDAhTZWN1cml0eTAeFw0xNjEy -MTYwMTA3MzJaFw0xOTEyMTYwMTA3MzJaMDUxDDAKBgNVBAoMA05ERzERMA8GA1UE -CwwIU2VjdXJpdHkxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAMOuihdNsoWgVg+DsRBc4kckQ5oiCVsx9NiRkUoCE3mu -dQb6pCRhoD3vjMMt18Ajr9xNMtqTtP3lqQ4iId4bd3WaNc2L4a9698QOnpY+Z70T -0pFckNvVcC+5kXHPCFsm/jIVsjJDdPQWM0fFqPGZTwQ8ssS5P9jdB2JHchHFOCWd -IYYO0jY1BEJ1AXeK4XzfU0/00s0LnleLrbEpNB9ckQaSbLU7I9o7fDr/5+WbV8tI -C5OG/ZZW7jlc4t4TLpzkUatBGW3c2PKJPgtWBAbtScxbtHB4mXV6iwmp3hlLT6tx -vKD+b8TFpxGPdoOfIHX6/hjMJa+kr4Z4MDHeNfHu6eUCAwEAAaMmMCQwDAYDVR0T -AQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEB -AHm7NNZvti4QfopARMIcuokWHac18Ka7HBRE04WxLw9vKuYYu1m7d/0sUhk3KuBB -ANOyzlt5y7ecEIAqxGe0m1yftD4Dgj33wB7kHvbIKUceKI563PM757RgDm6C70/p -3OwOCcx2FsSaKLFBdtahzv1cMriN7JpEoa1qT3MnmFyaK8hEaXxbRGwDDAXRgOix -ftOkNc5w0glQ7dl9CYq2QvsnMtG327pvyo6lBTKNjFzppZ/msOagPONuAcrtLAiU -wTNk5Aqlj6a+Njtq/LJFdeyYsVxwuqvVnEsU+9FJFL8PYrWpG8fA1dIXaPS+06Oi -vAMwOmVKhtgKvbaGcYa6efM= +MIIFDTCCAvWgAwIBAgIBADANBgkqhkiG9w0BAQsFADA3MRQwEgYDVQQDDAtOREcg +VGVzdCBDQTEMMAoGA1UECgwDTkRHMREwDwYDVQQLDAhTZWN1cml0eTAeFw0yMDAy +MjcxMDU2NTlaFw0yMzAyMjYxMDU2NTlaMDUxDDAKBgNVBAoMA05ERzERMA8GA1UE +CwwIU2VjdXJpdHkxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBALAc2LCkaet7eZdOPQgSRcz2e1mJQ3qwMqT1BdMn+AG/ +9ZMInCftXmJBHpb5WKeA4hclCP+sFUfaxt2TcJ7rNsPX6rkN5JFwMAIDMzjaq8Dr +1ZYjenTvInnb6tWIK2KP9nVdJRxSCXHIdN0pvJQdyi+3LOnTHyfHKIH9lTBLf2HY +Y1tpGFDWL/ywHbh7krUkIfC3vSx6xZXBNgStIH4gjpP2DxKfuPmSw1Kae2vOKcRJ +29PwB/H1TaPr0VvQTNN+cBC2oOehXpH/QRbU8Nye7uq4z/llKD5LMZAOJe7VUU5A +zxUH7DE27824CmS1ywd5JueyednsnsTjltEayQcnvAm/OEIr6rO0aDVmZH40i/W1 ++NdiWgGxt3OrvjU71BQAlaknshv2xDx9UNsLrQ+iOpDdsYxpD84s4Za0V+qnq1J5 +QCSD7oaQLi4SG9nPLC+wpEhMCVGCHtClfL1NVSBYVycbPp1iLQP3xCiwV9058XVP +T2psI+PaPLeXp1ZNpEkRl+/UyAdDKqb3C6nE1tqNbTURhuL/ECFFHiH1k4mlGZfG +n2YL90ROqKFm6YdYE357vPrdjDbow32Cqt8wJqTE3mIaxZH+vLqKRezbU8fmo+7g +yEMePsCTWw0yXPyorioLAbFZBO5512sa09CXLGmeTaLs0215zaK81HnAgMwFWT+9 +AgMBAAGjJjAkMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0G +CSqGSIb3DQEBCwUAA4ICAQASq7gPmWVMjm6XzmHJ2LSIJAjGEmjWSNBf1rvbEItH +NjxzLu4VfiShgj9y53yWoNJSJGGGtoKmaNQK63x9GzTQbN60izXlW20FP32b9Idz +6y7Ta4JSqNx1Q/OW+ABsWZZ68Ob19XLr8QY5fog3Teh7Q0jzln/rxteB7OUTJ5QC +o5Nq7PDlg/R9buRZnHHb1tmBHbSbkXK9wjgSQMtbga2EQuewJ5KYHYUrdYsklg2u +wqnxtL7iND1dGcG2J3QKdC2fT1i7EdU5ZFvDxo9VOPvQmrIzzHKfyfgmOD3QhOrz +2rCcze86Vi8zlk0yk8oQNTjLndnJgSWYq0OFl7iape1ObblulILDhb80RI5N6pK2 +WIb05Kl4Sm/bNcphi5KHtQlr7HzSNojr4qgHlo5KdQ6IW/ZOIGHgq6OjcifAIsKw +wkojOqriFdeKAiU83bWvxVUW/F9Yfe/hHCjjRxp6xMRoGTh63jGmAWxu/WR9GpFP +pfVsHLHg3p8tU4TusU1I5TuHZ/8B7atpFdlS5J8mc5bVB6oHYuaJVg4bbMGQIfll +m5CTfEKfTxkLjVtUGEhmoutb60qnXECNhUYFef69hMZ5VOUj4gAC8e2JKm/8yjG+ +K9USxsK1YTAMIKBbLqNsJmOB8mz08CxPU8R6XCvzDPr4rqnG18IcUxAlbYiH77eA +qg== -----END CERTIFICATE----- diff --git a/ndg/httpsclient/test/pki/localhost.csr b/ndg/httpsclient/test/pki/localhost.csr new file mode 100644 index 0000000..a3c5181 --- /dev/null +++ b/ndg/httpsclient/test/pki/localhost.csr @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEejCCAmICAQAwNTEMMAoGA1UECgwDTkRHMREwDwYDVQQLDAhTZWN1cml0eTES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEAsBzYsKRp63t5l049CBJFzPZ7WYlDerAypPUF0yf4Ab/1kwicJ+1eYkEelvlY +p4DiFyUI/6wVR9rG3ZNwnus2w9fquQ3kkXAwAgMzONqrwOvVliN6dO8iedvq1Ygr +Yo/2dV0lHFIJcch03Sm8lB3KL7cs6dMfJ8cogf2VMEt/YdhjW2kYUNYv/LAduHuS +tSQh8Le9LHrFlcE2BK0gfiCOk/YPEp+4+ZLDUpp7a84pxEnb0/AH8fVNo+vRW9BM +035wELag56Fekf9BFtTw3J7u6rjP+WUoPksxkA4l7tVRTkDPFQfsMTbvzbgKZLXL +B3km57J52eyexOOW0RrJBye8Cb84Qivqs7RoNWZkfjSL9bX412JaAbG3c6u+NTvU +FACVqSeyG/bEPH1Q2wutD6I6kN2xjGkPzizhlrRX6qerUnlAJIPuhpAuLhIb2c8s +L7CkSEwJUYIe0KV8vU1VIFhXJxs+nWItA/fEKLBX3TnxdU9Pamwj49o8t5enVk2k +SRGX79TIB0MqpvcLqcTW2o1tNRGG4v8QIUUeIfWTiaUZl8afZgv3RE6ooWbph1gT +fnu8+t2MNujDfYKq3zAmpMTeYhrFkf68uopF7NtTx+aj7uDIQx4+wJNbDTJc/Kiu +KgsBsVkE7nnXaxrT0JcsaZ5NouzTbXnNorzUecCAzAVZP70CAwEAAaAAMA0GCSqG +SIb3DQEBCwUAA4ICAQBQ+niVozKfjJP1VCTDb88/mAGEpEOGF+BXwN7EdQKAwZva +vAxQSpqt8h0EYyToPCV6P5GNzFQ/rRW2OYgWwXEHk3ptiQzcJzIF/F9CbE9vgYO5 +Xvosxy3uyJr15sAoS+qL4jEXg7dDni+LesYYR5qx6l/5nYG/6ql0Z/K9+Z/1xrci +00Ml6+xq/oT1kc0JQYuUKnMXgQb6Mishyt+ESoRDFt6oFqlza7TNhC/Q3YxuOjqw +Hr3rc1T9L8ArYFMhwjCBSitJS9ojqtPyXTkE4ZrQoaAAl/jdwbiHQ81/PQErgrk4 +3RkyokdrOkeK2CssJ402s8ybnbYzaI+wg4K7r4APsiOXAacfVObmPYzQLamqCNkE +hcGD6Yp9JpSgVWH+cTHa47slz3RjLxMW0EMfffYOlsWlxnlY+LHTMrphfd7otSlD +c3lSbUgURW6babKQBxOULR0AooaJK/uHgarb+6xNQoPTA7RSDfemfBGu+xAxIeE8 +xPRXH6DYjhzwiHB8tFKcfdXX+MZCQ+QUHsbeVLVl+0kkw3J5bRTo5lS6menuzbPA +osnOO2IMBrhctgspj6MJg6lyFyWUDYzWn0JU6+rF/NHmnrlFgJ5in1eg1u783bXJ +0jKPpw1QAOz+gI2MHKx2Tpbu43UtAb/35GtW711e6lhm3gZUxjVQj7qhfW1pcw== +-----END CERTIFICATE REQUEST----- diff --git a/ndg/httpsclient/test/pki/localhost.key b/ndg/httpsclient/test/pki/localhost.key index 98a3db4..4be01f3 100644 --- a/ndg/httpsclient/test/pki/localhost.key +++ b/ndg/httpsclient/test/pki/localhost.key @@ -1,27 +1,51 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAw66KF02yhaBWD4OxEFziRyRDmiIJWzH02JGRSgITea51Bvqk -JGGgPe+Mwy3XwCOv3E0y2pO0/eWpDiIh3ht3dZo1zYvhr3r3xA6elj5nvRPSkVyQ -29VwL7mRcc8IWyb+MhWyMkN09BYzR8Wo8ZlPBDyyxLk/2N0HYkdyEcU4JZ0hhg7S -NjUEQnUBd4rhfN9TT/TSzQueV4utsSk0H1yRBpJstTsj2jt8Ov/n5ZtXy0gLk4b9 -llbuOVzi3hMunORRq0EZbdzY8ok+C1YEBu1JzFu0cHiZdXqLCaneGUtPq3G8oP5v -xMWnEY92g58gdfr+GMwlr6SvhngwMd418e7p5QIDAQABAoIBAGgyO2PXQpU24DyY -oiMVYoQBQmIDd7nwqvDa6mNfIaGsjLIvqlGZDmuMcAbKFVVBa3ZCQS70ce60aMWW -1TPgqkab28RclMsNDnt+UWGtTy32LSyr/pKgX4F6LYFNfaST6c0fkmiDVPZYD+xK -ywAC43ldHM/12JKQd0sUwh/GRXR4/Y9anWR4uLcfWCRU/7JVefdUe1VWeeelCMB4 -wCcv9aQeFKnKeRopg5sryMo0thXSJCrO7UTSBmyasgNlMjUFpIIlPZQrWKXBtN48 -NT7yGZ4j4RpSS8jhIxZASmyj1eyn2bZEk5SFcFM0wN78lOIOyGBB50QJBSEuKvHT -suFBzI0CgYEA53PCyAxHcA4wfuZJgF24VTVUpyZEjjTBRtLrZxUrIagVph5iS9f8 -X8henBabaNBB/xsb17qPesCpqGJJEj2XpSKjTQdzmjCUZV8oZSLW87esXwm0Jfb6 -dmDqShwlph5l31ZG27I2uTMV8HE8PAOta5rBEFsEwEZDmwtsIvwu6NsCgYEA2G+Q -ZbFelFC6+aGKPTPrPVzX45IMPRE23f3euTJdEA+T06BD60LC0jQlxmQghlDUZkxs -s9ihPnj2nHuaxJyR4G+/56oba5Jds2ZAiU7FNGZpBHh5J6tUligRJ8cjdRiKJheb -H6imcRiD++6P2PnQUPuSnfnxo+yxDotUxtfflD8CgYA3brIn10kEvWFAA0d3Rvru -7Wbo4XFp3ZisrbdTaO57kRYeQwinwYLgLcz69S8jWz1zK7rvX+8dJyd6/I8jnzzC -gBk8SZyWQSxJWxEBp+ZzWHogCOLsdMpnr65tMXCS6JTdeHxgAb65fFK002nptbfD -Hb0/fY0Vv75RU9sCRgO2IQKBgQC+MALuKZdXsz0qIRZtw5LCQDSBiBPzEY5qMiym -BqiadWXMP/eO4wJ5XlOXauLdxGdwYVXgD8tZL0hdp4wPmxng47H5q5QeyeZGv+KU -BcDq09cSk567CiXxMNIqZyY0PlUfNf0dK8DeftFz72U+H/87OmXTJhB9oH7RrgPY -G1gfVwKBgQDiCfLBoEfwOpif1XAglNdIv6J1M3rwS85txHlY8H91iiBaoKosLzdj -ZTeqOn6ofsWj1NaR8OudZApZZT1gHjmDUnpzrQZvHn1gelsVwoZfXs1ZWX+J4giD -Rt3aZFRc4uPVgVNLHf4kADWao5bwvbxWfx74J2LQ4QJ4rxFumeg8HA== +MIIJJwIBAAKCAgEAsBzYsKRp63t5l049CBJFzPZ7WYlDerAypPUF0yf4Ab/1kwic +J+1eYkEelvlYp4DiFyUI/6wVR9rG3ZNwnus2w9fquQ3kkXAwAgMzONqrwOvVliN6 +dO8iedvq1YgrYo/2dV0lHFIJcch03Sm8lB3KL7cs6dMfJ8cogf2VMEt/YdhjW2kY +UNYv/LAduHuStSQh8Le9LHrFlcE2BK0gfiCOk/YPEp+4+ZLDUpp7a84pxEnb0/AH +8fVNo+vRW9BM035wELag56Fekf9BFtTw3J7u6rjP+WUoPksxkA4l7tVRTkDPFQfs +MTbvzbgKZLXLB3km57J52eyexOOW0RrJBye8Cb84Qivqs7RoNWZkfjSL9bX412Ja +AbG3c6u+NTvUFACVqSeyG/bEPH1Q2wutD6I6kN2xjGkPzizhlrRX6qerUnlAJIPu +hpAuLhIb2c8sL7CkSEwJUYIe0KV8vU1VIFhXJxs+nWItA/fEKLBX3TnxdU9Pamwj +49o8t5enVk2kSRGX79TIB0MqpvcLqcTW2o1tNRGG4v8QIUUeIfWTiaUZl8afZgv3 +RE6ooWbph1gTfnu8+t2MNujDfYKq3zAmpMTeYhrFkf68uopF7NtTx+aj7uDIQx4+ +wJNbDTJc/KiuKgsBsVkE7nnXaxrT0JcsaZ5NouzTbXnNorzUecCAzAVZP70CAwEA +AQKCAgA7nFgAWnZ7vZyniVUBMWYs/VU9Nkf/9DoAOeuR0JqSxQ3ziduJbEAhU9dt +1NKcR/S/Tsrf97hwTO45O9nIAOfVnHUX1EsEq4ukcr7YxLOUJn9yRWoCwJrT0ouH +YYMF5qLOkQuprV8DA44C+kiZfgfc5MywDMUj6racYWApK7TfQqZG36jaW9V8W4SD +2FgNNkDCwP6oWpocmXyWUp+H8a8xB/2MGUI8P+AbSqzyDaN3jGR7NWkLDjQMgxqn +UDdg8xAtqBOIs8dV9adregJZOva1vn6u9P2NMFiDYTaXw960dR1jCf55nhXHdT8X +Xa1uRyTTEkdKa1xGWp7HAqD+6gUsWw+fuavVMmMcgBm4tYSvFActBPZrpIuh6H1e +8g/ow+mnn1E/sfeu5vbvYCaq5mhchC7T9snI0FDH+eDqa9xBIqBA/f11IFE/kryA +zw2ZdieB5EtW9E8yMOdfVhTAj5y3nuBPcQPwlWVpoYY7PVX407qth3SpT7x8Xgan +lZFFraKvQ2MSI3iOrzHCinlsvc8SsmWJifDHAFxFmqead+Yi+M/BH0F8zkFikvmp +swWGE5znZX7EBiSVBwx7H6IFH+3HhAPVnADW4WIyykdoEydNczX0STuoj7XJdKiS +BPgse3aYfvy7bDpwUhdKbjT97Uvsm3Zj3HkEl8X1TMK2MtqUYQKCAQEA6PpoGQke +p2LRLsVdOekYG2M4uJDNtqEQ5qaZxrYEMupFPlcZv7JbIyqmE98hgKAaf+mE2fJq +Qn5puydgaLfsHjmcn3UUDTEvLnAx9Qrp79md5VFwOcAtTNA/eKhJSYujAMedJlFC +RcnHuxK3qWy0VODDx2qwqC+9GBUtH/fREQ8SXeJXfnGVwaR8vsWgtti3JefvcP21 +pHKmA8EpAK7tEuJuKBbqabbNXfsBswpfKnlG4DwcEbIWyOZ6GjSJKQuVvDQ6dGcI +YW6wC0jiqvMr8g5KbpfXP9uWhOTMg5epQqn9ALEetpnRZ3pIzLauW/pKKhcm48wG +mnADLBsYLvyuFQKCAQEAwYPtbG2RhFhMfOJjZxOVx6h/0HDsfRYmV9YZXpSs9lJE +9em0hdoA2ZF+aO8kjgWpZzG/yEnn9ZYTrks6vD+Q+bxXmtGOgt5YgB9XcaOFzhMy +QbqN9ChqZ7FezhWnYVz05GA9WQ0YqNmC7wRN22DlYUkkSx8vViDTpcTxZrgq+KAS +7h3tpENsQ9W8VEzCnKk8UcvvvbrjLDZ9AdZhbsgWa6YMxzEHduS7XnRk4pzKsLoD +zE+Flu5f0TYZoivNCt2v8qZToUt+3x63clgSxssxh40HCkpq8X46EvGSySTADRN9 +ZQTnxF+IxrsBg08DEkvYujUHyOx9gjSsdwPu1jPdCQKCAQAi9YAtUYsB/vf4lrD4 +gZuWcHsQZtoXb2/DzHgCs0lKApFFW3iHpVG7oyisjqzOSjfvvbCrq456T9G5vZzf +2clad7aizsSlfh3gUAGPDYYw8WyZ/nBimg2yGwWLK2Vj3col0c4o88SICFEqJxLc +H22n1tpJ0B6V/iPXu0W8KEl8z9AnulD6rjyUhAQb9De7eCVzUKxDPi7FcFas9Y0b +hyQX01ZIObgPCNkkIwgMP6AsFWqnNx3XIev9VWg5Cm0SdaSQSx4CXXuKczuDVfp9 +QGP1vGM62Rn5gQ/C/Toa/OTvFeba9bjwGYCskFLgNQFQ1+vNCRBS2kibX/d55M7q +LgxJAoIBACq29jdS0CYIX4nJZuuftRYHKX9XDbxmoTwONDdeVfdhHXuyhHhvMHTV +2rNK5iS7NzNiFdUeCyd4Ywu761EWLugRHL09uFlGvv447NJWp/he4F8F0MOuEB6n +VifjO7kQDbZDUAdSDrlmmbIsnwrnjno07N/wGEDIqi8TE736tPGAU+1fic8SWtl2 +WkW5U9PEv13GyDp3ACIIPPuk2IjdfaVxV6y4M8+YmYs14fbraQotZR06CGDSaTAC +4xGa3X2ihW3oLNmTkoOpghCpRCvBOkOTa5Rn7Mc1d5gq3p2rkQDR5oHf3VDJTaWg +LatQS3tEZqhjgD5nhSSyf8cdhSCe/hECggEAW8H26QCB+oDaQjevURUbkT4a5tE3 +sX9bGXlpAK+JE/hMJqxspNmXeTKm/GsMKT/JGaOIqstIYNsILjAihGbZj9OalOnX +re4rVYq5Cs7mDXVP7vLSeyX8Z6Y8qiWiwtx3UMwMXYs6+2qPbHFPr5Ult53rswaE +lIbGdYbPouEGPXxs4J0Xy4h+CQtCi5UVuR16gq2gPmqhMwLz19gXRPh3V+5hH32B +cgnqMKgAKxoJNpuPBL/v1OJ9YTFwFUokUMk3IzgTsblOXtxtpAlomv1lX6Y5JQxX +aBw0t8HRxMF+fdqq5xHEcgvp+CWqBNuPXdVPAH25nibsPuTgnPIb27PTew== -----END RSA PRIVATE KEY----- From 7992fc197332d4c7f93f55403f077cdfd5df11a0 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Wed, 14 Oct 2020 23:25:16 +0800 Subject: [PATCH 40/41] Correct a typo in subj_alt_name.py --- ndg/httpsclient/subj_alt_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndg/httpsclient/subj_alt_name.py b/ndg/httpsclient/subj_alt_name.py index 7cc3f47..7c195aa 100644 --- a/ndg/httpsclient/subj_alt_name.py +++ b/ndg/httpsclient/subj_alt_name.py @@ -69,7 +69,7 @@ class AttributeTypeAndValue(univ.Sequence): class RelativeDistinguishedName(univ.SetOf): - '''ASN.1 Realtive distinguished name''' + '''ASN.1 Relative distinguished name''' componentType = AttributeTypeAndValue() class RDNSequence(univ.SequenceOf): From 4005b64a9d4bf5b60a99e11174c75e8c00400ee0 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 17 Mar 2021 20:04:55 +0100 Subject: [PATCH 41/41] Travis CI: Test on current versions of CPython --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6d7ba85..f0f7398 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,13 @@ language: python python: - 2.7 - - 3.4 - - 3.5 - 3.6 + - 3.7 + - 3.8 + - 3.9 - nightly script: - python setup.py install -matrix: +jobs: allow_failures: - python: nightly