diff --git a/changelogs/fragments/urls-gssapi.yml b/changelogs/fragments/urls-gssapi.yml new file mode 100644 index 00000000000..c48436951d3 --- /dev/null +++ b/changelogs/fragments/urls-gssapi.yml @@ -0,0 +1,3 @@ +minor_changes: +- Added support for GSSAPI/Kerberos authentication with ``urls.py`` that is used by ``uri`` and ``get_url``. +- Added support for specify custom credentials for GSSAPI authentication. diff --git a/docs/docsite/rst/dev_guide/testing_httptester.rst b/docs/docsite/rst/dev_guide/testing_httptester.rst index a8806371430..878b6641f3a 100644 --- a/docs/docsite/rst/dev_guide/testing_httptester.rst +++ b/docs/docsite/rst/dev_guide/testing_httptester.rst @@ -17,6 +17,7 @@ HTTP Testing endpoint which provides the following capabilities: * nginx * SSL * SNI +* Negotiate Authentication Source files can be found in the `http-test-container `_ repository. diff --git a/lib/ansible/module_utils/urls.py b/lib/ansible/module_utils/urls.py index d5e87e21d57..b4cdd47342c 100644 --- a/lib/ansible/module_utils/urls.py +++ b/lib/ansible/module_utils/urls.py @@ -74,17 +74,17 @@ import ansible.module_utils.six.moves.urllib.error as urllib_error from ansible.module_utils.common.collections import Mapping from ansible.module_utils.six import PY3, string_types from ansible.module_utils.six.moves import cStringIO -from ansible.module_utils.basic import get_distribution +from ansible.module_utils.basic import get_distribution, missing_required_lib from ansible.module_utils._text import to_bytes, to_native, to_text try: # python3 import urllib.request as urllib_request - from urllib.request import AbstractHTTPHandler + from urllib.request import AbstractHTTPHandler, BaseHandler except ImportError: # python2 import urllib2 as urllib_request - from urllib2 import AbstractHTTPHandler + from urllib2 import AbstractHTTPHandler, BaseHandler urllib_request.HTTPRedirectHandler.http_error_308 = urllib_request.HTTPRedirectHandler.http_error_307 @@ -171,13 +171,105 @@ except ImportError: except ImportError: HAS_MATCH_HOSTNAME = False +HAS_CRYPTOGRAPHY = True +try: + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.exceptions import UnsupportedAlgorithm +except ImportError: + HAS_CRYPTOGRAPHY = False +# Old import for GSSAPI authentication, this is not used in urls.py but kept for backwards compatibility. try: import urllib_gssapi HAS_GSSAPI = True except ImportError: HAS_GSSAPI = False +GSSAPI_IMP_ERR = None +try: + import gssapi + + class HTTPGSSAPIAuthHandler(BaseHandler): + """ Handles Negotiate/Kerberos support through the gssapi library. """ + + AUTH_HEADER_PATTERN = re.compile(r'(?:.*)\s*(Negotiate|Kerberos)\s*([^,]*),?', re.I) + handler_order = 480 # Handle before Digest authentication + + def __init__(self, username=None, password=None): + self.username = username + self.password = password + self._context = None + + def get_auth_value(self, headers): + auth_match = self.AUTH_HEADER_PATTERN.search(headers.get('www-authenticate', '')) + if auth_match: + return auth_match.group(1), base64.b64decode(auth_match.group(2)) + + def http_error_401(self, req, fp, code, msg, headers): + # If we've already attempted the auth and we've reached this again then there was a failure. + if self._context: + return + + parsed = generic_urlparse(urlparse(req.get_full_url())) + + auth_header = self.get_auth_value(headers) + if not auth_header: + return + auth_protocol, in_token = auth_header + + username = None + if self.username: + username = gssapi.Name(self.username, name_type=gssapi.NameType.user) + + if username and self.password: + if not hasattr(gssapi.raw, 'acquire_cred_with_password'): + raise NotImplementedError("Platform GSSAPI library does not support " + "gss_acquire_cred_with_password, cannot acquire GSSAPI credential with " + "explicit username and password.") + + b_password = to_bytes(self.password, errors='surrogate_or_strict') + cred = gssapi.raw.acquire_cred_with_password(username, b_password, usage='initiate').creds + + else: + cred = gssapi.Credentials(name=username, usage='initiate') + + # Get the peer certificate for the channel binding token if possible (HTTPS). A bug on macOS causes the + # authentication to fail when the CBT is present. Just skip that platform. + cbt = None + cert = getpeercert(fp, True) + if cert and platform.system() != 'Darwin': + cert_hash = get_channel_binding_cert_hash(cert) + if cert_hash: + cbt = gssapi.raw.ChannelBindings(application_data=b"tls-server-end-point:" + cert_hash) + + # TODO: We could add another option that is set to include the port in the SPN if desired in the future. + target = gssapi.Name("HTTP@%s" % parsed['hostname'], gssapi.NameType.hostbased_service) + self._context = gssapi.SecurityContext(usage="initiate", name=target, creds=cred, channel_bindings=cbt) + + resp = None + while not self._context.complete: + out_token = self._context.step(in_token) + if not out_token: + break + + auth_header = '%s %s' % (auth_protocol, to_native(base64.b64encode(out_token))) + req.add_unredirected_header('Authorization', auth_header) + resp = self.parent.open(req) + + # The response could contain a token that the client uses to validate the server + auth_header = self.get_auth_value(resp.headers) + if not auth_header: + break + in_token = auth_header[1] + + return resp + +except ImportError: + GSSAPI_IMP_ERR = traceback.format_exc() + HTTPGSSAPIAuthHandler = None + if not HAS_MATCH_HOSTNAME: # The following block of code is under the terms and conditions of the # Python Software Foundation License @@ -408,6 +500,13 @@ class NoSSLError(SSLValidationError): pass +class MissingModuleError(Exception): + """Failed to import 3rd party module required by the caller""" + def __init__(self, message, import_traceback): + super(MissingModuleError, self).__init__(message) + self.import_traceback = import_traceback + + # Some environments (Google Compute Engine's CoreOS deploys) do not compile # against openssl and thus do not have any HTTPS support. CustomHTTPSConnection = None @@ -1032,6 +1131,43 @@ def maybe_add_ssl_handler(url, validate_certs, ca_path=None): return SSLValidationHandler(parsed.hostname, parsed.port or 443, ca_path=ca_path) +def getpeercert(response, binary_form=False): + """ Attempt to get the peer certificate of the response from urlopen. """ + # The response from urllib2.open() is different across Python 2 and 3 + if PY3: + socket = response.fp.raw._sock + else: + socket = response.fp._sock.fp._sock + + try: + return socket.getpeercert(binary_form) + except AttributeError: + pass # Not HTTPS + + +def get_channel_binding_cert_hash(certificate_der): + """ Gets the channel binding app data for a TLS connection using the peer cert. """ + if not HAS_CRYPTOGRAPHY: + return + + # Logic documented in RFC 5929 section 4 https://tools.ietf.org/html/rfc5929#section-4 + cert = x509.load_der_x509_certificate(certificate_der, default_backend()) + + hash_algorithm = None + try: + hash_algorithm = cert.signature_hash_algorithm + except UnsupportedAlgorithm: + pass + + # If the signature hash algorithm is unknown/unsupported or md5/sha1 we must use SHA256. + if not hash_algorithm or hash_algorithm.name in ['md5', 'sha1']: + hash_algorithm = hashes.SHA256() + + digest = hashes.Hash(hash_algorithm, default_backend()) + digest.update(certificate_der) + return digest.finalize() + + def rfc2822_date_string(timetuple, zone='-0000'): """Accepts a timetuple and optional zone which defaults to ``-0000`` and returns a date string as specified by RFC 2822, e.g.: @@ -1176,15 +1312,13 @@ class Request: ssl_handler = maybe_add_ssl_handler(url, validate_certs, ca_path=ca_path) if ssl_handler and not HAS_SSLCONTEXT: handlers.append(ssl_handler) - if HAS_GSSAPI and use_gssapi: - handlers.append(urllib_gssapi.HTTPSPNEGOAuthHandler()) parsed = generic_urlparse(urlparse(url)) if parsed.scheme != 'ftp': username = url_username + password = url_password if username: - password = url_password netloc = parsed.netloc elif '@' in parsed.netloc: credentials, netloc = parsed.netloc.split('@', 1) @@ -1200,7 +1334,15 @@ class Request: # reconstruct url without credentials url = urlunparse(parsed_list) - if username and not force_basic_auth: + if use_gssapi: + if HTTPGSSAPIAuthHandler: + handlers.append(HTTPGSSAPIAuthHandler(username, password)) + else: + imp_err_msg = missing_required_lib('gssapi', reason='for use_gssapi=True', + url='https://pypi.org/project/gssapi/') + raise MissingModuleError(imp_err_msg, import_traceback=GSSAPI_IMP_ERR) + + elif username and not force_basic_auth: passman = urllib_request.HTTPPasswordMgrWithDefaultRealm() # this creates a password manager @@ -1543,6 +1685,7 @@ def url_argument_spec(): force_basic_auth=dict(type='bool', default=False), client_cert=dict(type='path'), client_key=dict(type='path'), + use_gssapi=dict(type='bool', default=False), ) @@ -1603,6 +1746,7 @@ def fetch_url(module, url, data=None, headers=None, method=None, client_cert = module.params.get('client_cert') client_key = module.params.get('client_key') + use_gssapi = module.params.get('use_gssapi', use_gssapi) if not isinstance(cookies, cookiejar.CookieJar): cookies = cookiejar.LWPCookieJar() @@ -1655,6 +1799,8 @@ def fetch_url(module, url, data=None, headers=None, method=None, module.fail_json(msg='%s' % to_native(e), **info) except (ConnectionError, ValueError) as e: module.fail_json(msg=to_native(e), **info) + except MissingModuleError as e: + module.fail_json(msg=to_text(e), exception=e.import_traceback) except urllib_error.HTTPError as e: try: body = e.read() diff --git a/lib/ansible/modules/get_url.py b/lib/ansible/modules/get_url.py index 009070f689f..cc51ce99e57 100644 --- a/lib/ansible/modules/get_url.py +++ b/lib/ansible/modules/get_url.py @@ -166,6 +166,17 @@ options: - Header to identify as, generally appears in web server logs. type: str default: ansible-httpget + use_gssapi: + description: + - Use GSSAPI to perform the authentication, typically this is for Kerberos or Kerberos through Negotiate + authentication. + - Requires the Python library L(gssapi,https://github.com/pythongssapi/python-gssapi) to be installed. + - Credentials for GSSAPI can be specified with I(url_username)/I(url_password) or with the GSSAPI env var + C(KRB5CCNAME) that specified a custom Kerberos credential cache. + - NTLM authentication is C(not) supported even if the GSSAPI mech for NTLM has been installed. + type: bool + default: no + version_added: '2.11' # informational: requirements for nodes extends_documentation_fragment: - files diff --git a/lib/ansible/modules/uri.py b/lib/ansible/modules/uri.py index 890011be948..abd81ba8567 100644 --- a/lib/ansible/modules/uri.py +++ b/lib/ansible/modules/uri.py @@ -176,6 +176,17 @@ options: - Header to identify as, generally appears in web server logs. type: str default: ansible-httpget + use_gssapi: + description: + - Use GSSAPI to perform the authentication, typically this is for Kerberos or Kerberos through Negotiate + authentication. + - Requires the Python library L(gssapi,https://github.com/pythongssapi/python-gssapi) to be installed. + - Credentials for GSSAPI can be specified with I(url_username)/I(url_password) or with the GSSAPI env var + C(KRB5CCNAME) that specified a custom Kerberos credential cache. + - NTLM authentication is C(not) supported even if the GSSAPI mech for NTLM has been installed. + type: bool + default: no + version_added: '2.11' notes: - The dependency on httplib2 was removed in Ansible 2.1. - The module returns all the HTTP headers in lower-case. diff --git a/lib/ansible/plugins/doc_fragments/url.py b/lib/ansible/plugins/doc_fragments/url.py index ddb8e4d1147..724c28efba4 100644 --- a/lib/ansible/plugins/doc_fragments/url.py +++ b/lib/ansible/plugins/doc_fragments/url.py @@ -63,4 +63,15 @@ options: - PEM formatted file that contains your private key to be used for SSL client authentication. - If C(client_cert) contains both the certificate and key, this option is not required. type: path + use_gssapi: + description: + - Use GSSAPI to perform the authentication, typically this is for Kerberos or Kerberos through Negotiate + authentication. + - Requires the Python library L(gssapi,https://github.com/pythongssapi/python-gssapi) to be installed. + - Credentials for GSSAPI can be specified with I(url_username)/I(url_password) or with the GSSAPI env var + C(KRB5CCNAME) that specified a custom Kerberos credential cache. + - NTLM authentication is C(not) supported even if the GSSAPI mech for NTLM has been installed. + type: bool + default: no + version_added: '2.11' ''' diff --git a/lib/ansible/plugins/lookup/url.py b/lib/ansible/plugins/lookup/url.py index 7990d31ab35..29c00f14f4e 100644 --- a/lib/ansible/plugins/lookup/url.py +++ b/lib/ansible/plugins/lookup/url.py @@ -99,7 +99,9 @@ options: - section: url_lookup key: follow_redirects use_gssapi: - description: Use GSSAPI handler of requests + description: + - Use GSSAPI handler of requests + - As of Ansible 2.11, GSSAPI credentials can be specified with I(username) and I(password). type: boolean version_added: "2.10" default: False diff --git a/test/integration/targets/get_url/tasks/main.yml b/test/integration/targets/get_url/tasks/main.yml index 7edd4ced574..89d52b7dbbb 100644 --- a/test/integration/targets/get_url/tasks/main.yml +++ b/test/integration/targets/get_url/tasks/main.yml @@ -534,3 +534,12 @@ that: - '(result.content | b64decode) == "ansible.http.tests:SUCCESS"' when: has_httptester + +- name: Test use_gssapi=True + include_tasks: + file: use_gssapi.yml + apply: + environment: + KRB5_CONFIG: '{{ krb5_config }}' + KRB5CCNAME: FILE:{{ remote_tmp_dir }}/krb5.cc + when: krb5_config is defined diff --git a/test/integration/targets/get_url/tasks/use_gssapi.yml b/test/integration/targets/get_url/tasks/use_gssapi.yml new file mode 100644 index 00000000000..121cbacd100 --- /dev/null +++ b/test/integration/targets/get_url/tasks/use_gssapi.yml @@ -0,0 +1,60 @@ +- name: Skip explicit auth tests on FreeBSD as Heimdal there does not have gss_acquire_cred_with_password + when: ansible_facts.os_family != 'FreeBSD' + block: + - name: test Negotiate auth over HTTP with explicit credentials + get_url: + url: http://{{ httpbin_host }}/gssapi + dest: '{{ remote_tmp_dir }}/gssapi_explicit.txt' + use_gssapi: yes + url_username: '{{ krb5_username }}' + url_password: '{{ krb5_password }}' + register: http_explicit + + - name: get result of test Negotiate auth over HTTP with explicit credentials + slurp: + path: '{{ remote_tmp_dir }}/gssapi_explicit.txt' + register: http_explicit_actual + + - name: assert test Negotiate auth with implicit credentials + assert: + that: + - http_explicit.status_code == 200 + - http_explicit_actual.content | b64decode | trim == 'Microsoft Rulz' + +- name: FreeBSD - verify it fails with explicit credential + get_url: + url: http://{{ httpbin_host }}/gssapi + dest: '{{ remote_tmp_dir }}/gssapi_explicit.txt' + use_gssapi: yes + url_username: '{{ krb5_username }}' + url_password: '{{ krb5_password }}' + register: explicit_failure + when: ansible_facts.os_family == 'FreeBSD' + failed_when: + - '"Platform GSSAPI library does not support gss_acquire_cred_with_password, cannot acquire GSSAPI credential with explicit username and password" not in explicit_failure.msg' + +- name: skip tests on macOS, I cannot seem to get it to read a credential from a custom ccache + when: ansible_facts.distribution != 'MacOSX' + block: + - name: get Kerberos ticket for implicit auth tests + httptester_kinit: + username: '{{ krb5_username }}' + password: '{{ krb5_password }}' + + - name: test Negotiate auth over HTTPS with implicit credentials + get_url: + url: https://{{ httpbin_host }}/gssapi + dest: '{{ remote_tmp_dir }}/gssapi_implicit.txt' + use_gssapi: yes + register: https_implicit + + - name: get result of test Negotiate auth over HTTPS with implicit credentials + slurp: + path: '{{ remote_tmp_dir }}/gssapi_implicit.txt' + register: https_implicit_actual + + - name: assert test Negotiate auth with implicit credentials + assert: + that: + - https_implicit.status_code == 200 + - https_implicit_actual.content | b64decode | trim == 'Microsoft Rulz' diff --git a/test/integration/targets/module_utils_urls/aliases b/test/integration/targets/module_utils_urls/aliases new file mode 100644 index 00000000000..3c4491b0e0d --- /dev/null +++ b/test/integration/targets/module_utils_urls/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +needs/httptester diff --git a/test/integration/targets/module_utils_urls/library/test_peercert.py b/test/integration/targets/module_utils_urls/library/test_peercert.py new file mode 100644 index 00000000000..ecb7d2046f3 --- /dev/null +++ b/test/integration/targets/module_utils_urls/library/test_peercert.py @@ -0,0 +1,98 @@ +#!/usr/bin/python + +# Copyright: (c) 2020, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: test_perrcert +short_description: Test getting the peer certificate of a HTTP response +description: Test getting the peer certificate of a HTTP response. +options: + url: + description: The endpoint to get the peer cert for + required: true + type: str +author: +- Ansible Project +''' + +EXAMPLES = r''' +# +''' + +RETURN = r''' +# +''' + +import base64 + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_text +from ansible.module_utils.urls import getpeercert, Request + + +def get_x509_shorthand(name, value): + prefix = { + 'countryName': 'C', + 'stateOrProvinceName': 'ST', + 'localityName': 'L', + 'organizationName': 'O', + 'commonName': 'CN', + 'organizationalUnitName': 'OU', + }[name] + + return '%s=%s' % (prefix, value) + + +def main(): + module_args = dict( + url=dict(type='str', required=True), + ) + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + result = { + 'changed': False, + 'cert': None, + 'raw_cert': None, + } + + req = Request().get(module.params['url']) + try: + cert = getpeercert(req) + b_cert = getpeercert(req, binary_form=True) + + finally: + req.close() + + if cert: + processed_cert = { + 'issuer': '', + 'not_after': cert.get('notAfter', None), + 'not_before': cert.get('notBefore', None), + 'serial_number': cert.get('serialNumber', None), + 'subject': '', + 'version': cert.get('version', None), + } + + for field in ['issuer', 'subject']: + field_values = [] + for x509_part in cert.get(field, []): + field_values.append(get_x509_shorthand(x509_part[0][0], x509_part[0][1])) + + processed_cert[field] = ",".join(field_values) + + result['cert'] = processed_cert + + if b_cert: + result['raw_cert'] = to_text(base64.b64encode(b_cert)) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/module_utils_urls/meta/main.yml b/test/integration/targets/module_utils_urls/meta/main.yml new file mode 100644 index 00000000000..f3a332d55d0 --- /dev/null +++ b/test/integration/targets/module_utils_urls/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: +- prepare_http_tests +- setup_remote_tmp_dir diff --git a/test/integration/targets/module_utils_urls/tasks/main.yml b/test/integration/targets/module_utils_urls/tasks/main.yml new file mode 100644 index 00000000000..ca76a7d58e8 --- /dev/null +++ b/test/integration/targets/module_utils_urls/tasks/main.yml @@ -0,0 +1,32 @@ +- name: get peercert for HTTP connection + test_peercert: + url: http://{{ httpbin_host }}/get + register: cert_http + +- name: assert get peercert for HTTP connection + assert: + that: + - cert_http.raw_cert == None + +- name: get peercert for HTTPS connection + test_peercert: + url: https://{{ httpbin_host }}/get + register: cert_https + +# Alpine does not have openssl, just make sure the text was actually set instead +- name: check if openssl is installed + command: which openssl + ignore_errors: yes + register: openssl + +- name: get actual certificate from endpoint + shell: echo | openssl s_client -connect {{ httpbin_host }}:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' + register: cert_https_actual + changed_when: no + when: openssl is successful + +- name: assert get peercert for HTTPS connection + assert: + that: + - cert_https.raw_cert != None + - openssl is failed or cert_https.raw_cert == cert_https_actual.stdout_lines[1:-1] | join("") diff --git a/test/integration/targets/prepare_http_tests/handlers/main.yml b/test/integration/targets/prepare_http_tests/handlers/main.yml new file mode 100644 index 00000000000..172cab737f3 --- /dev/null +++ b/test/integration/targets/prepare_http_tests/handlers/main.yml @@ -0,0 +1,4 @@ +- name: Remove python gssapi + pip: + name: gssapi + state: absent diff --git a/test/integration/targets/prepare_http_tests/library/httptester_kinit.py b/test/integration/targets/prepare_http_tests/library/httptester_kinit.py new file mode 100644 index 00000000000..3a93c17df44 --- /dev/null +++ b/test/integration/targets/prepare_http_tests/library/httptester_kinit.py @@ -0,0 +1,134 @@ +#!/usr/bin/python + +# Copyright: (c) 2020, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: httptester_kinit +short_description: Get Kerberos ticket +description: Get Kerberos ticket using kinit non-interactively. +options: + username: + description: The username to get the ticket for. + required: true + type: str + password: + description: The password for I(username). + required; true + type: str +author: +- Ansible Project +''' + +EXAMPLES = r''' +# +''' + +RETURN = r''' +# +''' + +import contextlib +import errno +import os +import subprocess + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_bytes, to_text + +try: + import configparser +except ImportError: + import ConfigParser as configparser + + +@contextlib.contextmanager +def env_path(name, value, default_value): + """ Adds a value to a PATH-like env var and preserve the existing value if present. """ + orig_value = os.environ.get(name, None) + os.environ[name] = '%s:%s' % (value, orig_value or default_value) + try: + yield + + finally: + if orig_value: + os.environ[name] = orig_value + + else: + del os.environ[name] + + +@contextlib.contextmanager +def krb5_conf(module, config): + """ Runs with a custom krb5.conf file that extends the existing config if present. """ + if config: + ini_config = configparser.ConfigParser() + for section, entries in config.items(): + ini_config.add_section(section) + for key, value in entries.items(): + ini_config.set(section, key, value) + + config_path = os.path.join(module.tmpdir, 'krb5.conf') + with open(config_path, mode='wt') as config_fd: + ini_config.write(config_fd) + + with env_path('KRB5_CONFIG', config_path, '/etc/krb5.conf'): + yield + + else: + yield + + +def main(): + module_args = dict( + username=dict(type='str', required=True), + password=dict(type='str', required=True, no_log=True), + ) + module = AnsibleModule( + argument_spec=module_args, + required_together=[('username', 'password')], + ) + + # Debugging purposes, get the Kerberos version. On platforms like OpenSUSE this may not be on the PATH. + try: + process = subprocess.Popen(['krb5-config', '--version'], stdout=subprocess.PIPE) + stdout, stderr = process.communicate() + version = to_text(stdout) + except OSError as e: + if e.errno != errno.ENOENT: + raise + version = 'Unknown (no krb5-config)' + + # Heimdal has a few quirks that we want to paper over in this module + # 1. KRB5_TRACE does not work in any released version (<=7.7), we need to use a custom krb5.config to enable it + # 2. When reading the password it reads from the pty not stdin by default causing an issue with subprocess. We + # can control that behaviour with '--password-file=STDIN' + is_heimdal = os.uname()[0] in ['Darwin', 'FreeBSD'] + + kinit_args = ['kinit'] + config = {} + if is_heimdal: + kinit_args.append('--password-file=STDIN') + config['logging'] = {'krb5': 'FILE:/dev/stdout'} + kinit_args.append(to_text(module.params['username'], errors='surrogate_or_strict')) + + with krb5_conf(module, config): + # Weirdly setting KRB5_CONFIG in the modules environment block does not work unless we pass it in explicitly. + # Take a copy of the existing environment to make sure the process has the same env vars as ours. Also set + # KRB5_TRACE to output and debug logs helping to identify problems when calling kinit with MIT. + kinit_env = os.environ.copy() + kinit_env['KRB5_TRACE'] = '/dev/stdout' + + process = subprocess.Popen(kinit_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=kinit_env) + stdout, stderr = process.communicate(to_bytes(module.params['password'], errors='surrogate_or_strict') + b'\n') + rc = process.returncode + + module.exit_json(changed=True, stdout=to_text(stdout), stderr=to_text(stderr), rc=rc, version=version) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/prepare_http_tests/meta/main.yml b/test/integration/targets/prepare_http_tests/meta/main.yml index 1810d4bec98..c2c543a2383 100644 --- a/test/integration/targets/prepare_http_tests/meta/main.yml +++ b/test/integration/targets/prepare_http_tests/meta/main.yml @@ -1,2 +1,3 @@ dependencies: - setup_remote_tmp_dir + - setup_remote_constraints diff --git a/test/integration/targets/prepare_http_tests/tasks/kerberos.yml b/test/integration/targets/prepare_http_tests/tasks/kerberos.yml new file mode 100644 index 00000000000..9d5956fb4d2 --- /dev/null +++ b/test/integration/targets/prepare_http_tests/tasks/kerberos.yml @@ -0,0 +1,61 @@ +- set_fact: + krb5_config: '{{ remote_tmp_dir }}/krb5.conf' + krb5_realm: '{{ httpbin_host.split(".")[1:] | join(".") | upper }}' + krb5_provider: '{{ (ansible_facts.os_family == "FreeBSD" or ansible_facts.distribution == "MacOSX") | ternary("Heimdal", "MIT") }}' + +- set_fact: + krb5_username: admin@{{ krb5_realm }} + +- name: Create krb5.conf file + template: + src: krb5.conf.j2 + dest: '{{ krb5_config }}' + +- name: Include distribution specific variables + include_vars: '{{ lookup("first_found", params) }}' + vars: + params: + files: + - '{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml' + - '{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml' + - '{{ ansible_facts.distribution }}.yml' + - '{{ ansible_facts.os_family }}.yml' + - default.yml + paths: + - '{{ role_path }}/vars' + +- name: Install Kerberos sytem packages + package: + name: '{{ krb5_packages }}' + state: present + when: ansible_facts.distribution not in ['Alpine', 'MacOSX'] + +# apk isn't available on ansible-base so just call command +- name: Alpine - Install Kerberos system packages + command: apk add {{ krb5_packages | join(' ') }} + when: ansible_facts.distribution == 'Alpine' + +- name: Install python gssapi + pip: + name: + - gssapi + - importlib ; python_version < '2.7' + state: present + extra_args: '-c {{ remote_constraints }}' + environment: + # Need this custom path for OpenSUSE as krb5-config is placed there + PATH: '{{ ansible_facts.env.PATH }}:/usr/lib/mit/bin' + notify: Remove python gssapi + +- name: test the environment to make sure Kerberos is working properly + httptester_kinit: + username: '{{ krb5_username }}' + password: '{{ krb5_password }}' + environment: + KRB5_CONFIG: '{{ krb5_config }}' + KRB5CCNAME: FILE:{{ remote_tmp_dir }}/krb5.cc + +- name: remove test credential cache + file: + path: '{{ remote_tmp_dir }}/krb5.cc' + state: absent diff --git a/test/integration/targets/prepare_http_tests/tasks/main.yml b/test/integration/targets/prepare_http_tests/tasks/main.yml index 86e350c24ef..9ab00221ad5 100644 --- a/test/integration/targets/prepare_http_tests/tasks/main.yml +++ b/test/integration/targets/prepare_http_tests/tasks/main.yml @@ -22,3 +22,13 @@ - has_httptester|bool # skip the setup if running on Windows Server 2008 as httptester is not available - ansible_os_family != 'Windows' or (ansible_os_family == 'Windows' and not ansible_distribution_version.startswith("6.0.")) + +- set_fact: + krb5_password: "{{ lookup('env', 'KRB5_PASSWORD') }}" + +- name: setup Kerberos client + include_tasks: kerberos.yml + when: + - has_httptester|bool + - ansible_os_family != 'Windows' + - krb5_password != '' diff --git a/test/integration/targets/prepare_http_tests/templates/krb5.conf.j2 b/test/integration/targets/prepare_http_tests/templates/krb5.conf.j2 new file mode 100644 index 00000000000..3ddfe5eaab5 --- /dev/null +++ b/test/integration/targets/prepare_http_tests/templates/krb5.conf.j2 @@ -0,0 +1,25 @@ +[libdefaults] + default_realm = {{ krb5_realm | upper }} + dns_lookup_realm = false + dns_lookup_kdc = false + rdns = false + +[realms] + {{ krb5_realm | upper }} = { +{% if krb5_provider == 'Heimdal' %} +{# Heimdal seems to only use UDP unless TCP is explicitly set and we must use TCP as the SSH tunnel only supports TCP. #} +{# The hostname doesn't seem to work when using the alias, just use localhost as that works. #} + kdc = tcp/127.0.0.1 + admin_server = tcp/127.0.0.1 +{% else %} + kdc = {{ httpbin_host }} + admin_server = {{ httpbin_host }} +{% endif %} + } + +[domain_realm] + .{{ krb5_realm | lower }} = {{ krb5_realm | upper }} + {{ krb5_realm | lower }} = {{ krb5_realm | upper }} + +[logging] + krb5 = FILE:/dev/stdout diff --git a/test/integration/targets/prepare_http_tests/vars/Alpine.yml b/test/integration/targets/prepare_http_tests/vars/Alpine.yml new file mode 100644 index 00000000000..2ac6a380989 --- /dev/null +++ b/test/integration/targets/prepare_http_tests/vars/Alpine.yml @@ -0,0 +1,3 @@ +krb5_packages: +- krb5 +- krb5-dev diff --git a/test/integration/targets/prepare_http_tests/vars/Debian.yml b/test/integration/targets/prepare_http_tests/vars/Debian.yml new file mode 100644 index 00000000000..2b6f9b8df13 --- /dev/null +++ b/test/integration/targets/prepare_http_tests/vars/Debian.yml @@ -0,0 +1,3 @@ +krb5_packages: +- krb5-user +- libkrb5-dev diff --git a/test/integration/targets/prepare_http_tests/vars/FreeBSD.yml b/test/integration/targets/prepare_http_tests/vars/FreeBSD.yml new file mode 100644 index 00000000000..752b5368a81 --- /dev/null +++ b/test/integration/targets/prepare_http_tests/vars/FreeBSD.yml @@ -0,0 +1,2 @@ +krb5_packages: +- heimdal diff --git a/test/integration/targets/prepare_http_tests/vars/Suse.yml b/test/integration/targets/prepare_http_tests/vars/Suse.yml new file mode 100644 index 00000000000..0e159c4f07a --- /dev/null +++ b/test/integration/targets/prepare_http_tests/vars/Suse.yml @@ -0,0 +1,3 @@ +krb5_packages: +- krb5-client +- krb5-devel diff --git a/test/integration/targets/prepare_http_tests/vars/default.yml b/test/integration/targets/prepare_http_tests/vars/default.yml new file mode 100644 index 00000000000..5bc07d54769 --- /dev/null +++ b/test/integration/targets/prepare_http_tests/vars/default.yml @@ -0,0 +1,3 @@ +krb5_packages: +- krb5-devel +- krb5-workstation diff --git a/test/integration/targets/uri/tasks/main.yml b/test/integration/targets/uri/tasks/main.yml index 409607af32b..2297f6f189d 100644 --- a/test/integration/targets/uri/tasks/main.yml +++ b/test/integration/targets/uri/tasks/main.yml @@ -598,3 +598,12 @@ - name: Check return-content import_tasks: return-content.yml + +- name: Test use_gssapi=True + include_tasks: + file: use_gssapi.yml + apply: + environment: + KRB5_CONFIG: '{{ krb5_config }}' + KRB5CCNAME: FILE:{{ remote_tmp_dir }}/krb5.cc + when: krb5_config is defined diff --git a/test/integration/targets/uri/tasks/use_gssapi.yml b/test/integration/targets/uri/tasks/use_gssapi.yml new file mode 100644 index 00000000000..2a084888dd3 --- /dev/null +++ b/test/integration/targets/uri/tasks/use_gssapi.yml @@ -0,0 +1,76 @@ +- name: test that endpoint offers Negotiate auth + uri: + url: http://{{ httpbin_host }}/gssapi + status_code: 401 + register: no_auth_failure + failed_when: no_auth_failure.www_authenticate != 'Negotiate' + +- name: Skip explicit auth tests on FreeBSD as Heimdal there does not have gss_acquire_cred_with_password + when: ansible_facts.os_family != 'FreeBSD' + block: + - name: test Negotiate auth over HTTP with explicit credentials + uri: + url: http://{{ httpbin_host }}/gssapi + use_gssapi: yes + url_username: '{{ krb5_username }}' + url_password: '{{ krb5_password }}' + return_content: yes + register: http_explicit + + - name: test Negotiate auth over HTTPS with explicit credentials + uri: + url: https://{{ httpbin_host }}/gssapi + use_gssapi: yes + url_username: '{{ krb5_username }}' + url_password: '{{ krb5_password }}' + return_content: yes + register: https_explicit + + - name: assert test Negotiate auth with implicit credentials + assert: + that: + - http_explicit.status == 200 + - http_explicit.content | trim == 'Microsoft Rulz' + - https_explicit.status == 200 + - https_explicit.content | trim == 'Microsoft Rulz' + +- name: FreeBSD - verify it fails with explicit credential + uri: + url: https://{{ httpbin_host }}/gssapi + use_gssapi: yes + url_username: '{{ krb5_username }}' + url_password: '{{ krb5_password }}' + register: explicit_failure + when: ansible_facts.os_family == 'FreeBSD' + failed_when: + - '"Platform GSSAPI library does not support gss_acquire_cred_with_password, cannot acquire GSSAPI credential with explicit username and password" not in explicit_failure.msg' + +- name: skip tests on macOS, I cannot seem to get it to read a credential from a custom ccache + when: ansible_facts.distribution != 'MacOSX' + block: + - name: get Kerberos ticket for implicit auth tests + httptester_kinit: + username: '{{ krb5_username }}' + password: '{{ krb5_password }}' + + - name: test Negotiate auth over HTTP with implicit credentials + uri: + url: http://{{ httpbin_host }}/gssapi + use_gssapi: yes + return_content: yes + register: http_implicit + + - name: test Negotiate auth over HTTPS with implicit credentials + uri: + url: https://{{ httpbin_host }}/gssapi + use_gssapi: yes + return_content: yes + register: https_implicit + + - name: assert test Negotiate auth with implicit credentials + assert: + that: + - http_implicit.status == 200 + - http_implicit.content | trim == 'Microsoft Rulz' + - https_implicit.status == 200 + - https_implicit.content | trim == 'Microsoft Rulz' diff --git a/test/lib/ansible_test/_data/requirements/constraints.txt b/test/lib/ansible_test/_data/requirements/constraints.txt index 33daaadead2..fbf635f2bb7 100644 --- a/test/lib/ansible_test/_data/requirements/constraints.txt +++ b/test/lib/ansible_test/_data/requirements/constraints.txt @@ -43,6 +43,7 @@ botocore >= 1.10.0, < 1.14 ; python_version < '2.7' # adds support for the follo botocore >= 1.10.0 ; python_version >= '2.7' # adds support for the following AWS services: secretsmanager, fms, and acm-pca setuptools < 37 ; python_version == '2.6' # setuptools 37 and later require python 2.7 or later setuptools < 45 ; python_version == '2.7' # setuptools 45 and later require python 3.5 or later +gssapi < 1.6.0 ; python_version <= '2.7' # gssapi 1.6.0 and later require python 3 or later # freeze antsibull-changelog for consistent test results antsibull-changelog == 0.7.0 diff --git a/test/lib/ansible_test/_internal/cli.py b/test/lib/ansible_test/_internal/cli.py index 825317c2c5b..90f4e4ea840 100644 --- a/test/lib/ansible_test/_internal/cli.py +++ b/test/lib/ansible_test/_internal/cli.py @@ -1003,7 +1003,7 @@ def add_httptester_options(parser, argparse): group.add_argument('--httptester', metavar='IMAGE', - default='quay.io/ansible/http-test-container:1.1.0', + default='quay.io/ansible/http-test-container:1.3.0', help='docker image to use for the httptester container') group.add_argument('--disable-httptester', @@ -1016,6 +1016,9 @@ def add_httptester_options(parser, argparse): action='store_true', help=argparse.SUPPRESS) # internal use only + parser.add_argument('--httptester-krb5-password', + help=argparse.SUPPRESS) # internal use only + def add_extra_docker_options(parser, integration=True): """ diff --git a/test/lib/ansible_test/_internal/config.py b/test/lib/ansible_test/_internal/config.py index a3dce1096f2..61b05d3b3fc 100644 --- a/test/lib/ansible_test/_internal/config.py +++ b/test/lib/ansible_test/_internal/config.py @@ -9,6 +9,7 @@ from . import types as t from .util import ( find_python, + generate_password, generate_pip_command, ApplicationError, ) @@ -123,6 +124,8 @@ class EnvironmentConfig(CommonConfig): self.inject_httptester = args.inject_httptester if 'inject_httptester' in args else False # type: bool self.httptester = docker_qualify_image(args.httptester if 'httptester' in args else '') # type: str + krb5_password = args.httptester_krb5_password if 'httptester_krb5_password' in args else '' + self.httptester_krb5_password = krb5_password or generate_password() # type: str if self.get_delegated_completion().get('httptester', 'enabled') == 'disabled': self.httptester = False diff --git a/test/lib/ansible_test/_internal/delegation.py b/test/lib/ansible_test/_internal/delegation.py index 23ee4a6a755..1a04e6a250e 100644 --- a/test/lib/ansible_test/_internal/delegation.py +++ b/test/lib/ansible_test/_internal/delegation.py @@ -309,7 +309,7 @@ def delegate_docker(args, exclude, require, integration_targets): test_options += ['--volume', '%s:%s' % (docker_socket, docker_socket)] if httptester_id: - test_options += ['--env', 'HTTPTESTER=1'] + test_options += ['--env', 'HTTPTESTER=1', '--env', 'KRB5_PASSWORD=%s' % args.httptester_krb5_password] for host in HTTPTESTER_HOSTS: test_options += ['--link', '%s:%s' % (httptester_id, host)] @@ -462,7 +462,7 @@ def delegate_remote(args, exclude, require, integration_targets): cmd = generate_command(args, python_interpreter, os.path.join(ansible_root, 'bin'), content_root, options, exclude, require) if httptester_id: - cmd += ['--inject-httptester'] + cmd += ['--inject-httptester', '--httptester-krb5-password', args.httptester_krb5_password] if isinstance(args, TestConfig): if args.coverage and not args.coverage_label: diff --git a/test/lib/ansible_test/_internal/executor.py b/test/lib/ansible_test/_internal/executor.py index f93e388a19b..a31d205dc2a 100644 --- a/test/lib/ansible_test/_internal/executor.py +++ b/test/lib/ansible_test/_internal/executor.py @@ -1247,10 +1247,18 @@ def start_httptester(args): remote=8080, container=80, ), + dict( + remote=8088, + container=88, + ), dict( remote=8443, container=443, ), + dict( + remote=8749, + container=749, + ), ] container_id = get_docker_container_id() @@ -1287,6 +1295,7 @@ def run_httptester(args, ports=None): """ options = [ '--detach', + '--env', 'KRB5_PASSWORD=%s' % args.httptester_krb5_password, ] if ports: @@ -1331,7 +1340,9 @@ def inject_httptester(args): rules = ''' rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080 +rdr pass inet proto tcp from any to any port 88 -> 127.0.0.1 port 8088 rdr pass inet proto tcp from any to any port 443 -> 127.0.0.1 port 8443 +rdr pass inet proto tcp from any to any port 749 -> 127.0.0.1 port 8749 ''' cmd = ['pfctl', '-ef', '-'] @@ -1343,7 +1354,9 @@ rdr pass inet proto tcp from any to any port 443 -> 127.0.0.1 port 8443 elif iptables: ports = [ (80, 8080), + (88, 8088), (443, 8443), + (749, 8749), ] for src, dst in ports: @@ -1406,6 +1419,7 @@ def integration_environment(args, target, test_dir, inventory_path, ansible_conf if args.inject_httptester: env.update(dict( HTTPTESTER='1', + KRB5_PASSWORD=args.httptester_krb5_password, )) callback_plugins = ['junit'] + (env_config.callback_plugins or [] if env_config else []) diff --git a/test/lib/ansible_test/_internal/util.py b/test/lib/ansible_test/_internal/util.py index f0c902a7148..141048b17c2 100644 --- a/test/lib/ansible_test/_internal/util.py +++ b/test/lib/ansible_test/_internal/util.py @@ -364,6 +364,7 @@ def common_environment(): optional = ( 'HTTPTESTER', + 'KRB5_PASSWORD', 'LD_LIBRARY_PATH', 'SSH_AUTH_SOCK', # MacOS High Sierra Compatibility diff --git a/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha256.pem b/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha256.pem new file mode 100644 index 00000000000..fcc6f7a6be3 --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha256.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBjzCCATWgAwIBAgIQeNQTxkMgq4BF9tKogIGXUTAKBggqhkjOPQQ +DAjAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MDMxN1 +oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxNjBZM +BMGByqGSM49AgEGCCqGSM49AwEHA0IABDAfXTLOaC3ElgErlgk2tBlM +wf9XmGlGBw4vBtMJap1hAqbsdxFm6rhK3QU8PFFpv8Z/AtRG7ba3UwQ +prkssClejZzBlMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBg +EFBQcDAgYIKwYBBQUHAwEwFQYDVR0RBA4wDIIKU0VSVkVSMjAxNjAdB +gNVHQ4EFgQUnFDE8824TYAiBeX4fghEEg33UgYwCgYIKoZIzj0EAwID +SAAwRQIhAK3rXA4/0i6nm/U7bi6y618Ci2Is8++M3tYIXnEsA7zSAiA +w2s6bJoI+D7Xaey0Hp0gkks9z55y976keIEI+n3qkzw== +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha512.pem b/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha512.pem new file mode 100644 index 00000000000..1b45be5fbf1 --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha512.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBjjCCATWgAwIBAgIQHVj2AGEwd6pOOSbcf0skQDAKBggqhkjOPQQ +DBDAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA3NTUzOV +oXDTE4MDUzMDA4MTUzOVowFTETMBEGA1UEAwwKU0VSVkVSMjAxNjBZM +BMGByqGSM49AgEGCCqGSM49AwEHA0IABL8d9S++MFpfzeH8B3vG/PjA +AWg8tGJVgsMw9nR+OfC9ltbTUwhB+yPk3JPcfW/bqsyeUgq4//LhaSp +lOWFNaNqjZzBlMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBg +EFBQcDAgYIKwYBBQUHAwEwFQYDVR0RBA4wDIIKU0VSVkVSMjAxNjAdB +gNVHQ4EFgQUKUkCgLlxoeai0EtQrZth1/BSc5kwCgYIKoZIzj0EAwQD +RwAwRAIgRrV7CLpDG7KueyFA3ZDced9dPOcv2Eydx/hgrfxYEcYCIBQ +D35JvzmqU05kSFV5eTvkhkaDObd7V55vokhm31+Li +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha256.pem b/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha256.pem new file mode 100644 index 00000000000..fcbe01fc34a --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha256.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZDCCAhugAwIBAgIUbo9YpgkeniC5jRmOgFYX3mcVJn4wPgYJKoZIhvcNAQEK +MDGgDTALBglghkgBZQMEAgGhGjAYBgkqhkiG9w0BAQgwCwYJYIZIAWUDBAIBogQC +AgDeMBMxETAPBgNVBAMMCE9NSSBSb290MB4XDTIwMDkwNDE4NTMyNloXDTIxMDkw +NDE4NTMyNlowGDEWMBQGA1UEAwwNREMwMS5vbWkudGVzdDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANN++3POgcKcPILMdHWIEPiVENtKoJQ8iqKKeL+/ +j5oUULVuIn15H/RYMNFmStRIvj0dIL1JAq4W411wG2Tf/6niU2YSKPOAOtrVREef +gNvMZ06TYlC8UcGCLv4dBkU3q/FELV66lX9x6LcVwf2f8VWfDg4VNuwyg/eQUIgc +/yd5VV+1VXTf39QufVV+/hOtPptu+fBKOIuiuKm6FIqroqLri0Ysp6tWrSd7P6V4 +6zT2yd17981vaEv5Zek2t39PoLYzJb3rvqQmumgFBIUQ1eMPLFCXX8YYYC/9ByK3 +mdQaEnkD2eIOARLnojr2A228EgPpdM8phQkDzeWeYnhLiysCAwEAAaNJMEcwCQYD +VR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGAYDVR0R +BBEwD4INREMwMS5vbWkudGVzdDA+BgkqhkiG9w0BAQowMaANMAsGCWCGSAFlAwQC +AaEaMBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgGiBAICAN4DggEBAA66cbtiysjq +sCaDiYyRWCS9af2DGxJ6NAyku2aX+xgmRQzUzFAN5TihcPau+zzpk2zQKHDVMhNx +ouhTkIe6mq9KegpUuesWwkJ5KEeuBT949tIru2wAtlSVDvDcau5T9pRI/RLlnsWg +0sWaUAh/ujL+VKk6AanC4MRV69llwJcAVxlS/tYjwC74Dr8tMT1TQcVDvywB85e9 +mA3uz8mGKfiMk2TKD6+6on0UwBMB8NbKSB5bcgp+CJ2ceeblmCOgOcOcV5PCGoFj +fgAppr7HjfNPYaIV5l59LfKo2Bj9kXPMqA6/D4gJ3hwoJdY/NOtuNyk8cxWMnWUe ++E2Mm6ZnB3Y= +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha512.pem b/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha512.pem new file mode 100644 index 00000000000..add31090be3 --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha512.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZDCCAhugAwIBAgIUbo9YpgkeniC5jRmOgFYX3mcVJoEwPgYJKoZIhvcNAQEK +MDGgDTALBglghkgBZQMEAgOhGjAYBgkqhkiG9w0BAQgwCwYJYIZIAWUDBAIDogQC +AgC+MBMxETAPBgNVBAMMCE9NSSBSb290MB4XDTIwMDkwNDE4NTMyN1oXDTIxMDkw +NDE4NTMyN1owGDEWMBQGA1UEAwwNREMwMS5vbWkudGVzdDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANZMAyRDBn54RfveeVtikepqsyKVKooAc471snl5 +mEEeo6ZvlOrK1VGGmo/VlF4R9iW6f5iqxG792KXk+lDtx8sbapZWk/aQa+6I9wml +p17ocW4Otl7XyQ74UTQlxmrped0rgOk+I2Wu3IC7k64gmf/ZbL9mYN/+v8TlYYyO +l8DQbO61XWOJpWt7yf18OxZtPcHH0dkoTEyIxIQcv6FDFNvPjmJzubpDgsfnly7R +C0Rc2yPU702vmAfF0SGQbd6KoXUqlfy26C85vU0Fqom1Qo22ehKrfU50vZrXdaJ2 +gX14pm2kuubMjHtX/+bhNyWTxq4anCOl9/aoObZCM1D3+Y8CAwEAAaNJMEcwCQYD +VR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGAYDVR0R +BBEwD4INREMwMS5vbWkudGVzdDA+BgkqhkiG9w0BAQowMaANMAsGCWCGSAFlAwQC +A6EaMBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgOiBAICAL4DggEBAHgTDTn8onIi +XFLZ3sWJ5xCBvXypqC37dKALvXxNSo75SQZpOioG4GSdW3zjJWCiudGs7BselkFv +sHK7+5sLKTl1RvxeUoyTxnPZZmVlD3yLq8JBPxu5NScvcRwAcgm3stkq0irRnh7M +4Clw6oSKCKI7Lc3gnbvR3QLSYHeZpUcQgVCad6O/Hi+vxFMJT8PVigG0YUoTW010 +pDpi5uh18RxCqRJnnEC7aDrVarxD9aAvqp1wqwWShfP4FZ9m57DH81RTGD2ZzGgP +MsZU5JHVYKkO7IKKIBKuLu+O+X2aZZ4OMlMNBt2DUIJGzEBYV41+3TST9bBPD8xt +AAIFCBcgUYY= +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_md5.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_md5.pem new file mode 100644 index 00000000000..6671b731ccf --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/rsa_md5.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIQJzshhViMG5hLHIJHxa+TcTANBgkqhkiG9w0 +BAQQFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD +MxNloXDTE4MDUzMDA4MjMxNlowFTETMBEGA1UEAwwKU0VSVkVSMjAxN +jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN9N5GAzI7uq +AVlI6vUqhY5+EZWCWWGRwR3FT2DEXE5++AiJxXO0i0ZfAkLu7UggtBe +QwVNkaPD27EYzVUhy1iDo37BrFcLNpfjsjj8wVjaSmQmqvLvrvEh/BT +C5SBgDrk2+hiMh9PrpJoB3QAMDinz5aW0rEXMKitPBBiADrczyYrliF +AlEU6pTlKEKDUAeP7dKOBlDbCYvBxKnR3ddVH74I5T2SmNBq5gzkbKP +nlCXdHLZSh74USu93rKDZQF8YzdTO5dcBreJDJsntyj1o49w9WCt6M7 ++pg6vKvE+tRbpCm7kXq5B9PDi42Nb6//MzNaMYf9V7v5MHapvVSv3+y +sCAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA +QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G +A1UdDgQWBBTh4L2Clr9ber6yfY3JFS3wiECL4DANBgkqhkiG9w0BAQQ +FAAOCAQEA0JK/SL7SP9/nvqWp52vnsxVefTFehThle5DLzagmms/9gu +oSE2I9XkQIttFMprPosaIZWt7WP42uGcZmoZOzU8kFFYJMfg9Ovyca+ +gnG28jDUMF1E74KrC7uynJiQJ4vPy8ne7F3XJ592LsNJmK577l42gAW +u08p3TvEJFNHy2dBk/IwZp0HIPr9+JcPf7v0uL6lK930xHJHP56XLzN +YG8vCMpJFR7wVZp3rXkJQUy3GxyHPJPjS8S43I9j+PoyioWIMEotq2+ +q0IpXU/KeNFkdGV6VPCmzhykijExOMwO6doUzIUM8orv9jYLHXYC+i6 +IFKSb6runxF1MAik+GCSA== +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha.pem new file mode 100644 index 00000000000..2ed2b45abe6 --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIQUDHcKGevZohJV+TkIIYC1DANBgkqhkiG9w0 +BAQ0FADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD +MxN1oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxN +jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKr9bo/XXvHt +D6Qnhb1wyLg9lDQxxe/enH49LQihtVTZMwGf2010h81QrRUe/bkHTvw +K22s2lqj3fUpGxtEbYFWLAHxv6IFnIKd+Zi1zaCPGfas9ekqCSj3vZQ +j7lCJVGUGuuqnSDvsed6g2Pz/g6mJUa+TzjxN+8wU5oj5YVUK+aing1 +zPSA2MDCfx3+YzjxVwNoGixOz6Yx9ijT4pUsAYQAf1o9R+6W1/IpGgu +oax714QILT9heqIowwlHzlUZc1UAYs0/JA4CbDZaw9hlJyzMqe/aE46 +efqPDOpO3vCpOSRcSyzh02WijPvEEaPejQRWg8RX93othZ615MT7dqp +ECAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA +QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G +A1UdDgQWBBTgod3R6vejt6kOASAApA19xIG6kTANBgkqhkiG9w0BAQ0 +FAAOCAQEAVfz0okK2bh3OQE8cWNbJ5PjJRSAJEqVUvYaTlS0Nqkyuaj +gicP3hb/pF8FvaVaB6r7LqgBxyW5NNL1xwdNLt60M2zaULL6Fhm1vzM +sSMc2ynkyN4++ODwii674YcQAnkUh+ZGIx+CTdZBWJfVM9dZb7QjgBT +nVukeFwN2EOOBSpiQSBpcoeJEEAq9csDVRhEfcB8Wtz7TTItgOVsilY +dQY56ON5XszjCki6UA3GwdQbBEHjWF2WERqXWrojrSSNOYDvxM5mrEx +sG1npzUTsaIr9w8ty1beh/2aToCMREvpiPFOXnVV/ovHMU1lFQTNeQ0 +OI7elR0nJ0peai30eMpQQ==' +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha1.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha1.pem new file mode 100644 index 00000000000..de21a677981 --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha1.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIQJg/Mf5sR55xApJRK+kabbTANBgkqhkiG9w0 +BAQUFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD +MxNloXDTE4MDUzMDA4MjMxNlowFTETMBEGA1UEAwwKU0VSVkVSMjAxN +jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPKwYikjbzL +Lo6JtS6cyytdMMjSrggDoTnRUKauC5/izoYJd+2YVR5YqnluBJZpoFp +hkCgFFohUOU7qUsI1SkuGnjI8RmWTrrDsSy62BrfX+AXkoPlXo6IpHz +HaEPxjHJdUACpn8QVWTPmdAhwTwQkeUutrm3EOVnKPX4bafNYeAyj7/ +AGEplgibuXT4/ehbzGKOkRN3ds/pZuf0xc4Q2+gtXn20tQIUt7t6iwh +nEWjIgopFL/hX/r5q5MpF6stc1XgIwJjEzqMp76w/HUQVqaYneU4qSG +f90ANK/TQ3aDbUNtMC/ULtIfHqHIW4POuBYXaWBsqalJL2VL3YYkKTU +sCAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA +QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G +A1UdDgQWBBS1jgojcjPu9vqeP1uSKuiIonGwAjANBgkqhkiG9w0BAQU +FAAOCAQEAKjHL6k5Dv/Zb7dvbYEZyx0wVhjHkCTpT3xstI3+TjfAFsu +3zMmyFqFqzmr4pWZ/rHc3ObD4pEa24kP9hfB8nmr8oHMLebGmvkzh5h +0GYc4dIH7Ky1yfQN51hi7/X5iN7jnnBoCJTTlgeBVYDOEBXhfXi3cLT +u3d7nz2heyNq07gFP8iN7MfqdPZndVDYY82imLgsgar9w5d+fvnYM+k +XWItNNCUH18M26Obp4Es/Qogo/E70uqkMHost2D+tww/7woXi36X3w/ +D2yBDyrJMJKZLmDgfpNIeCimncTOzi2IhzqJiOY/4XPsVN/Xqv0/dzG +TDdI11kPLq4EiwxvPanCg== +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha256.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha256.pem new file mode 100644 index 00000000000..fb170186089 --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha256.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIQWkeAtqoFg6pNWF7xC4YXhTANBgkqhkiG9w0 +BAQsFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUyNzA5MD +I0NFoXDTE4MDUyNzA5MjI0NFowFTETMBEGA1UEAwwKU0VSVkVSMjAxN +jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALIPKM5uykFy +NmVoLyvPSXGk15ZDqjYi3AbUxVFwCkVImqhefLATit3PkTUYFtAT+TC +AwK2E4lOu1XHM+Tmp2KIOnq2oUR8qMEvfxYThEf1MHxkctFljFssZ9N +vASDD4lzw8r0Bhl+E5PhR22Eu1Wago5bvIldojkwG+WBxPQv3ZR546L +MUZNaBXC0RhuGj5w83lbVz75qM98wvv1ekfZYAP7lrVyHxqCTPDomEU +I45tQQZHCZl5nRx1fPCyyYfcfqvFlLWD4Q3PZAbnw6mi0MiWJbGYKME +1XGicjqyn/zM9XKA1t/JzChS2bxf6rsyA9I7ibdRHUxsm1JgKry2jfW +0CAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA +QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G +A1UdDgQWBBQabLGWg1sn7AXPwYPyfE0ER921ZDANBgkqhkiG9w0BAQs +FAAOCAQEAnRohyl6ZmOsTWCtxOJx5A8yr//NweXKwWWmFQXRmCb4bMC +xhD4zqLDf5P6RotGV0I/SHvqz+pAtJuwmr+iyAF6WTzo3164LCfnQEu +psfrrfMkf3txgDwQkA0oPAw3HEwOnR+tzprw3Yg9x6UoZEhi4XqP9AX +R49jU92KrNXJcPlz5MbkzNo5t9nr2f8q39b5HBjaiBJxzdM1hxqsbfD +KirTYbkUgPlVOo/NDmopPPb8IX8ubj/XETZG2jixD0zahgcZ1vdr/iZ ++50WSXKN2TAKBO2fwoK+2/zIWrGRxJTARfQdF+fGKuj+AERIFNh88HW +xSDYjHQAaFMcfdUpa9GGQ== +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha384.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha384.pem new file mode 100644 index 00000000000..c17f9ff47bd --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha384.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIQEmj1prSSQYRL2zYBEjsm5jANBgkqhkiG9w0 +BAQwFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD +MxN1oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxN +jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKsK5NvHi4xO +081fRLMmPqKsKaHvXgPRykLA0SmKxpGJHfTAZzxojHVeVwOm87IvQj2 +JUh/yrRwSi5Oqrvqx29l2IC/qQt2xkAQsO51/EWkMQ5OSJsl1MN3NXW +eRTKVoUuJzBs8XLmeraxQcBPyyLhq+WpMl/Q4ZDn1FrUEZfxV0POXgU +dI3ApuQNRtJOb6iteBIoQyMlnof0RswBUnkiWCA/+/nzR0j33j47IfL +nkmU4RtqkBlO13f6+e1GZ4lEcQVI2yZq4Zgu5VVGAFU2lQZ3aEVMTu9 +8HEqD6heyNp2on5G/K/DCrGWYCBiASjnX3wiSz0BYv8f3HhCgIyVKhJ +8CAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA +QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G +A1UdDgQWBBQS/SI61S2UE8xwSgHxbkCTpZXo4TANBgkqhkiG9w0BAQw +FAAOCAQEAMVV/WMXd9w4jtDfSrIsKaWKGtHtiMPpAJibXmSakBRwLOn +5ZGXL2bWI/Ac2J2Y7bSzs1im2ifwmEqwzzqnpVKShIkZmtij0LS0SEr +6Fw5IrK8tD6SH+lMMXUTvp4/lLQlgRCwOWxry/YhQSnuprx8IfSPvil +kwZ0Ysim4Aa+X5ojlhHpWB53edX+lFrmR1YWValBnQ5DvnDyFyLR6II +Ialp4vmkzI9e3/eOgSArksizAhpXpC9dxQBiHXdhredN0X+1BVzbgzV +hQBEwgnAIPa+B68oDILaV0V8hvxrP6jFM4IrKoGS1cq0B+Ns0zkG7ZA +2Q0W+3nVwSxIr6bd6hw7g== +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha512.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha512.pem new file mode 100644 index 00000000000..2ed2b45abe6 --- /dev/null +++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha512.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIQUDHcKGevZohJV+TkIIYC1DANBgkqhkiG9w0 +BAQ0FADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD +MxN1oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxN +jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKr9bo/XXvHt +D6Qnhb1wyLg9lDQxxe/enH49LQihtVTZMwGf2010h81QrRUe/bkHTvw +K22s2lqj3fUpGxtEbYFWLAHxv6IFnIKd+Zi1zaCPGfas9ekqCSj3vZQ +j7lCJVGUGuuqnSDvsed6g2Pz/g6mJUa+TzjxN+8wU5oj5YVUK+aing1 +zPSA2MDCfx3+YzjxVwNoGixOz6Yx9ijT4pUsAYQAf1o9R+6W1/IpGgu +oax714QILT9heqIowwlHzlUZc1UAYs0/JA4CbDZaw9hlJyzMqe/aE46 +efqPDOpO3vCpOSRcSyzh02WijPvEEaPejQRWg8RX93othZ615MT7dqp +ECAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA +QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G +A1UdDgQWBBTgod3R6vejt6kOASAApA19xIG6kTANBgkqhkiG9w0BAQ0 +FAAOCAQEAVfz0okK2bh3OQE8cWNbJ5PjJRSAJEqVUvYaTlS0Nqkyuaj +gicP3hb/pF8FvaVaB6r7LqgBxyW5NNL1xwdNLt60M2zaULL6Fhm1vzM +sSMc2ynkyN4++ODwii674YcQAnkUh+ZGIx+CTdZBWJfVM9dZb7QjgBT +nVukeFwN2EOOBSpiQSBpcoeJEEAq9csDVRhEfcB8Wtz7TTItgOVsilY +dQY56ON5XszjCki6UA3GwdQbBEHjWF2WERqXWrojrSSNOYDvxM5mrEx +sG1npzUTsaIr9w8ty1beh/2aToCMREvpiPFOXnVV/ovHMU1lFQTNeQ0 +OI7elR0nJ0peai30eMpQQ==' +-----END CERTIFICATE----- diff --git a/test/units/module_utils/urls/test_channel_binding.py b/test/units/module_utils/urls/test_channel_binding.py new file mode 100644 index 00000000000..05b461e07a5 --- /dev/null +++ b/test/units/module_utils/urls/test_channel_binding.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# (c) 2020 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import base64 +import os.path +import pytest + +import ansible.module_utils.urls as urls + + +@pytest.mark.skipif(not urls.HAS_CRYPTOGRAPHY, reason='Requires cryptography to be installed') +@pytest.mark.parametrize('certificate, expected', [ + ('rsa_md5.pem', b'\x23\x34\xB8\x47\x6C\xBF\x4E\x6D' + b'\xFC\x76\x6A\x5D\x5A\x30\xD6\x64' + b'\x9C\x01\xBA\xE1\x66\x2A\x5C\x3A' + b'\x13\x02\xA9\x68\xD7\xC6\xB0\xF6'), + ('rsa_sha1.pem', b'\x14\xCF\xE8\xE4\xB3\x32\xB2\x0A' + b'\x34\x3F\xC8\x40\xB1\x8F\x9F\x6F' + b'\x78\x92\x6A\xFE\x7E\xC3\xE7\xB8' + b'\xE2\x89\x69\x61\x9B\x1E\x8F\x3E'), + ('rsa_sha256.pem', b'\x99\x6F\x3E\xEA\x81\x2C\x18\x70' + b'\xE3\x05\x49\xFF\x9B\x86\xCD\x87' + b'\xA8\x90\xB6\xD8\xDF\xDF\x4A\x81' + b'\xBE\xF9\x67\x59\x70\xDA\xDB\x26'), + ('rsa_sha384.pem', b'\x34\xF3\x03\xC9\x95\x28\x6F\x4B' + b'\x21\x4A\x9B\xA6\x43\x5B\x69\xB5' + b'\x1E\xCF\x37\x58\xEA\xBC\x2A\x14' + b'\xD7\xA4\x3F\xD2\x37\xDC\x2B\x1A' + b'\x1A\xD9\x11\x1C\x5C\x96\x5E\x10' + b'\x75\x07\xCB\x41\x98\xC0\x9F\xEC'), + ('rsa_sha512.pem', b'\x55\x6E\x1C\x17\x84\xE3\xB9\x57' + b'\x37\x0B\x7F\x54\x4F\x62\xC5\x33' + b'\xCB\x2C\xA5\xC1\xDA\xE0\x70\x6F' + b'\xAE\xF0\x05\x44\xE1\xAD\x2B\x76' + b'\xFF\x25\xCF\xBE\x69\xB1\xC4\xE6' + b'\x30\xC3\xBB\x02\x07\xDF\x11\x31' + b'\x4C\x67\x38\xBC\xAE\xD7\xE0\x71' + b'\xD7\xBF\xBF\x2C\x9D\xFA\xB8\x5D'), + ('rsa-pss_sha256.pem', b'\xF2\x31\xE6\xFF\x3F\x9E\x16\x1B' + b'\xC2\xDC\xBB\x89\x8D\x84\x47\x4E' + b'\x58\x9C\xD7\xC2\x7A\xDB\xEF\x8B' + b'\xD9\xC0\xC0\x68\xAF\x9C\x36\x6D'), + ('rsa-pss_sha512.pem', b'\x85\x85\x19\xB9\xE1\x0F\x23\xE2' + b'\x1D\x2C\xE9\xD5\x47\x2A\xAB\xCE' + b'\x42\x0F\xD1\x00\x75\x9C\x53\xA1' + b'\x7B\xB9\x79\x86\xB2\x59\x61\x27'), + ('ecdsa_sha256.pem', b'\xFE\xCF\x1B\x25\x85\x44\x99\x90' + b'\xD9\xE3\xB2\xC9\x2D\x3F\x59\x7E' + b'\xC8\x35\x4E\x12\x4E\xDA\x75\x1D' + b'\x94\x83\x7C\x2C\x89\xA2\xC1\x55'), + ('ecdsa_sha512.pem', b'\xE5\xCB\x68\xB2\xF8\x43\xD6\x3B' + b'\xF4\x0B\xCB\x20\x07\x60\x8F\x81' + b'\x97\x61\x83\x92\x78\x3F\x23\x30' + b'\xE5\xEF\x19\xA5\xBD\x8F\x0B\x2F' + b'\xAA\xC8\x61\x85\x5F\xBB\x63\xA2' + b'\x21\xCC\x46\xFC\x1E\x22\x6A\x07' + b'\x24\x11\xAF\x17\x5D\xDE\x47\x92' + b'\x81\xE0\x06\x87\x8B\x34\x80\x59'), +]) +def test_cbt_with_cert(certificate, expected): + with open(os.path.join(os.path.dirname(__file__), 'fixtures', 'cbt', certificate)) as fd: + cert_der = base64.b64decode("".join([l.strip() for l in fd.readlines()[1:-1]])) + + actual = urls.get_channel_binding_cert_hash(cert_der) + assert actual == expected + + +def test_cbt_no_cryptography(monkeypatch): + monkeypatch.setattr(urls, 'HAS_CRYPTOGRAPHY', False) + assert urls.get_channel_binding_cert_hash(None) is None