python-u2flib-server-5.0.0/0000775000175000017500000000000013067131574015403 5ustar daindain00000000000000python-u2flib-server-5.0.0/python_u2flib_server.egg-info/0000775000175000017500000000000013067131574023247 5ustar daindain00000000000000python-u2flib-server-5.0.0/python_u2flib_server.egg-info/dependency_links.txt0000664000175000017500000000000113067131574027315 0ustar daindain00000000000000 python-u2flib-server-5.0.0/python_u2flib_server.egg-info/requires.txt0000664000175000017500000000005213067131574025644 0ustar daindain00000000000000cryptography>=1.2 six [u2f_server] WebOb python-u2flib-server-5.0.0/python_u2flib_server.egg-info/PKG-INFO0000664000175000017500000000212513067131574024344 0ustar daindain00000000000000Metadata-Version: 1.1 Name: python-u2flib-server Version: 5.0.0 Summary: Python based U2F server library Home-page: https://github.com/Yubico/python-u2flib-server Author: Yubico Open Source Maintainers Author-email: ossmaint@yubico.com License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Internet Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules python-u2flib-server-5.0.0/python_u2flib_server.egg-info/top_level.txt0000664000175000017500000000001613067131574025776 0ustar daindain00000000000000u2flib_server python-u2flib-server-5.0.0/python_u2flib_server.egg-info/SOURCES.txt0000664000175000017500000000137713067131574025143 0ustar daindain00000000000000COPYING ChangeLog MANIFEST.in NEWS README release.py setup.cfg setup.py examples/u2f_server.py python_u2flib_server.egg-info/PKG-INFO python_u2flib_server.egg-info/SOURCES.txt python_u2flib_server.egg-info/dependency_links.txt python_u2flib_server.egg-info/requires.txt python_u2flib_server.egg-info/top_level.txt test/__init__.py test/soft_u2f_v2.py test/test_attestation.py test/test_matchers.py test/test_model.py test/test_u2f.py test/test_utils.py u2flib_server/__init__.py u2flib_server/model.py u2flib_server/u2f.py u2flib_server/utils.py u2flib_server/attestation/__init__.py u2flib_server/attestation/data.py u2flib_server/attestation/matchers.py u2flib_server/attestation/metadata.py u2flib_server/attestation/model.py u2flib_server/attestation/resolvers.pypython-u2flib-server-5.0.0/NEWS0000664000175000017500000000434713067131547016112 0ustar daindain00000000000000* Version 5.0.0 (released 2017-03-30) ** Major release: This release is NOT backwards compatible. ** Object model has been updated to reflect the 1.1 FIDO JavaScript API. ** Full Python 2.7 and 3.3+ support. ** Lots of things have been rewritten. * Version 4.0.1 (released 2016-04-05) ** Bugfix: Certificates sometimes failed to parse on Python 3. ** Bugfix: Example server wasn't properly updated for cryptography. * Version 4.0.0 (released 2016-03-01) ** Major release: Changed backend from M2Crypto to cryptography ** Added support for Python 3.3+ and PyPy 2.6+ ** Added support for reading transport information from attestation certificates, as well as metadata statements. ** Added dependency on cryptography 1.2 or higher ** Added dependency on enum34 on python versions < 3.4 ** Removed dependency on M2Crypto, pyasn1, and pyasn1-modules ** utils.rand_bytes() now sources bytes from os.urandom() ** utils.websafe_encode(), u2f_v2.RawRegistrationResponse.serialize(), and u2f_v2.RawAuthenticationResponse.serialize() now all return text strings (unicode() on Python 2.x, str() on Python 3.x) * Version 3.2.0 (released 2015-06-16) ** License change release: Changed license to BSD 2-clause. * Version 3.1.2 (released 2015-06-02) ** Bugfix release: Attestation data reading was still broken in 3.1.1. * Version 3.1.1 (released 2015-06-02) ** Fix reading attestation data from directories. * Version 3.1.0 (released 2015-02-02) ** Added U2F high-level API. ** Added attestation and device metadata support. * Version 3.0.1 (released 2015-01-14) ** Fix JSONDict not pickling. ** Set 0 unused bits in attestation certificate signatures with known unused bits. * Version 3.0.0 (released 2014-10-09) ** Complete API rewrite to simplify library significantly. * Version 2.0.0 (released 2014-09-26) ** Updated to the latest U2F_V2 standard. ** Expose more low level U2F primitives. ** Removed old draft versions. * Version 1.0.0 (released 2014-02-18) ** First public release! ** Added support for U2F V2. ** Added u2f_server.py example, for a fully stand-alone example. * Version 0.0.2 (unreleased) ** Allow example server to be used for multiple origins. * Version 0.0.1 (released 2013-08-22) ** Initial internal release! python-u2flib-server-5.0.0/examples/0000775000175000017500000000000013067131574017221 5ustar daindain00000000000000python-u2flib-server-5.0.0/examples/u2f_server.py0000775000175000017500000001464713066466174021702 0ustar daindain00000000000000#!/usr/bin/env python # Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. """ Example web server providing single factor U2F enrollment and authentication. It is intended to be run standalone in a single process, and stores user data in memory only, with no permanent storage. Enrollment will overwrite existing users. If username is omitted, a default value of "user" will be used. Any error will be returned as a stacktrace with a 400 response code. Note that this is intended for test/demo purposes, not production use! This example requires webob to be installed. """ from u2flib_server.u2f import (begin_registration, begin_authentication, complete_registration, complete_authentication) from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.serialization import Encoding from webob.dec import wsgify from webob import exc import logging as log import json import traceback import argparse def get_origin(environ): if environ.get('HTTP_HOST'): host = environ['HTTP_HOST'] else: host = environ['SERVER_NAME'] if environ['wsgi.url_scheme'] == 'https': if environ['SERVER_PORT'] != '443': host += ':' + environ['SERVER_PORT'] else: if environ['SERVER_PORT'] != '80': host += ':' + environ['SERVER_PORT'] return '%s://%s' % (environ['wsgi.url_scheme'], host) class U2FServer(object): """ Very basic server providing a REST API to enroll one or more U2F device with a user, and to perform authentication with the enrolled devices. Only one challenge is valid at a time. Four calls are provided: enroll, bind, sign and verify. Each of these expects a username parameter, and bind and verify expect a second parameter, data, containing the JSON formatted data which is output by the U2F browser API upon calling the ENROLL or SIGN commands. """ def __init__(self): self.users = {} @wsgify def __call__(self, request): self.facet = get_origin(request.environ) self.app_id = self.facet page = request.path_info_pop() if not page: return json.dumps([self.facet]) try: username = request.params.get('username', 'user') data = request.params.get('data', None) if page == 'enroll': return self.enroll(username) elif page == 'bind': return self.bind(username, data) elif page == 'sign': return self.sign(username) elif page == 'verify': return self.verify(username, data) else: raise exc.HTTPNotFound() except Exception: log.exception("Exception in call to '%s'", page) return exc.HTTPBadRequest(comment=traceback.format_exc()) def enroll(self, username): if username not in self.users: self.users[username] = {} user = self.users[username] enroll = begin_registration(self.app_id, user.get('_u2f_devices_', [])) user['_u2f_enroll_'] = enroll.json return json.dumps(enroll.data_for_client) def bind(self, username, data): user = self.users[username] enroll = user.pop('_u2f_enroll_') device, cert = complete_registration(enroll, data, [self.facet]) user.setdefault('_u2f_devices_', []).append(device.json) log.info("U2F device enrolled. Username: %s", username) cert = x509.load_der_x509_certificate(cert, default_backend()) log.debug("Attestation certificate:\n%s", cert.public_bytes(Encoding.PEM)) return json.dumps(True) def sign(self, username): user = self.users[username] challenge = begin_authentication( self.app_id, user.get('_u2f_devices_', [])) user['_u2f_challenge_'] = challenge.json return json.dumps(challenge.data_for_client) def verify(self, username, data): user = self.users[username] challenge = user.pop('_u2f_challenge_') device, c, t = complete_authentication(challenge, data, [self.facet]) return json.dumps({ 'keyHandle': device['keyHandle'], 'touch': t, 'counter': c }) application = U2FServer() if __name__ == '__main__': from wsgiref.simple_server import make_server parser = argparse.ArgumentParser( description='U2F test server', add_help=True, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser.add_argument('-i', '--interface', nargs='?', default='localhost', help='network interface to bind to') parser.add_argument('-p', '--port', nargs='?', type=int, default=8081, help='TCP port to bind to') args = parser.parse_args() log.basicConfig(level=log.DEBUG, format='%(asctime)s %(message)s', datefmt='[%d/%b/%Y %H:%M:%S]') log.info("Starting server on http://%s:%d", args.interface, args.port) httpd = make_server(args.interface, args.port, application) httpd.serve_forever() python-u2flib-server-5.0.0/release.py0000664000175000017500000001542613067130165017400 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from __future__ import absolute_import from setuptools import setup as _setup, find_packages, Command from setuptools.command.sdist import sdist from distutils import log from distutils.errors import DistutilsSetupError from datetime import date from glob import glob import os import re VERSION_PATTERN = re.compile(r"(?m)^__version__\s*=\s*['\"](.+)['\"]$") base_module = __name__.rsplit('.', 1)[0] def get_version(module_name_or_file=None): """Return the current version as defined by the given module/file.""" if module_name_or_file is None: parts = base_module.split('.') module_name_or_file = parts[0] if len(parts) > 1 else \ find_packages(exclude=['test', 'test.*'])[0] if os.path.isdir(module_name_or_file): module_name_or_file = os.path.join(module_name_or_file, '__init__.py') with open(module_name_or_file, 'r') as f: match = VERSION_PATTERN.search(f.read()) return match.group(1) def setup(**kwargs): if 'version' not in kwargs: kwargs['version'] = get_version() kwargs.setdefault('packages', find_packages(exclude=['test', 'test.*'])) cmdclass = kwargs.setdefault('cmdclass', {}) cmdclass.setdefault('release', release) cmdclass.setdefault('build_man', build_man) cmdclass.setdefault('sdist', custom_sdist) return _setup(**kwargs) class custom_sdist(sdist): def run(self): self.run_command('build_man') sdist.run(self) class build_man(Command): description = "create man pages from asciidoc source" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): self.cwd = os.getcwd() self.fullname = self.distribution.get_fullname() self.name = self.distribution.get_name() self.version = self.distribution.get_version() def run(self): if os.getcwd() != self.cwd: raise DistutilsSetupError("Must be in package root!") for fname in glob(os.path.join('man', '*.adoc')): self.announce("Converting: " + fname, log.INFO) self.execute(os.system, ('a2x -d manpage -f manpage "%s"' % fname,)) class release(Command): description = "create and release a new version" user_options = [ ('keyid', None, "GPG key to sign with"), ('skip-tests', None, "skip running the tests"), ('pypi', None, "publish to pypi"), ] boolean_options = ['skip-tests', 'pypi'] def initialize_options(self): self.keyid = None self.skip_tests = 0 self.pypi = 0 def finalize_options(self): self.cwd = os.getcwd() self.fullname = self.distribution.get_fullname() self.name = self.distribution.get_name() self.version = self.distribution.get_version() def _verify_version(self): with open('NEWS', 'r') as news_file: line = news_file.readline() now = date.today().strftime('%Y-%m-%d') if not re.search(r'Version %s \(released %s\)' % (self.version, now), line): raise DistutilsSetupError("Incorrect date/version in NEWS!") def _verify_tag(self): if os.system('git tag | grep -q "^%s\$"' % self.fullname) == 0: raise DistutilsSetupError( "Tag '%s' already exists!" % self.fullname) def _verify_not_dirty(self): if os.system('git diff --shortstat | grep -q "."') == 0: raise DistutilsSetupError("Git has uncommitted changes!") def _sign(self): if os.path.isfile('dist/%s.tar.gz.asc' % self.fullname): # Signature exists from upload, re-use it: sign_opts = ['--output dist/%s.tar.gz.sig' % self.fullname, '--dearmor dist/%s.tar.gz.asc' % self.fullname] else: # No signature, create it: sign_opts = ['--detach-sign', 'dist/%s.tar.gz' % self.fullname] if self.keyid: sign_opts.insert(1, '--default-key ' + self.keyid) self.execute(os.system, ('gpg ' + (' '.join(sign_opts)),)) if os.system('gpg --verify dist/%s.tar.gz.sig' % self.fullname) != 0: raise DistutilsSetupError("Error verifying signature!") def _tag(self): tag_opts = ['-s', '-m ' + self.fullname, self.fullname] if self.keyid: tag_opts[0] = '-u ' + self.keyid self.execute(os.system, ('git tag ' + (' '.join(tag_opts)),)) def run(self): if os.getcwd() != self.cwd: raise DistutilsSetupError("Must be in package root!") self._verify_version() self._verify_tag() self._verify_not_dirty() self.run_command('check') self.execute(os.system, ('git2cl > ChangeLog',)) self.run_command('sdist') if not self.skip_tests: try: self.run_command('test') except SystemExit as e: if e.code != 0: raise DistutilsSetupError("There were test failures!") if self.pypi: cmd_obj = self.distribution.get_command_obj('upload') cmd_obj.sign = True if self.keyid: cmd_obj.identity = self.keyid self.run_command('upload') self._sign() self._tag() self.announce("Release complete! Don't forget to:", log.INFO) self.announce("") self.announce(" git push && git push --tags", log.INFO) self.announce("") python-u2flib-server-5.0.0/test/0000775000175000017500000000000013067131574016362 5ustar daindain00000000000000python-u2flib-server-5.0.0/test/__init__.py0000664000175000017500000000000013040135032020441 0ustar daindain00000000000000python-u2flib-server-5.0.0/test/test_attestation.py0000664000175000017500000001462513066466174022350 0ustar daindain00000000000000from u2flib_server.model import Transport from u2flib_server.attestation.metadata import MetadataProvider from u2flib_server.attestation.resolvers import create_resolver from u2flib_server.attestation.data import YUBICO from u2flib_server.attestation.model import ( VendorInfo, Selector, DeviceInfo, MetadataObject ) from cryptography import x509 from cryptography.hazmat.backends import default_backend from base64 import b64decode import json import unittest ATTESTATION_CERT = b64decode(b""" MIICGzCCAQWgAwIBAgIEdaP2dTALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBS b290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBa MCoxKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE5NzM2Nzk3MzMwWTATBgcqhkjOPQIB BggqhkjOPQMBBwNCAAQZo35Damtpl81YdmcbhEuXKAr7xDcQzAy5n3ftAAhtBbu8EeGU4ynfSgLo nckqX6J2uXLBppTNE3v2bt+Yf8MLoxIwEDAOBgorBgEEAYLECgECBAAwCwYJKoZIhvcNAQELA4IB AQC9LbiNPgs0sQYOHAJcg+lMk+HCsiWRlYVnbT4I/5lnqU907vY17XYAORd432bU3Nnhsbkvjz76 kQJGXeNAF4DPANGGlz8JU+LNEVE2PWPGgEM0GXgB7mZN5Sinfy1AoOdO+3c3bfdJQuXlUxHbo+nD pxxKpzq9gr++RbokF1+0JBkMbaA/qLYL4WdhY5NvaOyMvYpO3sBxlzn6FcP67hlotGH1wU7qhCeh +uur7zDeAWVh7c4QtJOXHkLJQfV3Z7ZMvhkIA6jZJAX99hisABU/SSa5DtgX7AfsHwa04h69AAAW DUzSk3HgOXbUd1FaSOPdlVFkG2N2JllFHykyO3zO """) ATTESTATION_CERT_WITH_TRANSPORT = b64decode(b""" MIICIjCCAQygAwIBAgIEIHHwozALBgkqhkiG9w0BAQswDzENMAsGA1UEAxMEdGVz dDAeFw0xNTA4MTEwOTAwMzNaFw0xNjA4MTAwOTAwMzNaMCkxJzAlBgNVBAMTHll1 YmljbyBVMkYgRUUgU2VyaWFsIDU0NDMzODA4MzBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABPdFG1pBjBBQVhLrD39Qg1vKjuR2kRdBZnwLI/zgzztQpf4ffpkrkB/3 E0TXj5zg8gN9sgMkX48geBe+tBEpvMmjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYu MS40LjEuNDE0ODIuMS4yMBMGCysGAQQBguUcAgEBBAQDAgQwMAsGCSqGSIb3DQEB CwOCAQEAb3YpnmHHduNuWEXlLqlnww9034ZeZaojhPAYSLR8d5NPk9gc0hkjQKmI aaBM7DsaHbcHMKpXoMGTQSC++NCZTcKvZ0Lt12mp5HRnM1NNBPol8Hte5fLmvW4t Q9EzLl4gkz7LSlORxTuwTbae1eQqNdxdeB+0ilMFCEUc+3NGCNM0RWd+sP5+gzMX BDQAI1Sc9XaPIg8t3du5JChAl1ifpu/uERZ2WQgtxeBDO6z1Xoa5qz4svf5oURjP ZjxS0WUKht48Z2rIjk5lZzERSaY3RrX3UtrnZEIzCmInXOrcRPeAD4ZutpiwuHe6 2ABsjuMRnKbATbOUiLdknNyPYYQz2g== """) ATTESTATION_CERT_WITH_KEY_VALUE_IDENTIFIER = b64decode(b""" MIICQzCCAS2gAwIBAgIEF/DtRjALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXVi aWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAw WhgPMjA1MDA5MDQwMDAwMDBaMCkxJzAlBgNVBAMMHll1YmljbyBVMkYgRUUgU2Vy aWFsIDQwMTY2NTM1MDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEPqOukfQCYH iqB+oNxSP1l5r3cVBcv/0wK3iW55eORN9qygi0DMtp5ypZWbNVX2V9d+hX/6UC4V K9gQdAuvUaWjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4y MBMGCysGAQQBguUcAgEBBAQDAgQwMAsGCSqGSIb3DQEBCwOCAQEAL8JwapHJJDrk N+UvUyTQx0rgKncEMESW3heCgQxBt2TR3W9QHEuRi9RX8whiIggGFaTL2kGUoRMY r0QttOD5Su8o+MywhXbNHhe1Ohh/YeiTcWZy1xnFwQApOud5M5BwZ+y7yyKbOFPv udCtsNIAULuRzPgdXr/113NDAw+FlsJbGNUnS/8PzhUPo6Oblgg/7Lq5kviKnLuV ZWZ7Vsz3SKUnhc5xho+3aRsweu+n0LEDos4IBAdIpFprq/Eqoo5azXDBQJb6tHjQ M1jUQwru/G+mndWp8KwBCnGp6kA64eAWxD3pfT/xrOhbfeB2D8ZHyxTxXmjCXcAE jHl3VfEmFQ== """) YUBICO_RESOLVER = create_resolver(YUBICO) EMPTY_RESOLVER = create_resolver([]) class AttestationTest(unittest.TestCase): def test_resolver(self): metadata = YUBICO_RESOLVER.resolve(ATTESTATION_CERT) self.assertEqual(metadata.identifier, '2fb54029-7613-4f1d-94f1-fb876c14a6fe') def test_provider(self): provider = MetadataProvider(YUBICO_RESOLVER) attestation = provider.get_attestation(ATTESTATION_CERT) self.assertTrue(attestation.trusted) def test_device_info_from_empty_oid(self): provider = MetadataProvider(YUBICO_RESOLVER) attestation = provider.get_attestation(ATTESTATION_CERT) self.assertEqual(attestation.device_info['deviceId'], '1.3.6.1.4.1.41482.1.2') def test_device_info_from_key_value_oid(self): provider = MetadataProvider(YUBICO_RESOLVER) attestation = provider.get_attestation( ATTESTATION_CERT_WITH_KEY_VALUE_IDENTIFIER) self.assertEqual(attestation.device_info['deviceId'], '1.3.6.1.4.1.41482.1.2') def test_versioning_newer(self): resolver = create_resolver(YUBICO) newer = json.loads(json.dumps(YUBICO)) newer['version'] = newer['version'] + 1 newer['trustedCertificates'] = [] resolver.add_metadata(newer) metadata = resolver.resolve(ATTESTATION_CERT) self.assertIsNone(metadata) def test_versioning_older(self): resolver = create_resolver(YUBICO) newer = json.loads(json.dumps(YUBICO)) newer['trustedCertificates'] = [] resolver.add_metadata(newer) metadata = resolver.resolve(ATTESTATION_CERT) self.assertEqual(metadata.identifier, '2fb54029-7613-4f1d-94f1-fb876c14a6fe') def test_transports_from_cert(self): provider = MetadataProvider(EMPTY_RESOLVER) attestation = provider.get_attestation(ATTESTATION_CERT_WITH_TRANSPORT) self.assertSetEqual(set(attestation.transports), set([Transport.USB, Transport.NFC])) def test_transports_from_metadata(self): provider = MetadataProvider(YUBICO_RESOLVER) cert = x509.load_der_x509_certificate(ATTESTATION_CERT, default_backend()) attestation = provider.get_attestation(cert) self.assertEqual(attestation.transports, [Transport.USB]) class DeviceInfoTest(unittest.TestCase): def test_selectors_empty(self): self.assertTrue(DeviceInfo().selectors is None) def test_selectors(self): devinfo = DeviceInfo(selectors=[{}, {'a': 1}, {'a': 1, 'b': 2}]) self.assertEqual([{}, {'a': 1}, {'a': 1, 'b': 2}], devinfo.selectors) self.assertTrue(isinstance(devinfo.selectors[0], Selector)) self.assertTrue(isinstance(devinfo.selectors[1], Selector)) self.assertTrue(isinstance(devinfo.selectors[2], Selector)) class MetadataObjectTest(unittest.TestCase): def test_vendorinfo(self): metadata = MetadataObject(vendorInfo={}) self.assertEqual({}, metadata.vendorInfo) self.assertTrue(isinstance(metadata.vendorInfo, VendorInfo)) def test_devices(self): metadata = MetadataObject(devices=[{}, {'a': 1}, {'a': 1, 'b': 2}]) self.assertEqual([{}, {'a': 1}, {'a': 1, 'b': 2}], metadata.devices) self.assertTrue(isinstance(metadata.devices[0], DeviceInfo)) self.assertTrue(isinstance(metadata.devices[1], DeviceInfo)) self.assertTrue(isinstance(metadata.devices[2], DeviceInfo)) python-u2flib-server-5.0.0/test/test_matchers.py0000664000175000017500000000336713066466174021620 0ustar daindain00000000000000import unittest from cryptography import x509 from cryptography.hazmat.backends import default_backend from u2flib_server.attestation.matchers import _get_ext_by_oid YUBICO_ATTESTATION_CERT_SERIAL_544338083 = b'''-----BEGIN CERTIFICATE----- MIICIjCCAQygAwIBAgIEIHHwozALBgkqhkiG9w0BAQswDzENMAsGA1UEAxMEdGVz dDAeFw0xNTA4MTEwOTAwMzNaFw0xNjA4MTAwOTAwMzNaMCkxJzAlBgNVBAMTHll1 YmljbyBVMkYgRUUgU2VyaWFsIDU0NDMzODA4MzBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABPdFG1pBjBBQVhLrD39Qg1vKjuR2kRdBZnwLI/zgzztQpf4ffpkrkB/3 E0TXj5zg8gN9sgMkX48geBe+tBEpvMmjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYu MS40LjEuNDE0ODIuMS4yMBMGCysGAQQBguUcAgEBBAQDAgQwMAsGCSqGSIb3DQEB CwOCAQEAb3YpnmHHduNuWEXlLqlnww9034ZeZaojhPAYSLR8d5NPk9gc0hkjQKmI aaBM7DsaHbcHMKpXoMGTQSC++NCZTcKvZ0Lt12mp5HRnM1NNBPol8Hte5fLmvW4t Q9EzLl4gkz7LSlORxTuwTbae1eQqNdxdeB+0ilMFCEUc+3NGCNM0RWd+sP5+gzMX BDQAI1Sc9XaPIg8t3du5JChAl1ifpu/uERZ2WQgtxeBDO6z1Xoa5qz4svf5oURjP ZjxS0WUKht48Z2rIjk5lZzERSaY3RrX3UtrnZEIzCmInXOrcRPeAD4ZutpiwuHe6 2ABsjuMRnKbATbOUiLdknNyPYYQz2g== -----END CERTIFICATE-----''' # From https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers # Regsitered number Enterprise # 1.3.6.1.4.1.41482 Yubico # 1.3.6.1.4.1.45724 FIDO Alliance, Inc. class X509ExtensionsTest(unittest.TestCase): attestation_cert = x509.load_pem_x509_certificate( YUBICO_ATTESTATION_CERT_SERIAL_544338083, default_backend(), ) def test_get_ext_by_oid_yubico(self): self.assertEqual( b'1.3.6.1.4.1.41482.1.2', _get_ext_by_oid(self.attestation_cert, '1.3.6.1.4.1.41482.2'), ) def test_get_ext_by_oid_fido_alliance(self): self.assertEqual( b'\x03\x02\x040', _get_ext_by_oid(self.attestation_cert, '1.3.6.1.4.1.45724.2.1.1'), ) python-u2flib-server-5.0.0/test/test_utils.py0000664000175000017500000000262113066466174021142 0ustar daindain00000000000000# coding=utf-8 import unittest from u2flib_server.utils import websafe_encode, websafe_decode class TestWebSafe(unittest.TestCase): # Base64 vectors adapted from https://tools.ietf.org/html/rfc4648#section-10 def test_websafe_decode(self): self.assertEqual(websafe_decode(b''), b'') self.assertEqual(websafe_decode(b'Zg'), b'f') self.assertEqual(websafe_decode(b'Zm8'), b'fo') self.assertEqual(websafe_decode(b'Zm9v'), b'foo') self.assertEqual(websafe_decode(b'Zm9vYg'), b'foob') self.assertEqual(websafe_decode(b'Zm9vYmE'), b'fooba') self.assertEqual(websafe_decode(b'Zm9vYmFy'), b'foobar') def test_websafe_decode_unicode(self): self.assertEqual(websafe_decode(u''), b'') self.assertEqual(websafe_decode(u'Zm9vYmFy'), b'foobar') def test_websafe_encode(self): self.assertEqual(websafe_encode(b''), u'') self.assertEqual(websafe_encode(b'f'), u'Zg') self.assertEqual(websafe_encode(b'fo'), u'Zm8') self.assertEqual(websafe_encode(b'foo'), u'Zm9v') self.assertEqual(websafe_encode(b'foob'), u'Zm9vYg') self.assertEqual(websafe_encode(b'fooba'), u'Zm9vYmE') self.assertEqual(websafe_encode(b'foobar'), u'Zm9vYmFy') def test_websafe_encode_unicode(self): self.assertEqual(websafe_encode(u''), u'') self.assertEqual(websafe_encode(u'foobar'), u'Zm9vYmFy') python-u2flib-server-5.0.0/test/soft_u2f_v2.py0000664000175000017500000001431013066466174021077 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from u2flib_server.utils import websafe_encode, sha_256 from u2flib_server.model import (RegisterRequest, RegisterResponse, SignResponse, ClientData, Type, RegisteredKey) from base64 import b64decode import six import struct import os from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import PublicFormat, Encoding from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.hazmat.primitives import hashes CURVE = ec.SECP256R1 CERT = b64decode(b""" MIIBhzCCAS6gAwIBAgIJAJm+6LEMouwcMAkGByqGSM49BAEwITEfMB0GA1UEAwwW WXViaWNvIFUyRiBTb2Z0IERldmljZTAeFw0xMzA3MTcxNDIxMDNaFw0xNjA3MTYx NDIxMDNaMCExHzAdBgNVBAMMFll1YmljbyBVMkYgU29mdCBEZXZpY2UwWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAAQ74Zfdc36YPZ+w3gnnXEPIBl1J3pol6IviRAMc /hCIZFbDDwMs4bSWeFdwqjGfjDlICArdmjMWnDF/XCGvHYEto1AwTjAdBgNVHQ4E FgQUDai/k1dOImjupkubYxhOkoX3sZ4wHwYDVR0jBBgwFoAUDai/k1dOImjupkub YxhOkoX3sZ4wDAYDVR0TBAUwAwEB/zAJBgcqhkjOPQQBA0gAMEUCIFyVmXW7zlnY VWhuyCbZ+OKNtSpovBB7A5OHAH52dK9/AiEA+mT4tz5eJV8W2OwVxcq6ZIjrwqXc jXSy2G0k27yAUDk= """) CERT_PRIV = b""" -----BEGIN EC PRIVATE KEY----- MHcCAQEEIMyk3gKcDg5lsYdl48fZoIFORhAc9cQxmn2Whv/+ya+2oAoGCCqGSM49 AwEHoUQDQgAEO+GX3XN+mD2fsN4J51xDyAZdSd6aJeiL4kQDHP4QiGRWww8DLOG0 lnhXcKoxn4w5SAgK3ZozFpwxf1whrx2BLQ== -----END EC PRIVATE KEY----- """ class SoftU2FDevice(object): """ This simulates the U2F browser API with a soft U2F device connected. It can be used for testing. """ def __init__(self): self.keys = {} self.counter = 0 def register(self, facet, app_id, request): """ RegisterRequest = { "version": "U2F_V2", "challenge": string, //b64 encoded challenge "appId": string, //app_id } """ if not isinstance(request, RegisterRequest): request = RegisterRequest(request) if request.version != "U2F_V2": raise ValueError("Unsupported U2F version: %s" % request.version) # Client data client_data = ClientData( typ=Type.REGISTER.value, challenge=request['challenge'], origin=facet ) client_data = client_data.json.encode('utf-8') client_param = sha_256(client_data) # ECC key generation priv_key = ec.generate_private_key(CURVE, default_backend()) pub_key = priv_key.public_key().public_bytes( Encoding.DER, PublicFormat.SubjectPublicKeyInfo) pub_key = pub_key[-65:] # Store key_handle = os.urandom(64) app_param = sha_256(app_id.encode('idna')) self.keys[key_handle] = (priv_key, app_param) # Attestation signature cert_priv = load_pem_private_key( CERT_PRIV, password=None, backend=default_backend()) cert = CERT data = b'\x00' + app_param + client_param + key_handle + pub_key signer = cert_priv.signer(ec.ECDSA(hashes.SHA256())) signer.update(data) signature = signer.finalize() raw_response = (b'\x05' + pub_key + six.int2byte(len(key_handle)) + key_handle + cert + signature) return RegisterResponse( version=request.version, registrationData=websafe_encode(raw_response), clientData=websafe_encode(client_data), ) def getAssertion(self, facet, app_id, challenge, key, touch_byte=1): """ signData = { 'version': "U2F_V2", 'challenge': websafe_encode(self.challenge), 'appId': self.binding.app_id, 'keyHandle': websafe_encode(self.binding.key_handle), } """ key = RegisteredKey.wrap(key) if key.version != "U2F_V2": raise ValueError("Unsupported U2F version: %s" % key.version) if key.keyHandle not in self.keys: raise ValueError("Unknown key handle!") # Client data client_data = ClientData( typ=Type.SIGN.value, challenge=challenge, origin=facet ) client_data = client_data.json.encode('utf-8') client_param = sha_256(client_data) # Unwrap: priv_key, app_param = self.keys[key.keyHandle] # Increment counter self.counter += 1 # Create signature touch = six.int2byte(touch_byte) counter = struct.pack('>I', self.counter) data = app_param + touch + counter + client_param signer = priv_key.signer(ec.ECDSA(hashes.SHA256())) signer.update(data) signature = signer.finalize() raw_response = touch + counter + signature return SignResponse( clientData=websafe_encode(client_data), signatureData=websafe_encode(raw_response), keyHandle=key['keyHandle'] ) python-u2flib-server-5.0.0/test/test_u2f.py0000664000175000017500000001767113066737637020516 0ustar daindain00000000000000from u2flib_server.u2f import (begin_registration, complete_registration, begin_authentication, complete_authentication) from u2flib_server.model import (U2fRegisterRequest, U2fSignRequest, RegisterResponse) from u2flib_server.utils import websafe_decode, websafe_encode from .soft_u2f_v2 import SoftU2FDevice import unittest import six APP_ID = 'https://www.example.com' APP_ID = 'http://www.example.com/appid' FACET = 'http://www.example.com' FACETS = [FACET] def register_token(devices=[]): token = SoftU2FDevice() request = begin_registration(APP_ID, devices) data = request.data_for_client response = token.register(FACET, data['appId'], data['registerRequests'][0]) device, cert = complete_registration(request.json, response) return device, token class U2fTest(unittest.TestCase): def test_register_fixed_values(self): req = U2fRegisterRequest.create( 'http://localhost:8081', [], websafe_decode('KEzvDDdHwnXtPHIMb0Uh43hgOJ-wQTsdLujGkeg6JxM') ) reg, cert = req.complete({ "version": "U2F_V2", "registrationData": "BQS94xQL46G4vheJPkYSuEteM6Km4-MwgBAu1zZ6MAbjDD" "gqhYbpHuIhhGOKjedeDd58qqktqOJsby9wMdHGnUtVQD8ISPywVi3J6SaKebCVQdHP" "u3_zQigRS8LhoDwKT5Ed3tg8AWuNw9XBZEh4doEDxKGuInFazirUw8acOu2qDcEwgg" "IjMIIBDaADAgECAgRyuHt0MAsGCSqGSIb3DQEBCzAPMQ0wCwYDVQQDEwR0ZXN0MB4X" "DTE1MDkwNDA3MTAyNloXDTE2MDkwMzA3MTAyNlowKjEoMCYGA1UEAxMfWXViaWNvIF" "UyRiBFRSBTZXJpYWwgMTkyNDY5Mjg1MjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA" "BC37i_h-xmEtGfWnuvj_BmuhtU18MKShNP_vZ7C2WJwj8OHaSLnzAfha14CMUPaKPt" "RFfP6w9CFGhvEizH33XZKjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0" "ODIuMS4yMBMGCysGAQQBguUcAgEBBAQDAgQwMAsGCSqGSIb3DQEBCwOCAQEAab7fWl" "J-lOR1sqIxawPU5DWZ1b9nQ0QmNNoetPHJ_fJC95r0esRq5axfmGufbNktNWanHww7" "i9n5WWxSaMTWuJSF0eAXUajo8odYA8nB4_0I6z615MWa9hTU64Pl9HlqkR5ez5jndm" "JNuAfhaIF4h062Jw051kMo_aENxuLixnybTfJG7Q5KRE00o2MFs5b9L9fzhDtBzv5Z" "-vGOefuiohowpwnxIA9l0tGqrum9plUdx06K9TqKMRDQ8naosy01rbouA6i5xVjl-t" "HT3z-r__FYcSZ_dQ5-SCPOh4F0w6T0UwzymQmeqYN3pP-UUgnJ-ihD-uhEWklKNYRy" "0K0G0jBGAiEA7rbbx2jwC1YGICkZMR07ggKWaHCwFBxNDW3OwhLNNzUCIQCSq0sjGS" "UnWMQgPEImrmd3tMKcbrjI995rti6UYozqsg", "clientData": "eyJvcmlnaW4iOiAiaHR0cDovL2xvY2FsaG9zdDo4MDgxIiwgImNo" "YWxsZW5nZSI6ICJLRXp2RERkSHduWHRQSElNYjBVaDQzaGdPSi13UVRzZEx1akdrZW" "c2SnhNIiwgInR5cCI6ICJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCJ9" }) assert reg def test_authenticate_fixed_values(self): device = { 'version': 'U2F_V2', 'publicKey': 'BBCcnAOknoMgokEGuTdfpNLQ-uylwlKp_xbEW8urjJsXKv9XZSL-V' '8C2nwcPEckav1mKZFr5K96uAoLtuxOUf-E', 'keyHandle': 'BIarIKfyMqyf4bEI6tOqGInAfHrrQkMA2eyPJlNnInbAG1tXNpdRs' '48ef92_b1-mfN4VhaTWxo1SGoxT6CIanw', 'appId': 'http://www.example.com/appid' } response = { 'keyHandle': 'BIarIKfyMqyf4bEI6tOqGInAfHrrQkMA2eyPJlNnInbAG1tXNpdRs' '48ef92_b1-mfN4VhaTWxo1SGoxT6CIanw', 'signatureData': 'AAAAAAEwRQIhAJrcBSpaDprFzXmVw60r6x-_gOZ0t-8v7DGii' 'Kmar0SAAiAYKKEX41nWUCLLoKiBYuHYdPP1MPPNQ0cX_JIybPtThA', 'clientData': 'eyJvcmlnaW4iOiAiaHR0cHM6Ly93d3cuZXhhbXBsZS5jb20iLCAi' 'Y2hhbGxlbmdlIjogIm9JZXUtblB4eDlEY0Y3TF9EQ0Uza3ZZb3gtYzRVdXZGYjhsTk' 'c2dGgxMG8iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24ifQ' } req = U2fSignRequest.create( 'http://www.example.com/appid', [device], websafe_decode('oIeu-nPxx9DcF7L_DCE3kvYox-c4UuvFb8lNG6th10o') ) req.complete(response) def test_register_soft_u2f(self): device, token = register_token() assert device def test_authenticate_single_soft_u2f(self): # Register device, token = register_token() # Authenticate request = begin_authentication(APP_ID, [device]) data = request.data_for_client response = token.getAssertion( FACET, data['appId'], data['challenge'], data['registeredKeys'][0] ) complete_authentication(request.json, response) def test_authenticate_multiple_soft_u2f(self): # Register device1, token1 = register_token() device2, token2 = register_token([device1]) # Authenticate request = begin_authentication(APP_ID, [device1, device2]) data = request.data_for_client response = token1.getAssertion( FACET, data['appId'], data['challenge'], data['registeredKeys'][0] ) complete_authentication(request.json, response) def test_authenticate_soft_u2f(self): device, token = register_token() challenge1 = U2fSignRequest.create(APP_ID, [device]) data1 = challenge1.data_for_client challenge2 = U2fSignRequest.create(APP_ID, [device]) data2 = challenge2.data_for_client response2 = token.getAssertion( FACET, data2['appId'], data2['challenge'], data2['registeredKeys'][0] ) response1 = token.getAssertion( FACET, data1['appId'], data1['challenge'], data1['registeredKeys'][0] ) challenge1.complete(response1) challenge2.complete(response2) self.assertRaisesRegex(ValueError, 'challenge', challenge1.complete, response2) self.assertRaisesRegex(ValueError, 'challenge', challenge2.complete, response1) def test_wrong_facet(self): token = SoftU2FDevice() request = U2fRegisterRequest.create(APP_ID, []) data = request.data_for_client response = token.register( "http://wrongfacet.com", data['appId'], data['registerRequests'][0] ) self.assertRaisesRegex(ValueError, 'facet', request.complete, response, FACETS) response2 = token.register( FACET, data['appId'], data['registerRequests'][0] ) device, cert = request.complete(response2, FACETS) signreq = U2fSignRequest.create(APP_ID, [device]) data = signreq.data_for_client response = token.getAssertion( 'http://notright.com', data['appId'], data['challenge'], data['registeredKeys'][0] ) self.assertRaisesRegex(ValueError, 'facet', signreq.complete, response, FACETS) def test_wrong_challenge(self): device = SoftU2FDevice() request = begin_registration(APP_ID) data = request.data_for_client response = device.register(FACET, data['appId'], data['registerRequests'][0]) request2 = begin_registration(APP_ID) self.assertRaisesRegex(ValueError, 'challenge', complete_registration, request2.json, response) def test_invalid_signature(self): device = SoftU2FDevice() request = begin_registration(APP_ID) data = request.data_for_client response = device.register(FACET, data['appId'], data['registerRequests'][0]) response = RegisterResponse.wrap(response) raw_data = response.registrationData.bytes raw_data = raw_data[:-4] + b'\0\0\0\0' response['registrationData'] = websafe_encode(raw_data) response = response.json self.assertRaisesRegex(ValueError, 'signature', complete_registration, request.json, response) if six.PY2: U2fTest.assertRaisesRegex = U2fTest.assertRaisesRegexp python-u2flib-server-5.0.0/test/test_model.py0000664000175000017500000001607213066466174021107 0ustar daindain00000000000000from u2flib_server.utils import websafe_decode from u2flib_server.model import (JSONDict, RegistrationData, SignatureData, U2fRegisterRequest, U2fSignRequest) from binascii import b2a_hex import unittest SAMPLE_REG_DATA = websafe_decode( 'BQRFJ5xApW6uBsuSJ_FgUcL-seKecha71q8evgqfQwc5QuqQhv4qJoL3KpSUKX1T6XVJEqyOkn' 'Ok397y2Rh5m_zkQD9wrlgMW9EGOGmBQ0lKIa79vcQl2tMF7hlhNjIGrJHw7Rqg8wbnKWSvQjs5' 'ytK3s3X9YRuNcY3ht5pJYZbGa-4wggGHMIIBLqADAgECAgkAmb7osQyi7BwwCQYHKoZIzj0EAT' 'AhMR8wHQYDVQQDDBZZdWJpY28gVTJGIFNvZnQgRGV2aWNlMB4XDTEzMDcxNzE0MjEwM1oXDTE2' 'MDcxNjE0MjEwM1owITEfMB0GA1UEAwwWWXViaWNvIFUyRiBTb2Z0IERldmljZTBZMBMGByqGSM' '49AgEGCCqGSM49AwEHA0IABDvhl91zfpg9n7DeCedcQ8gGXUnemiXoi-JEAxz-EIhkVsMPAyzh' 'tJZ4V3CqMZ-MOUgICt2aMxacMX9cIa8dgS2jUDBOMB0GA1UdDgQWBBQNqL-TV04iaO6mS5tjGE' '6ShfexnjAfBgNVHSMEGDAWgBQNqL-TV04iaO6mS5tjGE6ShfexnjAMBgNVHRMEBTADAQH_MAkG' 'ByqGSM49BAEDSAAwRQIgXJWZdbvOWdhVaG7IJtn44o21Kmi8EHsDk4cAfnZ0r38CIQD6ZPi3Pl' '4lXxbY7BXFyrpkiOvCpdyNdLLYbSTbvIBQOTBGAiEAsp3iNiXaF9mk6mHzJqva-hi7AlT-_or-' '2HdKUJycqaUCIQCP9P4aju4iq8U6hRyIIllppzwfK9_7QNv_7_OV7pQxug' ) SAMPLE_REG_DATA_NEEDS_FIX = websafe_decode( 'BQQR2Q82wJ9RLOcH5TvQvve7LrBnDp0YiCSDxKPiHsg_AY1b70GK-dcCt-HqCkqJZikAXL4zLY' 'CsKmucc1xna99BQAMOcuxXOpiG-MJIB3zUpvT1hO2v18nBYsRRFjRPIStBxFyh6PMMjA10aZFf' '68_EFpgc_CAfEiEqr5L41anLJ3EwggIcMIIBBqADAgECAgQ4Zt91MAsGCSqGSIb3DQEBCzAuMS' 'wwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEw' 'MDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKzEpMCcGA1UEAwwgWXViaWNvIFUyRiBFRSBTZXJpYW' 'wgMTM4MzExNjc4NjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ3jfx0DHOblHJO09Ujubh2' 'gQZWwT3ob6-uzzjZD1XiyAob_gsw3FOzXefQRblty48r-U-o4LkDFjx_btwuSHtxoxIwEDAOBg' 'orBgEEAYLECgEBBAAwCwYJKoZIhvcNAQELA4IBAQIaR2TKAInPkq24f6hIU45yzD79uzR5KUME' 'e4IWqTm69METVio0W2FHWXlpeUe85nGqanwGeW7U67G4_WAnGbcd6zz2QumNsdlmb_AebbdPRa' '95Z8BG1ub_S04JoxQYNLaa8WRlzN7POgqAnAqkmnsZQ_W9Tj2uO9zP3mpxOkkmnqz7P5zt4Lp5' 'xrv7p15hGOIPD5V-ph7tUmiCJsq0LfeRA36X7aXi32Ap0rt_wyfnRef59YYr7SmwaMuXKjbIZS' 'LesscZZTMzXd-uuLb6DbUCasqEVBkGGqTRfAcOmPov1nHUrNDCkOR0obR4PsJG4PiamIfApNeo' 'XGYpGbok6nucMEUCIQCCL2jamBxyJQ6ktxgJVNFRKf4pUHvlvFgyXTQ6NOYlAwIgSQ1TB64V25' 'deHKak1UEZA2AbkR9znO2XJKd93v1BY9Y' ) SAMPLE_SIG_DATA = websafe_decode( 'AAAAAAEwRgIhAPVBA3i9Ag6UOe9Jv-fz0J8HLIGfAS26eP8m0FRoZvufAiEAzmxU_mJxDJXOvV' '-FMl_Ug2qYUGFZ6hu9m2VYdTkbvgA' ) class RegistrationDataTest(unittest.TestCase): def test_invalid_data(self): self.assertRaises(ValueError, RegistrationData, b'abc') def test_str(self): rawresponse = RegistrationData(SAMPLE_REG_DATA) self.assertEqual(b'050445279c', b2a_hex(rawresponse.bytes)[:10]) self.assertEqual(SAMPLE_REG_DATA, rawresponse.bytes) def test_str_needs_fix(self): rawresponse = RegistrationData(SAMPLE_REG_DATA_NEEDS_FIX) self.assertEqual(b'050411d90f', b2a_hex(rawresponse.bytes)[:10]) self.assertNotEqual(SAMPLE_REG_DATA_NEEDS_FIX, rawresponse.bytes) class SignatureDataTest(unittest.TestCase): def test_str(self): rawresponse = SignatureData(SAMPLE_SIG_DATA) self.assertEqual(b'0000000001', b2a_hex(rawresponse.bytes)[:10]) self.assertEqual(SAMPLE_SIG_DATA, rawresponse.bytes) class JSONDictTest(unittest.TestCase): def test_create(self): self.assertEqual({}, JSONDict()) def test_create_from_bytes(self): self.assertEqual({'a': 1, 'b': 2}, JSONDict(b'{"a":1,"b":2}')) def test_create_from_unicode(self): self.assertEqual({'a': 1, 'b': 2}, JSONDict(u'{"a":1,"b":2}')) def test_create_from_dict(self): self.assertEqual({'a': 1, 'b': 2}, JSONDict({'a': 1, 'b': 2})) def test_create_from_kwargs(self): self.assertEqual({'a': 1, 'b': 2}, JSONDict(a=1, b=2)) def test_create_from_list(self): self.assertEqual({}, JSONDict([])) self.assertEqual({'a': 1, 'b': 2}, JSONDict([('a', 1), ('b', 2)])) def test_create_wrong_nargs(self): self.assertRaises(TypeError, JSONDict, {}, {}) self.assertRaises(TypeError, JSONDict, {'a': 1}, {'b': 2}) def test_json(self): self.assertEqual('{}', JSONDict().json) self.assertEqual('{"a": 1}', JSONDict(a=1).json) def test_wrap(self): self.assertTrue(isinstance(JSONDict.wrap({}), JSONDict)) x = JSONDict() self.assertTrue(x is JSONDict.wrap(x)) def test_getattr_unknown(self): self.assertRaises(AttributeError, lambda: JSONDict().foo) def test_getattr(self): self.assertEqual(1, JSONDict(a=1).a) def test_required_fields(self): class Foo(JSONDict): _required_fields = ['foo', 'bar'] Foo({'foo': 1, 'bar': 2}) self.assertRaises(ValueError, Foo, {'foo': 1}) self.assertRaises(ValueError, Foo, {'bar': 1}) self.assertRaises(ValueError, Foo) class U2fRegisterRequestTest(unittest.TestCase): def test_u2f_register_request(self): challenge = "Jtb6wLXjMHN67fV1BVNivz-qnAnD8OOqFju49RDBJro" req = U2fRegisterRequest.create( 'https://example.com', [], websafe_decode(challenge) ) self.assertEqual(U2fRegisterRequest.wrap({ "registeredKeys": [], "appId": "https://example.com", "registerRequests": [{ "version": "U2F_V2", "challenge": "Jtb6wLXjMHN67fV1BVNivz-qnAnD8OOqFju49RDBJro" }] }), req) self.assertEqual(U2fRegisterRequest.wrap(req.json), req) self.assertEqual( websafe_decode('EAaArVRs5qV39C9S3zO0z9ynVoWeZkuNfeMpsVDQnOk'), req.applicationParameter ) self.assertEqual([], req.registeredKeys) self.assertEqual(1, len(req.registerRequests)) reg_req = req.get_request('U2F_V2') self.assertEqual(reg_req.challenge, websafe_decode(challenge)) self.assertEqual(reg_req.version, 'U2F_V2') class U2fSignRequestTest(unittest.TestCase): def test_missing_keys(self): self.assertRaises(ValueError, U2fSignRequest.wrap, { "appId": "https://example.com", "challenge": "0000", "registeredKeys": [] }) def test_u2f_sign_request(self): challenge = "Jtb6wLXjMHN67fV1BVNivz-qnAnD8OOqFju49RDBJro" req = U2fSignRequest.wrap( { "appId": "https://example.com", "registeredKeys": [{ "publicKey": "BBCcnAOknoMgokEGuTdfpNLQ-uylwlKp_xbEW8urjJsXK" "v9XZSL-V8C2nwcPEckav1mKZFr5K96uAoLtuxOUf-E", "version": "U2F_V2", "keyHandle": "BIarIKfyMqyf4bEI6tOqGInAfHrrQkMA2eyPJlNnInbAG" "1tXNpdRs48ef92_b1-mfN4VhaTWxo1SGoxT6CIanw" }], "challenge": challenge } ) self.assertEqual(U2fSignRequest.wrap(req.json), req) self.assertEqual( req.applicationParameter, websafe_decode('EAaArVRs5qV39C9S3zO0z9ynVoWeZkuNfeMpsVDQnOk') ) self.assertEqual(req.challenge, websafe_decode(challenge)) python-u2flib-server-5.0.0/setup.py0000775000175000017500000000543613067130237017123 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from __future__ import absolute_import from release import setup import sys install_requires = ['cryptography>=1.2', 'six'] if sys.version_info < (3, 4): install_requires.append('enum34') setup( name='python-u2flib-server', author='Dain Nilsson', author_email='dain@yubico.com', description='Python based U2F server library', maintainer='Yubico Open Source Maintainers', maintainer_email='ossmaint@yubico.com', url='https://github.com/Yubico/python-u2flib-server', install_requires=install_requires, test_suite='test', tests_require=[], extras_require={ 'u2f_server': ['WebOb'], }, classifiers=[ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', '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 :: Implementation :: PyPy', 'Topic :: Internet', 'Topic :: Security :: Cryptography', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) python-u2flib-server-5.0.0/ChangeLog0000664000175000017500000005325213067131574017164 0ustar daindain000000000000002017-03-30 Dain Nilsson * NEWS, setup.py, u2flib_server/__init__.py: Prepare release. 2017-03-29 Dain Nilsson * test/test_u2f.py: Get rid of deprecation warning in tests. 2017-03-29 Dain Nilsson * test/test_u2f.py: Cleanups and additional tests. 2017-03-29 Dain Nilsson * u2flib_server/model.py: Minor cleanups. - Use IntEnum for TRANSPORT. - Throw ValueError on invalid signature instead of exposing the exception from cryptography. 2017-03-29 Dain Nilsson * README: Fix python dependency in README. 2017-03-28 Dain Nilsson * .travis.yml: Fix pypy versions on Travis. 2017-03-28 Dain Nilsson * .travis.yml, NEWS: Add pypy and pypy3 to travis builds. 2017-03-28 Dain Nilsson * : Merge PR #40. 2017-03-28 Dain Nilsson * setup.cfg: Re-add universal=1 to bdist_wheel which was removed unintentionally. 2017-03-28 Dain Nilsson * u2flib_server/attestation/resolvers.py: Use setdefault to initialize empty list. 2017-03-28 Dain Nilsson * u2flib_server/attestation/data.py: Added missing transports to YubiKey Plus. 2017-03-25 Frank Cash * README: Update README 2017-03-22 Dain Nilsson * u2flib_server/model.py: Make Registration and Signature data take raw data only. 2017-03-22 Dain Nilsson * test/test_attestation.py, u2flib_server/attestation/matchers.py: Fix metadata match by OID with value. 2017-02-28 Dain Nilsson * examples/u2f_server.py, u2flib_server/model.py: Return DeviceRegistration used for successful auth. 2017-02-28 Dain Nilsson * test/test_attestation.py, u2flib_server/attestation/__init__.py, u2flib_server/attestation/metadata.py, u2flib_server/attestation/model.py: Remove duplicate Attestation class. 2017-02-28 Dain Nilsson * .gitmodules, MANIFEST.in, release.py, setup.py, test/soft_u2f_v2.py, u2flib_server/yubicommon, vendor/yubicommon: Remove yubicommon. 2017-02-27 Dain Nilsson * test/test_model.py, u2flib_server/model.py: Require at least one RegisteredKey for new U2fSignRequest. 2017-02-21 Dain Nilsson * README, examples/u2f_server.py: Update example. 2017-02-21 Dain Nilsson * examples/u2f_server.py, examples/yubiauth_server.py: Drop yubiauth_server. 2017-02-21 Dain Nilsson * test/test_attestation.py, test/test_model.py, test/test_serialization.py, test/test_u2f.py: Fix tests. 2017-02-21 Dain Nilsson * .travis.yml: Update travis. 2017-02-21 Dain Nilsson * test/test_attestation.py, test/test_jsapi.py, test/test_matchers.py, test/test_model.py, u2flib_server/attestation/model.py: Fix tests. 2017-02-21 Dain Nilsson * .pre-commit-config.yaml, setup.cfg, test/soft_u2f_v2.py, test/test_model.py: flake8 2017-02-21 Dain Nilsson * examples/u2f_server.py, test/soft_u2f_v2.py, test/test_attestation.py, test/test_jsapi.py, test/test_model.py, test/test_serialization.py, test/test_u2f.py, test/test_u2f_highlevel.py, test/test_u2f_v2.py, test/test_utils.py, u2flib_server/attestation/matchers.py, u2flib_server/attestation/metadata.py, u2flib_server/{jsapi.py => attestation/model.py}, u2flib_server/attestation/resolvers.py, u2flib_server/{data.py => model.py}, u2flib_server/u2f.py, u2flib_server/utils.py: More fixes. 2017-02-17 Dain Nilsson * examples/u2f_server.py: Updated example. 2017-02-17 Dain Nilsson * setup.py, test/soft_u2f_v2.py, test/test_u2f_highlevel.py, u2flib_server/__init__.py, u2flib_server/data.py, u2flib_server/jsapi.py, u2flib_server/u2f_v2.py, u2flib_server/utils.py: Re-implemented objects in data.py (to be moved) 2017-01-23 Dain Nilsson * : Merge pull request #32. 2016-04-05 Dain Nilsson * NEWS, u2flib_server/__init__.py: Bumped version post release. 2016-04-05 Dain Nilsson * NEWS, u2flib_server/__init__.py: Prepare release. 2016-03-30 Dain Nilsson * NEWS: Updated NEWS. 2016-03-30 Dain Nilsson * examples/u2f_server.py: Fixed example server which was still expecting M2Crypto. 2016-03-30 Dain Nilsson * u2flib_server/u2f_v2.py: Fix certification parsing on py3 (fixes #29). 2016-03-30 Dain Nilsson * test/test_u2f_v2.py: Added failing test (#29). 2016-03-01 Dain Nilsson * NEWS, u2flib_server/__init__.py: Bumped version post release. 2016-03-01 Dain Nilsson * u2flib_server/__init__.py: Set version for release. 2016-03-01 Dain Nilsson * NEWS: Updated NEWS for release. 2016-03-01 Dain Nilsson * BLURB: Corrected URL in BLURB. 2016-03-01 Dain Nilsson * NEWS: Updated NEWS with latest changes. Updated entry about added/removed dependencies. Added note about transports. Removed note in NEWS about double-hashing as this bug was introduced during the port to cryptography, and is not in any released version. 2016-02-26 Alex Willmer * setup.py, u2flib_server/attestation/matchers.py: Removed pyasn1 dependencies Note that get_ext_by_oid() still returns the raw bytes of the extension. I'm still undecided on whether keeping this behaviour is the right thing to do or not. References Yubico/python-u2flib-server#19 2016-02-26 Dain Nilsson * test/test_attestation.py: Added tests for reading transport data. 2016-02-26 Dain Nilsson * setup.py, u2flib_server/attestation/data.py, u2flib_server/attestation/metadata.py, u2flib_server/jsapi.py: Added support for transport data in attestation certificates and metadata. 2016-02-26 Dain Nilsson * .travis.yml: Disable PyPy in TravisCI (closes #24). We should re-enable this once TravisCI supports PyPy 2.6 2016-02-26 Dain Nilsson * : Merge pull request #20 from moreati/python3.x Add Python 3.x support 2016-02-26 Alex Willmer * setup.cfg: Declared universal wheel support Since we are pure python, and the code is written in a subset of Python supported by both 2.x and 3.x we can do this. 2016-02-26 Alex Willmer * : Merge #!/usr/bin/env & double-hash fixes from 'master' 2016-02-25 Alex Willmer * test/test_u2f_v2.py, u2flib_server/u2f_v2.py: Fixed uses of .encode() on bytes variables These raised AttributeError on Python 3. I've also changed the remaining exceptions to use the repr() of the supplied arguments, for consistency. 2016-02-25 Alex Willmer * u2flib_server/attestation/resolvers.py, u2flib_server/jsapi.py, u2flib_server/utils.py: Used text_type to check for need .encode() See inline comments https://github.com/Yubico/python-u2flib-server/commit/49930e539deeede13fcc512feaf88bc414080a46 2016-02-25 Alex Willmer * vendor/yubicommon: Updated yubicommon for compat.text_type 2016-02-22 Dain Nilsson * test/soft_u2f_v2.py, test/test_attestation.py, test/test_jsapi.py, test/test_matchers.py, u2flib_server/attestation/resolvers.py, u2flib_server/jsapi.py, u2flib_server/u2f_v2.py, u2flib_server/utils.py: Made string types compatible with Python 3. 2016-02-15 Dain Nilsson * test/soft_u2f_v2.py, u2flib_server/u2f_v2.py: Fix double-hashing of message to be signed for authentication. 2016-02-15 Dain Nilsson * test/test_u2f_v2.py: Add test for double-hash for authentication. 2016-02-14 Dain Nilsson * test/soft_u2f_v2.py, test/test_u2f_v2.py, u2flib_server/u2f_v2.py: Fix double-hashing in attestation signatures. 2016-02-12 Dain Nilsson * : Merge pull request #21 from medina/python-path env-derived paths 2016-02-04 medina * examples/u2f_server.py: Update u2f_server.py 2016-01-25 Dain Nilsson * vendor/yubicommon: Updated yubicommon. 2016-01-25 Dain Nilsson * : Merge pull request #18 from moreati/cryptography1.2 Rebase of m2crypto -> cryptography migration 2016-01-25 Alex Willmer * examples/u2f_server.py, u2flib_server/attestation/matchers.py, u2flib_server/attestation/resolvers.py, u2flib_server/jsapi.py: Changes uses of map() to list comprehensions In Python 3.x map() returns an iterator, and in addtion comprehensions are generally considered more pythonic. 2016-01-25 Alex Willmer * .travis.yml, tox.ini: Test Python 3.3-3.5 & PyPy under Tox & Travis The testsuite currently fails under Python 3.x. This is expected, for now. 2016-01-14 Alex Willmer * README: Updated installation instructions for m2crypto -> cryptography transition 2016-01-14 Alex Willmer * NEWS: Added upcoming changes to NEWS 2016-01-14 Alex Willmer * u2flib_server/attestation/resolvers.py: Added docstring to MetadataResolver._verify_cert() 2016-01-14 Alex Willmer * test/test_matchers.py: Added unit tests for get_ext_by_oid() Originally this file was introduced in https://github.com/moreati/python-u2flib-server/commit/ef597bf0c2e2 2016-01-14 Alex Willmer * .travis.yml: Add CI build-dep for Cryptography 2016-01-12 Dain Nilsson * u2flib_server/attestation/data.py: Updated Yubico metadata. 2016-01-12 Dain Nilsson * vendor/yubicommon: Updated yubicommon. 2016-01-12 Dain Nilsson * : Merge pull request #17 from mark-adams/with-cryptography Ported all crypto-related code to use cryptography instead of M2Crypto 2016-01-11 Mark Adams * u2flib_server/__init__.py: Increment version to 4.0.0 2016-01-11 Mark Adams * u2flib_server/attestation/resolvers.py: Changed MetadataResolver._verify_cert to return a bool instead of int 2016-01-11 Mark Adams * test/soft_u2f_v2.py, u2flib_server/u2f_v2.py, u2flib_server/utils.py: Stop raising a generic exception when signature verification fails Both methods no longer raise a generic exception (InvalidSignature is allowed to bubble instead since this provides better feedback to the caller 2016-01-09 Mark Adams * u2flib_server/__init__.py: Increment version since the change from M2Crypto to cryptography is fairly significant 2016-01-05 Mark Adams * setup.py, test/soft_u2f_v2.py, test/test_attestation.py, test/test_u2f_highlevel.py, u2flib_server/attestation/matchers.py, u2flib_server/attestation/resolvers.py, u2flib_server/u2f_v2.py, u2flib_server/utils.py: Ported all crypto-related code to use cryptography instead of M2Crypto 2015-09-30 Dain Nilsson * vendor/yubicommon: Updated yubicommon. 2015-08-03 Dain Nilsson * : Merge pull request #11 from moreati/extras_require Declare dependencies of the scripts in examples/ 2015-07-31 Henrik Stråth * : Merge pull request #12 from moreati/test_jsapi Unit tests for u2flib_server.jsapi, 100% coverage 2015-07-29 Alex Willmer * setup.py: Declare dependencies of the scripts in examples/ This allows e.g. `pip install python-u2flib-server[u2f_server]`. The line containing `python_version=="2.6"` is an environment marker, supported since setuptools 0.7 (released June 2013). `argparse` is in the stdlib from Python 2.7. 2015-07-29 Alex Willmer * .travis.yml: Speed up Travis builds - Move to container-based infrastructure - Use latest pip to benefit from wheel based installs - Save pip downloads and wheels cache between builds 2015-07-29 Alex Willmer * .coveragerc, .gitignore, .travis.yml, dev-requirements.txt, tox.ini: Add Tox support and reporting of test coverage 2015-06-30 Dain Nilsson * README: Fixed instructions in README. 2015-06-30 Dain Nilsson * setup.py, u2flib_server/__init__.py, vendor/yubicommon: Updated yubicommon. 2015-06-16 Dain Nilsson * README: Updated README. 2015-06-16 Dain Nilsson * .gitmodules, MANIFEST.in, release.py, setup.py, test/__init__.py, test/test_attestation.py, test/test_serialization.py, test/test_u2f_highlevel.py, test/test_u2f_v2.py, u2flib_server/yubicommon, vendor/yubicommon: Use yubicommon. 2015-06-16 Dain Nilsson * NEWS, u2flib_server/__init__.py: Updated version and NEWS for release. 2015-06-15 Dain Nilsson * BLURB, COPYING, examples/u2f_server.py, examples/yubiauth_server.py, release.py, setup.py, test/soft_u2f_v2.py, test/test_attestation.py, test/test_serialization.py, test/test_u2f_highlevel.py, test/test_u2f_v2.py, u2flib_server/__init__.py, u2flib_server/attestation/__init__.py, u2flib_server/attestation/matchers.py, u2flib_server/attestation/metadata.py, u2flib_server/attestation/resolvers.py, u2flib_server/jsapi.py, u2flib_server/u2f.py, u2flib_server/u2f_v2.py, u2flib_server/utils.py: Changed license to BSD 2-clause. 2015-06-02 Dain Nilsson * NEWS, u2flib_server/__init__.py, u2flib_server/attestation/resolvers.py: Fix metadata again. 2015-06-02 Dain Nilsson * NEWS: Updated NEWS for release. 2015-04-13 Dain Nilsson * u2flib_server/attestation/data.py: Updated Yubico metadata. 2015-03-12 Dain Nilsson * NEWS, u2flib_server/attestation/resolvers.py: Fix reading metadata from directory. 2015-03-12 Dain Nilsson * : Merge pull request #8 from Yubico/issue-6-fix Now deletes challenges after use. 2015-02-18 Dain Nilsson * u2flib_server/u2f_v2.py: Remove cert that wasn't broken. 2015-02-18 Dain Nilsson * u2flib_server/u2f_v2.py: Added missing broken cert. 2015-02-02 Dain Nilsson * NEWS, u2flib_server/__init__.py: Bumped version. 2015-02-02 Dain Nilsson * NEWS: Updated NEWS for release. 2015-02-02 Dain Nilsson * setup.py: Add version requirement to pyasn1 dependency. 2015-02-02 Dain Nilsson * test/test_attestation.py, u2flib_server/attestation/resolvers.py: Only allow the latest version of each metadata object. 2015-01-29 Dain Nilsson * NEWS, setup.py, test/test_attestation.py, u2flib_server/attestation/__init__.py, u2flib_server/attestation/data.py, u2flib_server/attestation/matchers.py, u2flib_server/attestation/metadata.py, u2flib_server/attestation/resolvers.py, u2flib_server/jsapi.py: Added attestation/metadata. 2015-01-21 Dain Nilsson * README, examples/u2f_server.py, u2flib_server/jsapi.py, u2flib_server/u2f.py, u2flib_server/u2f_v2.py: Updated u2f_server.py to use highlevel API. 2015-01-21 Dain Nilsson * setup.py: Change development status to stable. 2015-01-19 Dain Nilsson * : commit 263e793ced7a05faaf144dc39cee3cce404c01c7 Author: Dain Nilsson Date: Mon Jan 19 12:34:39 2015 +0100 2015-01-19 Dain Nilsson * NEWS, test/test_u2f_highlevel.py: Add test for registering multiple devices. 2015-01-19 Dain Nilsson * test/{test_u2f_multiple.py => test_u2f_highlevel.py}: Rename test. 2015-01-19 Dain Nilsson * test/test_u2f_multiple.py, u2flib_server/{u2f_multiple.py => u2f.py}: Rename u2f_multiple to u2f. 2015-01-19 Dain Nilsson * u2flib_server/__init__.py, u2flib_server/jsapi.py, u2flib_server/jsobjects.py, u2flib_server/u2f_multiple.py: Cleanups and copy-paste fix. 2015-01-14 Low Kian Seong * setup.py: Update setup.py Adding `description` to `setup.py` 2015-01-14 Dain Nilsson * NEWS: Update NEWS for release. 2015-01-14 Dain Nilsson * NEWS, u2flib_server/u2f_v2.py: Fix certificates with unused bits in signature. 2014-12-09 Henrik Stråth * README: Update README 2015-01-14 Dain Nilsson * : Merge pull request #7 from lowks/patch-1 Update setup.py 2015-01-14 Dain Nilsson * NEWS: Update NEWS for release. 2015-01-14 Dain Nilsson * NEWS, u2flib_server/u2f_v2.py: Fix certificates with unused bits in signature. 2014-12-10 Henrik Stråth * test/test_u2f_multiple.py, u2flib_server/u2f_multiple.py: Changed the new tests to nosetests 2014-12-10 Henrik Stråth * test/test_u2f_multiple.py, u2flib_server/jsobjects.py, u2flib_server/u2f_multiple.py: Added RegisterRequestData.getRegisterRequest(response) and corresponding AuthenticateRequestData method 2014-12-10 Henrik Stråth * test/soft_u2f_v2.py, test/test_u2f_multiple.py, u2flib_server/jsobjects.py, u2flib_server/u2f_multiple.py: First fully working draft 2014-12-09 Henrik Stråth * test/soft_u2f_v2.py, test/test_u2f_multiple.py, u2flib_server/u2f_multiple.py, u2flib_server/u2f_v2.py: First passing unit test for multi-device registration 2014-12-09 Henrik Stråth * README: Update README 2014-12-09 Henrik Stråth * test/test_u2f_multiple.py, u2flib_server/jsobjects.py, u2flib_server/u2f_multiple.py: Initial stab at multi key support 2014-12-09 Henrik Stråth * .gitignore: Added Pycharm to .gitignore 2014-12-01 Henrik Stråth * NEWS: Update NEWS 2014-12-01 Henrik Stråth * NEWS: Update NEWS 2014-10-22 Henrik Stråth * README, README.adoc: Symlinked README and linked to developers.yubico.com/U2F 2014-10-10 Dain Nilsson * NEWS, u2flib_server/__init__.py: Update NEWS and bump version. 2014-10-10 Dain Nilsson * u2flib_server/jsapi.py: Fix error handling for JSONDict.__getattr__ 2014-10-09 Dain Nilsson * NEWS: Updated NEWS for release. 2014-10-09 Dain Nilsson * NEWS, examples/u2f_server.py, examples/yubiauth_server.py, test/soft_u2f_v2.py, test/test_serialization.py, test/test_u2f_v2.py, u2flib_server/__init__.py, u2flib_server/jsapi.py, u2flib_server/u2f_v2.py: 3.0.0 API rewrite 2014-10-09 Dain Nilsson * u2flib_server/u2f_v2.py: Use EVP API instead of assuming EC for attestation cert verification. 2014-10-07 Klas Lindfors * .travis.yml: install swig instead 2014-10-07 Klas Lindfors * .travis.yml: add travis 2014-09-26 Dain Nilsson * release.py: Fix keyid passing in release.py 2014-09-26 Dain Nilsson * NEWS: Updated NEWS for release. 2014-09-26 Dain Nilsson * NEWS, u2flib_server/__init__.py: Bumped version and updated NEWS. 2014-09-25 Dain Nilsson * u2flib_server/__init__.py: Bumped version. 2014-09-19 Dain Nilsson * NEWS, README: Updated NEWS and README 2014-09-19 Dain Nilsson * examples/u2f_server.py, examples/yubiauth_server.py: Use simplified AppID to allow non-https in example server. 2014-09-18 Dain Nilsson * setup.py: Fix license in setup.py 2014-09-16 Dain Nilsson * u2flib_server/jsapi.py, u2flib_server/u2f_v2.py: Allow JSONDict to take kwargs. 2014-09-16 Dain Nilsson * u2flib_server/u2f_v2.py: Fix description of SignResponse in comment. 2014-09-15 Dain Nilsson * test/soft_u2f_v2.py, u2flib_server/u2f_v2.py, u2flib_server/utils.py: Use OpenSSL for random. 2014-09-15 Dain Nilsson * u2flib_server/u2f_v2.py: Support providing a challenge explicitly. 2014-09-15 Dain Nilsson * u2flib_server/u2f_v2.py: Expose JSAPI objects from u2f_v2. 2014-09-15 Dain Nilsson * u2flib_server/u2f_v2.py: Allow passing RegisterResponse/SignResponse. 2014-09-15 Dain Nilsson * test/soft_u2f_v0.py, test/test_serialization.py, test/test_u2f_v0.py, u2flib_server/u2f_v0.py, u2flib_server/utils.py: Removed v0 and related code. 2014-09-15 Dain Nilsson * u2flib_server/jsapi.py, u2flib_server/u2f_v2.py: Introduce jsapi to deal with the JavaScript objects. 2014-09-15 Dain Nilsson * u2flib_server/u2f_v2.py: Provice access to raw data for types. 2014-09-11 Henrik Stråth * README: Updated README, added a Dependencies section. 2014-09-10 Dain Nilsson * u2flib_server/u2f_v0.py, u2flib_server/u2f_v2.py: Replace staticmethod with classmethod where applicable. 2014-09-08 Dain Nilsson * examples/u2f_server.py: Added a note about requiring webob for the example server (fixes #3). 2014-09-01 Dain Nilsson * test/soft_u2f_v2.py, u2flib_server/u2f_v2.py: Updated for latest U2F_V2 spec. 2014-02-18 Dain Nilsson * NEWS, u2flib_server/__init__.py: Updated version and NEWS for release. 2014-02-13 Dain Nilsson * {u2flib_server => test}/soft_u2f_v0.py, {u2flib_server => test}/soft_u2f_v2.py, test/test_u2f_v0.py, test/test_u2f_v2.py: Moved SoftU2FDevice to test/ 2014-02-13 Dain Nilsson * Initial import python-u2flib-server-5.0.0/u2flib_server/0000775000175000017500000000000013067131574020154 5ustar daindain00000000000000python-u2flib-server-5.0.0/u2flib_server/attestation/0000775000175000017500000000000013067131574022513 5ustar daindain00000000000000python-u2flib-server-5.0.0/u2flib_server/attestation/__init__.py0000664000175000017500000000301713066466174024633 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from u2flib_server.attestation.resolvers import create_resolver from u2flib_server.attestation.metadata import MetadataProvider __all__ = ['create_resolver', 'MetadataProvider'] python-u2flib-server-5.0.0/u2flib_server/attestation/model.py0000664000175000017500000000573613066466174024206 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from u2flib_server.model import JSONDict, Transport class VendorInfo(JSONDict): pass class Selector(JSONDict): pass class DeviceInfo(JSONDict): @property def selectors(self): selectors = self.get('selectors') if selectors is None: return None return [Selector(selector) for selector in selectors] @property def transports(self): transport_int = self.get('transports') if transport_int is None: return None return [t for t in Transport if t.value & transport_int] class MetadataObject(JSONDict): @property def vendorInfo(self): return VendorInfo(self['vendorInfo']) @property def devices(self): return [DeviceInfo(dev) for dev in self['devices']] class Attestation(object): def __init__(self, trusted, vendor_info=None, device_info=None, cert_transports=None): self._trusted = trusted self._vendor_info = vendor_info self._device_info = device_info if device_info.transports is None and cert_transports is None: self._transports = None else: transports = sum(t.value for t in cert_transports or []) | \ sum(t.value for t in device_info.transports or []) self._transports = [t for t in Transport if t.value & transports] @property def trusted(self): return self._trusted @property def vendor_info(self): return self._vendor_info @property def device_info(self): return self._device_info @property def transports(self): return self._transports python-u2flib-server-5.0.0/u2flib_server/attestation/resolvers.py0000664000175000017500000001265313066466174025126 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from u2flib_server.attestation.model import MetadataObject from u2flib_server.attestation.data import YUBICO import six import os import json from cryptography import x509 from cryptography.x509.oid import NameOID from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec, rsa, padding __all__ = ['MetadataResolver', 'create_resolver'] class MetadataResolver(object): def __init__(self): self._identifiers = {} # identifier -> Metadata self._certs = {} # Subject -> Cert self._metadata = {} # Cert -> Metadata def add_metadata(self, metadata): metadata = MetadataObject.wrap(metadata) if metadata.identifier in self._identifiers: existing = self._identifiers[metadata.identifier] if metadata.version <= existing.version: return # Older version else: # Re-index everything self._identifiers[metadata.identifier] = metadata self._certs.clear() self._metadata.clear() for metadata in self._identifiers.values(): self._index(metadata) else: self._identifiers[metadata.identifier] = metadata self._index(metadata) def _index(self, metadata): for cert_pem in metadata.trustedCertificates: if isinstance(cert_pem, six.text_type): cert_pem = cert_pem.encode('ascii') cert = x509.load_pem_x509_certificate(cert_pem, default_backend()) subject = cert.subject \ .get_attributes_for_oid(NameOID.COMMON_NAME)[0].value self._certs.setdefault(subject, []).append(cert) self._metadata[cert] = metadata def _verify_cert(self, cert, pubkey): """Returns True if cert contains a correct signature made using the provided key NB: This *only* checks the signature. No other checks are performed. E.g. the trust chain, expiry are all ignored. """ cert_signature = cert.signature cert_bytes = cert.tbs_certificate_bytes if isinstance(pubkey, rsa.RSAPublicKey): verifier = pubkey.verifier( cert_signature, padding.PKCS1v15(), cert.signature_hash_algorithm ) elif isinstance(pubkey, ec.EllipticCurvePublicKey): verifier = pubkey.verifier( cert_signature, ec.ECDSA(cert.signature_hash_algorithm) ) else: raise ValueError("Unsupported public key value") verifier.update(cert_bytes) try: verifier.verify() return True except InvalidSignature: return False def resolve(self, cert): if isinstance(cert, bytes): cert = x509.load_der_x509_certificate(cert, default_backend()) issuer = cert.issuer \ .get_attributes_for_oid(NameOID.COMMON_NAME)[0].value for issuer in self._certs.get(issuer, []): if self._verify_cert(cert, issuer.public_key()): return self._metadata[issuer] return None def _load_from_file(fname): with open(fname, 'r') as f: return json.load(f) def _load_from_dir(dname): json_fnames = [os.path.join(dname, d) for d in os.listdir(dname) if d.endswith('.json')] return [_load_from_file(fname) for fname in json_fnames] def _add_data(resolver, data): if isinstance(data, list): for d in data: _add_data(resolver, d) return elif isinstance(data, six.string_types): if os.path.isdir(data): data = _load_from_dir(data) elif os.path.isfile(data): data = _load_from_file(data) return _add_data(resolver, data) if data is not None: resolver.add_metadata(data) def create_resolver(data=None): resolver = MetadataResolver() if data is None: data = YUBICO _add_data(resolver, data) return resolver python-u2flib-server-5.0.0/u2flib_server/attestation/data.py0000664000175000017500000001172013066466174024005 0ustar daindain00000000000000YUBICO = { "identifier": "2fb54029-7613-4f1d-94f1-fb876c14a6fe", "version": 4, "vendorInfo": { "url": "https://yubico.com", "imageUrl": "https://developers.yubico.com/U2F/Images/yubico.png", "name": "Yubico" }, "trustedCertificates": [ "-----BEGIN CERTIFICATE-----\nMIIDHjCCAgagAwIBAgIEG1BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ\ndWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw\nMDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290\nIENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk\n5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep\n8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw\nnebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT\n9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw\nLvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ\nhjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN\nBgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4\nMYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt\nhX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k\nLVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U\nsG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc\nU9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==\n-----END CERTIFICATE-----" ], "devices": [ { "deviceId": "1.3.6.1.4.1.41482.1.1", "displayName": "Security Key by Yubico", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/fido-u2f-security-key/", "imageUrl": "https://developers.yubico.com/U2F/Images/SKY.png", "selectors": [ { "type": "x509Extension", "parameters": { "key": "1.3.6.1.4.1.41482.1.1" } }, { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.1", "key": "1.3.6.1.4.1.41482.2" } } ] }, { "deviceId": "1.3.6.1.4.1.41482.1.2", "displayName": "YubiKey NEO/NEO-n", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/yubikey-neo/", "imageUrl": "https://developers.yubico.com/U2F/Images/NEO.png", "selectors": [ { "type": "x509Extension", "parameters": { "key": "1.3.6.1.4.1.41482.1.2" } }, { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.2", "key": "1.3.6.1.4.1.41482.2" } } ] }, { "deviceId": "1.3.6.1.4.1.41482.1.3", "displayName": "YubiKey Plus", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/", "imageUrl": "https://developers.yubico.com/U2F/Images/PLS.png", "selectors": [ { "type": "x509Extension", "parameters": { "key": "1.3.6.1.4.1.41482.1.3" } }, { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.3", "key": "1.3.6.1.4.1.41482.2" } } ] }, { "deviceId": "1.3.6.1.4.1.41482.1.4", "displayName": "YubiKey Edge", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/", "imageUrl": "https://developers.yubico.com/U2F/Images/YKE.png", "selectors": [ { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.4", "key": "1.3.6.1.4.1.41482.2" } } ] }, { "deviceId": "1.3.6.1.4.1.41482.1.5", "displayName": "YubiKey 4/YubiKey 4 Nano", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/yubikey4/", "imageUrl": "https://developers.yubico.com/U2F/Images/YK4.png", "selectors": [ { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.5", "key": "1.3.6.1.4.1.41482.2" } } ] } ] } python-u2flib-server-5.0.0/u2flib_server/attestation/matchers.py0000664000175000017500000000516313066466174024706 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from cryptography.x509 import ExtensionNotFound, ObjectIdentifier __all__ = [ 'DeviceMatcher', 'FingerprintMatcher', 'ExtensionMatcher', 'DEFAULT_MATCHERS' ] class DeviceMatcher(object): selector_type = None def matches(self, certificate, parameters=None): raise NotImplementedError class FingerprintMatcher(DeviceMatcher): selector_type = 'fingerprint' def matches(self, certificate, parameters=[]): fingerprints = [s.lower() for s in parameters] return certificate.get_fingerprint('sha1').lower() in fingerprints def _get_ext_by_oid(cert, oid): oid = ObjectIdentifier(oid) try: extension = cert.extensions.get_extension_for_oid(oid) return extension.value.value except ExtensionNotFound: return None class ExtensionMatcher(DeviceMatcher): selector_type = 'x509Extension' def matches(self, certificate, parameters={}): key = parameters.get('key') match_value = parameters.get('value') extension_value = _get_ext_by_oid(certificate, key) return extension_value is not None and ( match_value is None or match_value.encode('utf-8') == extension_value ) DEFAULT_MATCHERS = [ FingerprintMatcher(), ExtensionMatcher() ] python-u2flib-server-5.0.0/u2flib_server/attestation/metadata.py0000664000175000017500000000614713066466174024663 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from u2flib_server.attestation.model import DeviceInfo, Attestation from u2flib_server.attestation.matchers import DEFAULT_MATCHERS from u2flib_server.attestation.resolvers import create_resolver from u2flib_server.model import Transport from cryptography import x509 from cryptography.hazmat.backends import default_backend __all__ = ['MetadataProvider'] class MetadataProvider(object): def __init__(self, resolver=None, matchers=DEFAULT_MATCHERS): if resolver is None: resolver = create_resolver() self._resolver = resolver self._matchers = {} for matcher in matchers: self.add_matcher(matcher) def add_matcher(self, matcher): self._matchers[matcher.selector_type] = matcher def get_attestation(self, cert): if isinstance(cert, bytes): cert = x509.load_der_x509_certificate(cert, default_backend()) metadata = self._resolver.resolve(cert) if metadata is not None: trusted = True vendor_info = metadata.vendorInfo device_info = self._lookup_device(metadata, cert) else: trusted = False vendor_info = None device_info = DeviceInfo() cert_transports = Transport.transports_from_cert(cert) return Attestation(trusted, vendor_info, device_info, cert_transports) def _lookup_device(self, metadata, cert): for device in metadata.devices: selectors = device.selectors if selectors is None: return device for selector in selectors: matcher = self._matchers.get(selector.type) if matcher and matcher.matches(cert, selector.parameters): return device return DeviceInfo() python-u2flib-server-5.0.0/u2flib_server/__init__.py0000664000175000017500000000256213066737642022301 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. __version__ = "5.0.0" python-u2flib-server-5.0.0/u2flib_server/model.py0000664000175000017500000003531213066727735021643 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from u2flib_server.utils import websafe_encode, websafe_decode, sha_256 from cryptography import x509 from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import load_der_public_key from binascii import a2b_hex from enum import Enum, IntEnum, unique import struct import json import six import os __all__ = [ 'Transport', 'Type', 'RegistrationData', 'SignatureData', 'RegisteredKey', 'DeviceRegistration', 'ClientData', 'RegisterRequest', 'RegisterResponse', 'SignResponse', 'U2fRegisterRequest', 'U2fSignRequest' ] U2F_V2 = 'U2F_V2' TRANSPORTS_EXT_OID = x509.ObjectIdentifier('1.3.6.1.4.1.45724.2.1.1') PUB_KEY_DER_PREFIX = a2b_hex( '3059301306072a8648ce3d020106082a8648ce3d030107034200') CERTS_TO_FIX = [ a2b_hex('349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8'), a2b_hex('dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f'), a2b_hex('1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae'), a2b_hex('d0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb'), a2b_hex('6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897'), a2b_hex('ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511') ] def _parse_tlv_size(tlv): l = tlv[1] n_bytes = 1 if l > 0x80: n_bytes = l - 0x80 l = 0 for i in range(2, 2 + n_bytes): l = l * 256 + tlv[i] return 2 + n_bytes + l def _pop_bytes(data, l): x = bytes(data[:l]) del data[:l] return x def _fix_cert(der): # Some early certs have UNUSED BITS incorrectly set. if sha_256(der) in CERTS_TO_FIX: der = der[:-257] + b'\0' + der[-256:] return der def _validate_client_data(client_data, challenge, typ, valid_facets): if client_data.typ != typ: raise ValueError("Wrong type! Was: %r, expecting: %r" % ( client_data.typ, typ)) if challenge != client_data.challenge: raise ValueError("Wrong challenge! Was: %r, expecting: %r" % ( client_data.challenge, challenge)) if valid_facets is not None and client_data.origin not in valid_facets: raise ValueError("Invalid facet! Was: %r, expecting one of: %r" % ( client_data.origin, valid_facets)) @unique class Transport(IntEnum): BT = 0x01 # Bluetooth Classic BLE = 0x02 # Bluetooth Low Energy USB = 0x04 NFC = 0x08 @property def key(self): return self.name.lower() @staticmethod def transports_from_cert(cert): if isinstance(cert, bytes): cert = x509.load_der_x509_certificate(cert, default_backend()) try: ext = cert.extensions.get_extension_for_oid(TRANSPORTS_EXT_OID) der_bitstring = ext.value.value int_bytes = bytearray(der_bitstring[3:]) # Mask away unused bits (should already be 0, but make sure) unused_bits = six.indexbytes(der_bitstring, 2) int_bytes[-1] &= (0xff << unused_bits) # Reverse the bitstring and convert to integer transports = 0 for byte in int_bytes: for _ in range(8): transports = (transports << 1) | (byte & 1) byte >>= 1 return [t for t in Transport if t.value & transports] except x509.ExtensionNotFound: return None @unique class Type(Enum): REGISTER = 'navigator.id.finishEnrollment' SIGN = 'navigator.id.getAssertion' class RegistrationData(object): def __init__(self, data): buf = bytearray(data) if buf.pop(0) != 0x05: raise ValueError('Reserved byte value must be 0x05') self.pub_key = _pop_bytes(buf, 65) self.key_handle = _pop_bytes(buf, buf.pop(0)) cert_len = _parse_tlv_size(buf) self.certificate = _fix_cert(_pop_bytes(buf, cert_len)) self.signature = bytes(buf) @property def keyHandle(self): return websafe_encode(self.key_handle) @property def publicKey(self): return websafe_encode(self.pub_key) def verify(self, app_param, chal_param): cert = x509.load_der_x509_certificate(self.certificate, default_backend()) pubkey = cert.public_key() verifier = pubkey.verifier(self.signature, ec.ECDSA(hashes.SHA256())) verifier.update(b'\0' + app_param + chal_param + self.key_handle + self.pub_key) try: verifier.verify() except InvalidSignature: raise ValueError('Attestation signature is invalid') @property def bytes(self): return ( six.int2byte(0x05) + self.pub_key + six.int2byte(len(self.key_handle)) + self.key_handle + self.certificate + self.signature ) class SignatureData(object): def __init__(self, data): buf = bytearray(data) self.user_presence = buf.pop(0) self.counter = struct.unpack('>I', _pop_bytes(buf, 4))[0] self.signature = bytes(buf) def verify(self, app_param, chal_param, der_pubkey): pubkey = load_der_public_key(PUB_KEY_DER_PREFIX + der_pubkey, default_backend()) verifier = pubkey.verifier(self.signature, ec.ECDSA(hashes.SHA256())) verifier.update(app_param + six.int2byte(self.user_presence) + struct.pack('>I', self.counter) + chal_param) try: verifier.verify() except InvalidSignature: raise ValueError('U2F signature is invalid') @property def bytes(self): return ( six.int2byte(self.user_presence) + struct.pack('>I', self.counter) + self.signature ) class JSONDict(dict): _required_fields = [] def __init__(self, *args, **kwargs): if len(args) == 1 and not kwargs: arg = args[0] args = tuple() if isinstance(arg, six.text_type): kwargs = json.loads(arg) elif isinstance(arg, six.binary_type): kwargs = json.loads(arg.decode('utf-8')) else: kwargs = dict(arg) super(JSONDict, self).__init__(*args, **kwargs) missing = set(self._required_fields).difference(self.keys()) if missing: raise ValueError('Missing required fields: %s' % ', '.join(missing)) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, key)) @property def json(self): return json.dumps(self) @classmethod def wrap(cls, data): return data if isinstance(data, cls) else cls(data) class WithAppId(object): @property def applicationParameter(self): return sha_256(self['appId'].encode('idna')) class WithChallenge(object): @property def challenge(self): return websafe_decode(self['challenge']) class WithKeyHandle(object): @property def keyHandle(self): return websafe_decode(self['keyHandle']) class RegisteredKey(JSONDict, WithAppId, WithKeyHandle): _required_fields = ['version', 'keyHandle'] @property def key_data(self): data = { 'version': self['version'], 'keyHandle': self['keyHandle'] } if 'appId' in self: data['appId'] = self['appId'] if self.get('transports') is not None: data['transports'] = self['transports'] return data @property def transports(self): if 'transports' in self: return [getattr(Transport, x.upper()) for x in self['transports']] return None class DeviceRegistration(RegisteredKey): _required_fields = ['version', 'keyHandle', 'publicKey'] @property def publicKey(self): return websafe_decode(self['publicKey']) class ClientData(JSONDict, WithChallenge): _required_fields = ['typ', 'challenge', 'origin'] def __init__(self, *args, **kwargs): if len(args) == 1: data = args[0] if isinstance(data, six.binary_type): data = data.decode('utf-8') try: args = [websafe_decode(data)] except ValueError: pass # Not encoded, leave as is super(ClientData, self).__init__(*args, **kwargs) @property def typ(self): return Type(self['typ']) class WithClientData(object): @property def clientData(self): return ClientData.wrap(self['clientData']) @property def challengeParameter(self): return sha_256(websafe_decode(self['clientData'])) class RegisterRequest(JSONDict, WithAppId, WithChallenge): _required_fields = ['version', 'challenge'] class RegisterResponse(JSONDict, WithClientData): _required_fields = ['version', 'registrationData', 'clientData'] @property def registrationData(self): return RegistrationData(websafe_decode(self['registrationData'])) def verify(self, app_param): self.registrationData.verify(app_param, self.challengeParameter) class SignResponse(JSONDict, WithClientData, WithKeyHandle): _required_fields = ['keyHandle', 'signatureData', 'clientData'] @property def signatureData(self): return SignatureData(websafe_decode(self['signatureData'])) def verify(self, app_param, der_pubkey): self.signatureData.verify(app_param, self.challengeParameter, der_pubkey) class WithRegisteredKeys(object): @property def registeredKeys(self): return [RegisteredKey.wrap(x) for x in self['registeredKeys']] class U2fRegisterRequest(JSONDict, WithAppId, WithRegisteredKeys): _required_fields = ['appId', 'registerRequests', 'registeredKeys'] @property def registerRequests(self): return [RegisterRequest.wrap(x) for x in self['registerRequests']] def get_request(self, version): for req in self.registerRequests: if req.version == version: return req raise ValueError('No RegisterRequest found for version: %s' % version) @property def data_for_client(self): return { 'appId': self['appId'], 'registerRequests': self['registerRequests'], 'registeredKeys': [r.key_data for r in self.registeredKeys] } @classmethod def create(cls, app_id, registered_keys, challenge=None): if challenge is None: challenge = os.urandom(32) return cls( appId=app_id, registerRequests=[RegisterRequest( version=U2F_V2, challenge=websafe_encode(challenge) )], registeredKeys=registered_keys ) def complete(self, response, valid_facets=None): resp = RegisterResponse.wrap(response) req = self.get_request(U2F_V2) _validate_client_data(resp.clientData, req.challenge, Type.REGISTER, valid_facets) resp.verify(self.applicationParameter) registration_data = resp.registrationData transports = Transport.transports_from_cert( registration_data.certificate) transports = [t.key for t in transports] if transports else transports return DeviceRegistration( version=req.version, keyHandle=registration_data.keyHandle, appId=self.appId, publicKey=registration_data.publicKey, transports=transports, ), registration_data.certificate class U2fSignRequest(JSONDict, WithAppId, WithChallenge, WithRegisteredKeys): _required_fields = ['appId', 'challenge', 'registeredKeys'] def __init__(self, *args, **kwargs): super(U2fSignRequest, self).__init__(*args, **kwargs) if len(self.registeredKeys) == 0: raise ValueError('Must have at least one RegisteredKey') @property def data_for_client(self): return { 'appId': self['appId'], 'challenge': self['challenge'], 'registeredKeys': [r.key_data for r in self.registeredKeys] } @property def devices(self): return [DeviceRegistration.wrap(x) for x in self['registeredKeys']] @classmethod def create(cls, app_id, devices, challenge=None): if challenge is None: challenge = os.urandom(32) return cls( appId=app_id, registeredKeys=devices, challenge=websafe_encode(challenge) ) def complete(self, response, valid_facets=None): resp = SignResponse.wrap(response) _validate_client_data(resp.clientData, self.challenge, Type.SIGN, valid_facets) device = next(d for d in self.devices if d.keyHandle == resp.keyHandle) app_param = device.applicationParameter \ if 'appId' in device else self.applicationParameter resp.verify(app_param, device.publicKey) sign_data = resp.signatureData return device, sign_data.counter, sign_data.user_presence python-u2flib-server-5.0.0/u2flib_server/u2f.py0000664000175000017500000000411013066466174021224 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from u2flib_server.model import U2fRegisterRequest, U2fSignRequest __all__ = [ 'begin_registration', 'complete_registration', 'begin_authentication', 'complete_authentication' ] def begin_registration(app_id, registered_keys=[], challenge=None): return U2fRegisterRequest.create(app_id, registered_keys, challenge) def complete_registration(request, response, valid_facets=None): return U2fRegisterRequest.wrap(request).complete(response, valid_facets) def begin_authentication(app_id, devices, challenge=None): return U2fSignRequest.create(app_id, devices, challenge) def complete_authentication(request, response, valid_facets=None): return U2fSignRequest.wrap(request).complete(response, valid_facets) python-u2flib-server-5.0.0/u2flib_server/utils.py0000664000175000017500000000415713066466174021703 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # 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 HOLDER 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. from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from base64 import urlsafe_b64decode, urlsafe_b64encode import six import re BASE64URL = re.compile(br'^[-_a-zA-Z0-9]*=*$') def websafe_decode(data): if isinstance(data, six.text_type): data = data.encode('ascii') if not BASE64URL.match(data): raise ValueError('Invalid character(s)') data += b'=' * (-len(data) % 4) return urlsafe_b64decode(data) def websafe_encode(data): if isinstance(data, six.text_type): data = data.encode('ascii') return urlsafe_b64encode(data).replace(b'=', b'').decode('ascii') def sha_256(data): h = hashes.Hash(hashes.SHA256(), default_backend()) h.update(data) return h.finalize() python-u2flib-server-5.0.0/PKG-INFO0000664000175000017500000000212513067131574016500 0ustar daindain00000000000000Metadata-Version: 1.1 Name: python-u2flib-server Version: 5.0.0 Summary: Python based U2F server library Home-page: https://github.com/Yubico/python-u2flib-server Author: Yubico Open Source Maintainers Author-email: ossmaint@yubico.com License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Internet Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules python-u2flib-server-5.0.0/README0000664000175000017500000001067613066534770016301 0ustar daindain00000000000000== u2flib-server Provides functionality for working with the server side aspects of the U2F protocol as defined in the link:http://fidoalliance.org/specifications/download[FIDO specifications]. It supports Python 2.7, Python 3.3+ and PyPy 2.7+. To read more about U2F and how to use a U2F library, visit link:http://developers.yubico.com/U2F[developers.yubico.com/U2F]. === Dependencies u2flib-server depends on link:https://pypi.python.org/pypi/cryptography[cryptography], which requires libffi, OpenSSL, and a C compiler to build. On a Debian or Ubuntu system, the build dependencies can be installed with the following command: $ sudo apt-get install build-essential libssl-dev libffi-dev python-dev For Windows the cryptography project provides prebuilt wheels. For other platforms refer to link:https://cryptography.io/en/stable/installation/[cryptography installation]. === Installation u2flib-server is installable by running the following command: $ pip install python-u2flib-server ==== Check out the code Run these commands to check out the source code: git clone https://github.com/Yubico/python-u2flib-server.git cd python-u2flib-server git submodule init git submodule update ==== Build a source release To build a source release tar ball, run this command: python setup.py sdist The resulting build will be created in the dist/ subdirectory. === Example See `examples/u2f_server.py` for a working example of a HTTP server for U2F enrollment and authentication. `u2f_server.py` can be run as a stand-alone server, and can be used to test a U2F client implementation, such as python-u2flib-host, using for example cURL. The examples below show cURL command to register a U2F device, and to authenticate it. ==== Registration Registration is initiated by sending a request to the server: ---- $ curl http://localhost:8081/enroll {"appId": "http://localhost:8081", "registeredKeys": [], "registerRequests": [{"version": "U2F_V2", "challenge": "9TCtiRRLBFqMokOWfepjej99lMKQhZfm20Sgtay-FMs"}]} ---- The RegisterRequest data is then fed to the U2F client, resulting in the RegisterResponse data, which is passed back to the server: ---- $ curl http://localhost:8081/bind -d'data={"registrationData": "BQQNSrGo5bCdPyQNh1etGjidrJPBwTqittKe5DgKWyumIuGSnQxIHzM8Xd9W2eBrAJezRf7nIbxVRYkiA2G_teiEQLJa3tSyM-irgZHNXwsHC-YnfpXJ_uQkRMsgx37oAefHJI3RsBe4yCN2noa-jO1mgtgRrPK405QdcpI7xVk3XmAwggGHMIIBLqADAgECAgkAmb7osQyi7BwwCQYHKoZIzj0EATAhMR8wHQYDVQQDDBZZdWJpY28gVTJGIFNvZnQgRGV2aWNlMB4XDTEzMDcxNzE0MjEwM1oXDTE2MDcxNjE0MjEwM1owITEfMB0GA1UEAwwWWXViaWNvIFUyRiBTb2Z0IERldmljZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDvhl91zfpg9n7DeCedcQ8gGXUnemiXoi-JEAxz-EIhkVsMPAyzhtJZ4V3CqMZ-MOUgICt2aMxacMX9cIa8dgS2jUDBOMB0GA1UdDgQWBBQNqL-TV04iaO6mS5tjGE6ShfexnjAfBgNVHSMEGDAWgBQNqL-TV04iaO6mS5tjGE6ShfexnjAMBgNVHRMEBTADAQH_MAkGByqGSM49BAEDSAAwRQIgXJWZdbvOWdhVaG7IJtn44o21Kmi8EHsDk4cAfnZ0r38CIQD6ZPi3Pl4lXxbY7BXFyrpkiOvCpdyNdLLYbSTbvIBQOTBEAiBs0qu8RRZDf4qJo5qnHOd6hNDu9aEyNGQCeHp47D6-9gIgST3rq1JrUn_xvPh5AAGsn64cLvJlF_V0MF2A73tkLOc", "clientData": "eyJvcmlnaW4iOiAiaHR0cDovL2xvY2FsaG9zdDo4MDgxIiwgImNoYWxsZW5nZSI6ICI5VEN0aVJSTEJGcU1va09XZmVwamVqOTlsTUtRaFpmbTIwU2d0YXktRk1zIiwgInR5cCI6ICJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCJ9","version":"U2F_V2"}' true ---- The result, "true", indicates that registration was successful. ==== Authentication Authentication for a previously registered U2F device is done by sending a request to the server: ---- $ curl http://localhost:8081/sign {"appId": "http://localhost:8081", "registeredKeys": [{"version": "U2F_V2", "appId": "http://localhost:8081", "keyHandle": "slre1LIz6KuBkc1fCwcL5id-lcn-5CREyyDHfugB58ckjdGwF7jII3aehr6M7WaC2BGs8rjTlB1ykjvFWTdeYA"}], "challenge": "FnueX-NpT9kB7I41dc8DvPXU1-yj7oO_cBT3e9PWOAw"} ---- The AuthenticateRequest data is then fed to the U2F client, resulting in an AuthenticateResponse object which is passed back to the server: ---- $ curl http://localhost:8081/verify -d'data={"keyHandle": "slre1LIz6KuBkc1fCwcL5id-lcn-5CREyyDHfugB58ckjdGwF7jII3aehr6M7WaC2BGs8rjTlB1ykjvFWTdeYA", "signatureData": "AQAAAAEwRgIhALhe7LTwnBHTPQQIGbn_wPR80S7-HPPliZh966vL3VeiAiEA35w-BVDROwdLGlztLgejw9bnXSrYY0-3EC-_qhi0XaI", "clientData": "eyJvcmlnaW4iOiAiaHR0cDovL2xvY2FsaG9zdDo4MDgxIiwgImNoYWxsZW5nZSI6ICJGbnVlWC1OcFQ5a0I3STQxZGM4RHZQWFUxLXlqN29PX2NCVDNlOVBXT0F3IiwgInR5cCI6ICJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIn0"}' {"touch": 1, "counter": 1} ---- The response indicates success, giving the U2F devices internal counter value, as well as the value of the user presence parameter. python-u2flib-server-5.0.0/MANIFEST.in0000664000175000017500000000012513067130316017130 0ustar daindain00000000000000include release.py include COPYING include NEWS include ChangeLog include examples/* python-u2flib-server-5.0.0/setup.cfg0000664000175000017500000000022613067131574017224 0ustar daindain00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 80 exclude = .*/, u2flib_server/attestation/data.py [egg_info] tag_build = tag_date = 0 python-u2flib-server-5.0.0/COPYING0000664000175000017500000000243013040135032016415 0ustar daindain00000000000000Copyright (c) 2014 Yubico AB 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. 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.