python-u2flib-server-3.2.0/0000775000175000017500000000000012537764006015406 5ustar daindain00000000000000python-u2flib-server-3.2.0/test/0000775000175000017500000000000012537764006016365 5ustar daindain00000000000000python-u2flib-server-3.2.0/test/test_u2f_v2.py0000664000175000017500000000710312537761741021105 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 import u2f_v2 as u2f from soft_u2f_v2 import SoftU2FDevice import unittest APP_ID = 'http://www.example.com/appid' FACET = 'https://www.example.com' FACETS = [FACET] class U2fV2Test(unittest.TestCase): def test_register_soft_u2f(self): token = SoftU2FDevice() request = u2f.start_register(APP_ID) response = token.register(request.json, FACET) device, cert = u2f.complete_register(request, response) assert device def test_authenticate_soft_u2f(self): token = SoftU2FDevice() request = u2f.start_register(APP_ID) response = token.register(request.json, FACET) device, cert = u2f.complete_register(request, response) challenge1 = u2f.start_authenticate(device) challenge2 = u2f.start_authenticate(device) response2 = token.getAssertion(challenge2.json, FACET) response1 = token.getAssertion(challenge1.json, FACET) assert u2f.verify_authenticate(device, challenge1, response1) assert u2f.verify_authenticate(device, challenge2, response2) try: u2f.verify_authenticate(device, challenge1, response2) except: pass else: assert False, "Incorrect validation should fail!" try: u2f.verify_authenticate(device, challenge2, response1) except: pass else: assert False, "Incorrect validation should fail!" def test_wrong_facet(self): token = SoftU2FDevice() request = u2f.start_register(APP_ID) response = token.register(request.json, "http://wrongfacet.com") try: u2f.complete_register(request, response, FACETS) except: pass else: assert False, "Incorrect facet should fail!" response2 = token.register(request.json, FACET) device, cert = u2f.complete_register(request, response2) challenge = u2f.start_authenticate(device) response = token.getAssertion(challenge.json, "http://notright.com") try: u2f.verify_authenticate(device, challenge, response, FACETS) except: pass else: assert False, "Incorrect facet should fail!" python-u2flib-server-3.2.0/test/__init__.py0000664000175000017500000000000012537761414020464 0ustar daindain00000000000000python-u2flib-server-3.2.0/test/test_attestation.py0000664000175000017500000000715412537755764022357 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.metadata import MetadataProvider from u2flib_server.attestation.resolvers import create_resolver from u2flib_server.attestation.data import YUBICO from M2Crypto import X509 import json import unittest ATTESTATION_CERT = """ MIICGzCCAQWgAwIBAgIEdaP2dTALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBS b290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBa MCoxKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE5NzM2Nzk3MzMwWTATBgcqhkjOPQIB BggqhkjOPQMBBwNCAAQZo35Damtpl81YdmcbhEuXKAr7xDcQzAy5n3ftAAhtBbu8EeGU4ynfSgLo nckqX6J2uXLBppTNE3v2bt+Yf8MLoxIwEDAOBgorBgEEAYLECgECBAAwCwYJKoZIhvcNAQELA4IB AQC9LbiNPgs0sQYOHAJcg+lMk+HCsiWRlYVnbT4I/5lnqU907vY17XYAORd432bU3Nnhsbkvjz76 kQJGXeNAF4DPANGGlz8JU+LNEVE2PWPGgEM0GXgB7mZN5Sinfy1AoOdO+3c3bfdJQuXlUxHbo+nD pxxKpzq9gr++RbokF1+0JBkMbaA/qLYL4WdhY5NvaOyMvYpO3sBxlzn6FcP67hlotGH1wU7qhCeh +uur7zDeAWVh7c4QtJOXHkLJQfV3Z7ZMvhkIA6jZJAX99hisABU/SSa5DtgX7AfsHwa04h69AAAW DUzSk3HgOXbUd1FaSOPdlVFkG2N2JllFHykyO3zO """.replace('\n', '').decode('base64') class AttestationTest(unittest.TestCase): def test_resolver(self): resolver = create_resolver(YUBICO) cert = X509.load_cert_der_string(ATTESTATION_CERT) metadata = resolver.resolve(cert) assert metadata.identifier == '2fb54029-7613-4f1d-94f1-fb876c14a6fe' def test_provider(self): provider = MetadataProvider() cert = X509.load_cert_der_string(ATTESTATION_CERT) attestation = provider.get_attestation(cert) assert attestation.trusted 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) cert = X509.load_cert_der_string(ATTESTATION_CERT) metadata = resolver.resolve(cert) assert metadata is None def test_versioning_older(self): resolver = create_resolver(YUBICO) newer = json.loads(json.dumps(YUBICO)) newer['trustedCertificates'] = [] resolver.add_metadata(newer) cert = X509.load_cert_der_string(ATTESTATION_CERT) metadata = resolver.resolve(cert) assert metadata.identifier == '2fb54029-7613-4f1d-94f1-fb876c14a6fe' python-u2flib-server-3.2.0/test/test_u2f_highlevel.py0000664000175000017500000000547412537756107022536 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 import u2f from soft_u2f_v2 import SoftU2FDevice import unittest APP_ID = 'https://www.example.com' FACET = APP_ID def register_token(devices=[]): token = SoftU2FDevice() request_data = u2f.start_register(APP_ID, devices) response = token.register(request_data.registerRequests[0].json, FACET) device, cert = u2f.complete_register(request_data, response) return device, token class AttestationTest(unittest.TestCase): 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 sign_request = u2f.start_authenticate([device]) response1 = token.getAssertion( sign_request.authenticateRequests[0].json, FACET ) assert u2f.verify_authenticate([device], sign_request, response1) def test_authenticate_multiple_soft_u2f(self): # Register device1, token1 = register_token() device2, token2 = register_token([device1]) # Authenticate auth_request_data = u2f.start_authenticate([device1, device2]) response = token1.getAssertion( auth_request_data.authenticateRequests[0].json, FACET ) assert u2f.verify_authenticate([device1, device2], auth_request_data, response) python-u2flib-server-3.2.0/test/test_serialization.py0000664000175000017500000000342412537756341022661 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. import unittest from u2flib_server import u2f_v2 as u2f from u2flib_server.jsapi import RegisterRequest class SerializationTest(unittest.TestCase): def test_enroll_serialization(self): enroll1 = u2f.start_register('https://example.com') enroll2 = RegisterRequest(enroll1.json) assert enroll1.appId == enroll2.appId assert enroll1.json == enroll2.json if __name__ == '__main__': unittest.main() python-u2flib-server-3.2.0/test/soft_u2f_v2.py0000664000175000017500000001334512537565201021077 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 M2Crypto import EC, BIO from u2flib_server.utils import (websafe_encode, websafe_decode, sha_256 as H, rand_bytes) from u2flib_server.jsapi import (RegisterRequest, RegisterResponse, SignRequest, SignResponse, ClientData) import struct CURVE = EC.NID_X9_62_prime256v1 CERT = """ MIIBhzCCAS6gAwIBAgIJAJm+6LEMouwcMAkGByqGSM49BAEwITEfMB0GA1UEAwwW WXViaWNvIFUyRiBTb2Z0IERldmljZTAeFw0xMzA3MTcxNDIxMDNaFw0xNjA3MTYx NDIxMDNaMCExHzAdBgNVBAMMFll1YmljbyBVMkYgU29mdCBEZXZpY2UwWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAAQ74Zfdc36YPZ+w3gnnXEPIBl1J3pol6IviRAMc /hCIZFbDDwMs4bSWeFdwqjGfjDlICArdmjMWnDF/XCGvHYEto1AwTjAdBgNVHQ4E FgQUDai/k1dOImjupkubYxhOkoX3sZ4wHwYDVR0jBBgwFoAUDai/k1dOImjupkub YxhOkoX3sZ4wDAYDVR0TBAUwAwEB/zAJBgcqhkjOPQQBA0gAMEUCIFyVmXW7zlnY VWhuyCbZ+OKNtSpovBB7A5OHAH52dK9/AiEA+mT4tz5eJV8W2OwVxcq6ZIjrwqXc jXSy2G0k27yAUDk= """.decode('base64') CERT_PRIV = """ -----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, request, facet="https://www.example.com"): """ 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='navigator.id.finishEnrollment', challenge=request['challenge'], origin=facet ) client_data = client_data.json client_param = H(client_data) # ECC key generation privu = EC.gen_params(CURVE) privu.gen_key() pub_key = str(privu.pub().get_der())[-65:] # Store key_handle = rand_bytes(64) app_param = request.appParam self.keys[key_handle] = (privu, app_param) # Attestation signature cert_priv = EC.load_key_bio(BIO.MemoryBuffer(CERT_PRIV)) cert = CERT digest = H(chr(0x00) + app_param + client_param + key_handle + pub_key) signature = cert_priv.sign_dsa_asn1(digest) raw_response = chr(0x05) + pub_key + chr(len(key_handle)) + \ key_handle + cert + signature return RegisterResponse( registrationData=websafe_encode(raw_response), clientData=websafe_encode(client_data), ) def getAssertion(self, request, facet="https://www.example.com", touch=False): """ signData = { 'version': "U2F_V2", 'challenge': websafe_encode(self.challenge), 'appId': self.binding.app_id, 'keyHandle': websafe_encode(self.binding.key_handle), } """ if not isinstance(request, SignRequest): request = SignRequest(request) if request.version != "U2F_V2": raise ValueError("Unsupported U2F version: %s" % request.version) key_handle = websafe_decode(request.keyHandle) if key_handle not in self.keys: raise ValueError("Unknown key handle!") # Client data client_data = ClientData( typ="navigator.id.getAssertion", challenge=request['challenge'], origin=facet ) client_data = client_data.json client_param = H(client_data) # Unwrap: privu, app_param = self.keys[key_handle] # Increment counter self.counter += 1 # Create signature touch = chr(1 if touch else 0) counter = struct.pack('>I', self.counter) digest = H(app_param + touch + counter + client_param) signature = privu.sign_dsa_asn1(digest) raw_response = touch + counter + signature return SignResponse( clientData=websafe_encode(client_data), signatureData=websafe_encode(raw_response), keyHandle=request.keyHandle ) python-u2flib-server-3.2.0/examples/0000775000175000017500000000000012537764006017224 5ustar daindain00000000000000python-u2flib-server-3.2.0/examples/yubiauth_server.py0000664000175000017500000001410012537565201023006 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. """ Example web server providing U2F enrollment and authentication. It can be run standalone, or by a WSGI container such as Apache with mod_wsgi. A YubiAuth installation is required to store users and their enrollment data. Enrollment will overwrite existing users. All users will have a u2f_ prefix added to their usernames. 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! """ from yubiauth import YubiAuth from u2flib_server.u2f_v2 import (start_register, complete_register, start_authenticate, verify_authenticate) from webob.dec import wsgify from webob import exc import json import traceback 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 a U2F device with a YubiAuth user, and to perform a sign with the enrolled device. Only one device per uses is supported, and only one challenge is valid at a time. Four calls are provided: enroll, bind, sign and verify. Each of these expects username and password parameters, and bind and verify expect a third parameter, data, containing the JSON formatted data which is output by the U2F browser API upon calling the ENROLL or SIGN commands. """ @wsgify def __call__(self, request): self.origin = get_origin(request.environ) self.app_id = self.origin page = request.path_info_pop() # To be able to see what the server considers its origin to be: if page == 'origin': return self.origin elif page is None: return json.dumps([self.origin]) with YubiAuth() as auth: try: username = 'u2f_' + request.params['username'] password = request.params['password'] data = request.params.get('data', None) self.auth = auth if page == 'enroll': return self.enroll(username, password) elif page == 'bind': return self.bind(username, password, data) elif page == 'sign': return self.sign(username, password) elif page == 'verify': return self.verify(username, password, data) else: raise exc.HTTPNotFound() except Exception: return exc.HTTPBadRequest(comment=traceback.format_exc()) def enroll(self, username, password): try: user = self.auth.get_user(username) user.set_password(password) except: user = self.auth.create_user(username, password) enroll = start_register(self.app_id) user.attributes['_u2f_enroll_'] = enroll.json return enroll.json def bind(self, username, password, data): user = self._get_user(username, password) enroll = user.attributes['_u2f_enroll_'] binding, cert = complete_register(enroll, data, [self.origin]) user.attributes['_u2f_binding_'] = binding.json user.attributes['_u2f_cert_'] = cert.as_pem() return json.dumps({ 'username': username[4:], 'origin': self.origin, 'attest_cert': cert.as_pem() }) def sign(self, username, password): user = self._get_user(username, password) binding = user.attributes['_u2f_binding_'] challenge = start_authenticate(binding) user.attributes['_u2f_challenge_'] = challenge.json return challenge.json def verify(self, username, password, data): user = self._get_user(username, password) binding = user.attributes['_u2f_binding_'] challenge = user.attributes['_u2f_challenge_'] c, t = verify_authenticate(binding, challenge, data, [self.origin]) return json.dumps({ 'touch': t, 'counter': c }) def _get_user(self, username, password): user = self.auth.get_user(username) if not user.validate_password(password): raise ValueError('Invalid password!') return user application = U2FServer() if __name__ == '__main__': from wsgiref.simple_server import make_server httpd = make_server('0.0.0.0', 8081, application) httpd.serve_forever() python-u2flib-server-3.2.0/examples/u2f_server.py0000775000175000017500000001457212537565331021674 0ustar daindain00000000000000#!/usr/bin/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.jsapi import DeviceRegistration from u2flib_server.u2f import (start_register, complete_register, start_authenticate, verify_authenticate) 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] devices = map(DeviceRegistration.wrap, user.get('_u2f_devices_', [])) enroll = start_register(self.app_id, devices) user['_u2f_enroll_'] = enroll.json return enroll.json def bind(self, username, data): user = self.users[username] binding, cert = complete_register(user.pop('_u2f_enroll_'), data, [self.facet]) devices = map(DeviceRegistration.wrap, user.get('_u2f_devices_', [])) devices.append(binding) user['_u2f_devices_'] = [d.json for d in devices] log.info("U2F device enrolled. Username: %s", username) log.debug("Attestation certificate:\n%s", cert.as_text()) return json.dumps(True) def sign(self, username): user = self.users[username] devices = map(DeviceRegistration.wrap, user.get('_u2f_devices_', [])) challenge = start_authenticate(devices) user['_u2f_challenge_'] = challenge.json return challenge.json def verify(self, username, data): user = self.users[username] devices = map(DeviceRegistration.wrap, user.get('_u2f_devices_', [])) challenge = user.pop('_u2f_challenge_') c, t = verify_authenticate(devices, challenge, data, [self.facet]) return json.dumps({ '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-3.2.0/ChangeLog0000664000175000017500000002262212537764006017164 0ustar daindain000000000000002015-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_highlevel.py, test/test_u2f_multiple.py: Rename test. 2015-01-19 Dain Nilsson * test/test_u2f_multiple.py, u2flib_server/u2f.py, u2flib_server/u2f_multiple.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 * test/soft_u2f_v0.py, test/soft_u2f_v2.py, test/test_u2f_v0.py, test/test_u2f_v2.py, u2flib_server/soft_u2f_v0.py, u2flib_server/soft_u2f_v2.py: Moved SoftU2FDevice to test/ 2014-02-13 Dain Nilsson * Initial import python-u2flib-server-3.2.0/PKG-INFO0000664000175000017500000000134112537764006016502 0ustar daindain00000000000000Metadata-Version: 1.1 Name: python-u2flib-server Version: 3.2.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: Programming Language :: Python Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Topic :: Internet Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules python-u2flib-server-3.2.0/u2flib_server/0000775000175000017500000000000012537764006020157 5ustar daindain00000000000000python-u2flib-server-3.2.0/u2flib_server/__init__.py0000664000175000017500000000256212537566112022273 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__ = "3.2.0" python-u2flib-server-3.2.0/u2flib_server/jsapi.py0000664000175000017500000001120612537565201021633 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_decode, sha_256 import json __all__ = [ 'ClientData', 'DeviceRegistration', 'RegisterRequest', 'RegisterResponse', 'SignRequest', 'SignResponse', 'RegisterRequestData', 'AuthenticateRequestData' ] class JSONDict(dict): def __init__(self, *args, **kwargs): if len(args) == 1: data = args[0] elif len(args) == 0: data = kwargs else: raise TypeError("Wrong number of arguments given!") if isinstance(data, basestring): self.update(json.loads(data)) elif isinstance(data, dict): self.update(data) else: raise TypeError("Unexpected type! Expected one of dict or string") 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 appParam(self): return sha_256(self['appId'].encode('idna')) class WithChallenge(object): @property def challenge(self): return websafe_decode(self['challenge']) class DeviceRegistration(JSONDict, WithAppId): pass class ClientData(JSONDict, WithChallenge): pass class WithClientData(object): @property def clientData(self): return ClientData(websafe_decode(self['clientData'])) @property def clientParam(self): return sha_256(websafe_decode(self['clientData'])) class RegisterRequest(JSONDict, WithAppId, WithChallenge): pass class RegisterResponse(JSONDict, WithClientData): @property def registrationData(self): return websafe_decode(self['registrationData']) class SignRequest(JSONDict, WithAppId, WithChallenge): pass class SignResponse(JSONDict, WithClientData): @property def signatureData(self): return websafe_decode(self['signatureData']) class RegisterRequestData(JSONDict): @property def authenticateRequests(self): return map(SignRequest, self['authenticateRequests']) @property def registerRequests(self): return map(RegisterRequest, self['registerRequests']) def getRegisterRequest(self, response): return self.registerRequests[0] class AuthenticateRequestData(JSONDict): @property def authenticateRequests(self): return map(SignRequest, self['authenticateRequests']) def getAuthenticateRequest(self, response): return next(req for req in self.authenticateRequests if req.keyHandle == response.keyHandle) # # Metadata # 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 map(Selector, selectors) class MetadataObject(JSONDict): @property def vendorInfo(self): return VendorInfo(self['vendorInfo']) @property def devices(self): return map(DeviceInfo, self['devices']) python-u2flib-server-3.2.0/u2flib_server/u2f.py0000664000175000017500000000611012537565201021217 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 import u2f_v2 from u2flib_server.jsapi import (AuthenticateRequestData, RegisterRequestData, SignResponse, RegisterResponse) from u2flib_server.utils import rand_bytes __all__ = [ 'start_register', 'complete_register', 'start_authenticate', 'verify_authenticate' ] def start_register(app_id, devices, challenge=None): # RegisterRequest register_request = u2f_v2.start_register(app_id, challenge) # SignRequest[] sign_requests = start_authenticate( devices, 'check-only' ).authenticateRequests return RegisterRequestData( registerRequests=[register_request], authenticateRequests=sign_requests ) def complete_register(request_data, response, valid_facets=None): request_data = RegisterRequestData.wrap(request_data) response = RegisterResponse.wrap(response) return u2f_v2.complete_register(request_data.getRegisterRequest(response), response, valid_facets) def start_authenticate(devices, challenge=None): sign_requests = [u2f_v2.start_authenticate(d, challenge or rand_bytes(32)) for d in devices] return AuthenticateRequestData(authenticateRequests=sign_requests) def verify_authenticate(devices, request_data, response, valid_facets=None): request_data = AuthenticateRequestData.wrap(request_data) response = SignResponse.wrap(response) sign_request = request_data.getAuthenticateRequest(response) device = next(d for d in devices if d.keyHandle == sign_request.keyHandle) return u2f_v2.verify_authenticate( device, sign_request, response, valid_facets ) python-u2flib-server-3.2.0/u2flib_server/attestation/0000775000175000017500000000000012537764006022516 5ustar daindain00000000000000python-u2flib-server-3.2.0/u2flib_server/attestation/__init__.py0000664000175000017500000000305312537565201024624 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, Attestation __all__ = ['create_resolver', 'MetadataProvider', 'Attestation'] python-u2flib-server-3.2.0/u2flib_server/attestation/resolvers.py0000664000175000017500000000763212537565201025120 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. __all__ = ['MetadataResolver', 'create_resolver'] from M2Crypto import X509 from u2flib_server.jsapi import MetadataObject from u2flib_server.attestation.data import YUBICO import os import json 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: cert_der = ''.join(cert_pem.splitlines()[1:-1]).decode('base64') cert = X509.load_cert_der_string(cert_der) subject = cert.get_subject().as_text() if subject not in self._certs: self._certs[subject] = [] self._certs[subject].append(cert) self._metadata[cert] = metadata def resolve(self, cert): for issuer in self._certs.get(cert.get_issuer().as_text(), []): if cert.verify(issuer.get_pubkey()) == 1: 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): return map(_load_from_file, [os.path.join(dname, d) for d in os.listdir(dname) if d.endswith('.json')]) def _add_data(resolver, data): if isinstance(data, list): for d in data: _add_data(resolver, d) return elif isinstance(data, basestring): 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-3.2.0/u2flib_server/attestation/matchers.py0000664000175000017500000000546312537565201024702 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. __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 = map(lambda s: s.lower(), parameters) return certificate.get_fingerprint('sha1').lower() in fingerprints # This is needed since older versions of M2Crypto don't have a way of getting # extensions by their OID. def get_ext_by_oid(cert, oid): from pyasn1.codec.der import decoder from pyasn1_modules import rfc2459 cert, _ = decoder.decode(cert.as_der(), asn1Spec=rfc2459.Certificate()) for ext in cert['tbsCertificate']['extensions']: if ext['extnID'].prettyPrint() == oid: return decoder.decode(ext['extnValue'])[0].asOctets() 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) if extension_value is not None: if match_value is None or match_value == extension_value: return True return False DEFAULT_MATCHERS = [ FingerprintMatcher(), ExtensionMatcher() ] python-u2flib-server-3.2.0/u2flib_server/attestation/data.py0000664000175000017500000001024712511447073023777 0ustar daindain00000000000000YUBICO = { "identifier": "2fb54029-7613-4f1d-94f1-fb876c14a6fe", "version": 2, "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-----"], "vendorInfo": { "name": "Yubico", "url": "https://yubico.com", "imageUrl": "https://developers.yubico.com/U2F/Images/yubico.png" }, "devices": [ { "displayName": "Security Key by Yubico", "deviceId": "1.3.6.1.4.1.41482.1.1", "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": { "key": "1.3.6.1.4.1.41482.2", "value": "1.3.6.1.4.1.41482.1.1" } } ] }, { "displayName": "YubiKey NEO/NEO-n", "deviceId": "1.3.6.1.4.1.41482.1.2", "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": { "key": "1.3.6.1.4.1.41482.2", "value": "1.3.6.1.4.1.41482.1.2" } } ] }, { "displayName": "YubiKey Plus", "deviceId": "1.3.6.1.4.1.41482.1.3", "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": { "key": "1.3.6.1.4.1.41482.2", "value": "1.3.6.1.4.1.41482.1.3" } } ] }, { "displayName": "YubiKey Edge", "deviceId": "1.3.6.1.4.1.41482.1.4", "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/", "imageUrl": "https://developers.yubico.com/U2F/Images/YKE.png", "selectors": [ { "type": "x509Extension", "parameters": { "key": "1.3.6.1.4.1.41482.2", "value": "1.3.6.1.4.1.41482.1.4" } } ] } ] } python-u2flib-server-3.2.0/u2flib_server/attestation/metadata.py0000664000175000017500000000611412537565201024646 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.matchers import DEFAULT_MATCHERS from u2flib_server.attestation.resolvers import create_resolver __all__ = ['Attestation', 'MetadataProvider'] class Attestation(object): def __init__(self, trusted, vendor_info=None, device_info=None): self._trusted = trusted self._vendor_info = vendor_info self._device_info = device_info @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 UNKNOWN_ATTESTATION = Attestation(False) 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): metadata = self._resolver.resolve(cert) if metadata is None: return UNKNOWN_ATTESTATION vendor_info = metadata.vendorInfo device_info = self._lookup_device(metadata, cert) return Attestation(True, vendor_info, device_info) 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 None python-u2flib-server-3.2.0/u2flib_server/yubicommon/0000775000175000017500000000000012537764006022340 5ustar daindain00000000000000python-u2flib-server-3.2.0/u2flib_server/yubicommon/__init__.py0000664000175000017500000001444112537762367024465 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__ = '0.1.0' __dependencies__ = [] __all__ = ['get_version', 'setup', 'release'] from setuptools import setup as _setup, find_packages, Command from distutils import log from distutils.errors import DistutilsSetupError from datetime import date import os import re VERSION_PATTERN = re.compile(r"(?m)^__version__\s*=\s*['\"](.+)['\"]$") DEPENDENCY_PATTERN = re.compile( r"(?m)__dependencies__\s*=\s*\[((['\"].+['\"]\s*(,\s*)?)+)\]") def get_version(module_name=None): """Return the current version as defined by the given module.""" if module_name is None: parts = __name__.split('.') module_name = parts[0] if len(parts) > 1 else find_packages()[0] with open('%s/__init__.py' % module_name, 'r') as f: match = VERSION_PATTERN.search(f.read()) return match.group(1) def get_dependencies(module): basedir = os.path.dirname(__file__) fn = os.path.join(basedir, module, '__init__.py') if os.path.isfile(fn): with open(fn, 'r') as f: match = DEPENDENCY_PATTERN.search(f.read()) if match: return map(lambda s: s.strip().strip('"\''), match.group(1).split(',')) return [] def get_package(module): return __name__ + '.' + module def setup(**kwargs): if 'version' not in kwargs: kwargs['version'] = get_version() packages = kwargs.setdefault('packages', find_packages(exclude=[__name__ + '.*'])) install_requires = kwargs.setdefault('install_requires', []) for yc_module in kwargs.pop('yc_requires', []): packages.append(get_package(yc_module)) for dep in get_dependencies(yc_module): if dep not in install_requires: install_requires.append(dep) cmdclass = kwargs.setdefault('cmdclass', {}) cmdclass.setdefault('release', release) return _setup(**kwargs) 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 _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.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-3.2.0/u2flib_server/utils.py0000664000175000017500000000407112537565201021667 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 M2Crypto import EC, Rand from base64 import urlsafe_b64decode, urlsafe_b64encode from hashlib import sha256 import os PUB_KEY_DER_PREFIX = "3059301306072a8648ce3d020106082a8648ce3d030107034200" \ .decode('hex') def pub_key_from_der(der): return EC.pub_key_from_der(PUB_KEY_DER_PREFIX + der) def websafe_decode(data): if isinstance(data, unicode): data = data.encode('utf-8') data += '=' * (-len(data) % 4) return urlsafe_b64decode(data) def websafe_encode(data): return urlsafe_b64encode(data).replace('=', '') def sha_256(data): h = sha256() h.update(data) return h.digest() Rand.rand_seed(os.urandom(1024)) def rand_bytes(n_bytes): return Rand.rand_bytes(n_bytes) python-u2flib-server-3.2.0/u2flib_server/u2f_v2.py0000664000175000017500000001712312537565201021634 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 M2Crypto import X509 from u2flib_server.jsapi import (RegisterRequest, RegisterResponse, SignRequest, SignResponse, DeviceRegistration) from u2flib_server.utils import (pub_key_from_der, sha_256, websafe_decode, websafe_encode, rand_bytes) import struct __all__ = [ 'start_register', 'complete_register', 'start_authenticate', 'verify_authenticate' ] VERSION = 'U2F_V2' FIXSIG = [ 'CN=Yubico U2F EE Serial 776137165', 'CN=Yubico U2F EE Serial 1086591525', 'CN=Yubico U2F EE Serial 1973679733', 'CN=Yubico U2F EE Serial 13503277888', 'CN=Yubico U2F EE Serial 13831167861', 'CN=Yubico U2F EE Serial 14803321578' ] class RawRegistrationResponse(object): """ Object representing a raw registration response. registrationData = 0x05, pubkey, kh_len, key_handle, cert, signature """ PUBKEY_LEN = 65 def __init__(self, app_param, chal_param, data): self.app_param = app_param self.chal_param = chal_param self.data = data if ord(data[0]) != 0x05: raise ValueError("Invalid data: %s" % data.encode('hex')) data = data[1:] self.pub_key = data[:self.PUBKEY_LEN] data = data[self.PUBKEY_LEN:] kh_len = ord(data[0]) data = data[1:] self.key_handle = data[:kh_len] data = data[kh_len:] self.certificate = self._fixsig(X509.load_cert_der_string(data)) self.signature = data[len(self.certificate.as_der()):] def __str__(self): return self.data.encode('hex') def verify_csr_signature(self): data = chr(0x00) + self.app_param + self.chal_param + \ self.key_handle + self.pub_key pubkey = self.certificate.get_pubkey() pubkey.reset_context('sha256') pubkey.verify_init() pubkey.verify_update(data) if not pubkey.verify_final(self.signature) == 1: raise Exception('Attestation signature verification failed!') def _fixsig(self, cert): subject = cert.get_subject().as_text() if subject in FIXSIG: # Set unused bits in signature to 0 der = list(cert.as_der()) der[-257] = chr(0) cert = X509.load_cert_der_string(''.join(der)) return cert def serialize(self): return websafe_encode(self.app_param + self.chal_param + self.data) @classmethod def deserialize(cls, serialized): data = websafe_decode(serialized) return cls(data[:32], data[32:64], data[64:]) class RawAuthenticationResponse(object): """ Object representing a raw authentication response. authenticationData = touch, counter, signature """ def __init__(self, app_param, chal_param, data): self.app_param = app_param self.chal_param = chal_param self.data = data self.user_presence = data[0] self.counter = data[1:5] self.counter_int = struct.unpack('>I', self.counter)[0] self.signature = data[5:] def __str__(self): return self.data.encode('hex') def verify_signature(self, pubkey): data = self.app_param + self.user_presence + self.counter + \ self.chal_param digest = sha_256(data) pub_key = pub_key_from_der(pubkey) if not pub_key.verify_dsa_asn1(digest, self.signature) == 1: raise Exception('Challenge signature verification failed!') def serialize(self): return websafe_encode(self.app_param + self.chal_param + self.data) @classmethod def deserialize(cls, serialized): data = websafe_decode(serialized) return cls(data[:32], data[32:64], data[64:]) def _validate_client_data(client_data, challenge, typ, valid_facets): """ Validate the client data. clientData = { "typ": string, "challenge": string, //b64 encoded challenge. "origin": string, //Facet used } """ if client_data.typ != typ: raise ValueError("Wrong type! Was: %s, expecting: %s" % ( client_data.typ, typ)) if challenge != client_data.challenge: raise ValueError("Wrong challenge! Was: %s, expecting: %s" % ( client_data.challenge.encode('hex'), challenge.encode('hex'))) if valid_facets is not None and client_data.origin not in valid_facets: raise ValueError("Invalid facet! Was: %s, expecting one of: %r" % ( client_data.origin, valid_facets)) def start_register(app_id, challenge=None): if challenge is None: challenge = rand_bytes(32) return RegisterRequest( version=VERSION, appId=app_id, challenge=websafe_encode(challenge) ) def complete_register(request, response, valid_facets=None): request = RegisterRequest.wrap(request) response = RegisterResponse.wrap(response) _validate_client_data(response.clientData, request.challenge, "navigator.id.finishEnrollment", valid_facets) raw_response = RawRegistrationResponse( request.appParam, response.clientParam, response.registrationData ) raw_response.verify_csr_signature() return DeviceRegistration( appId=request.appId, keyHandle=websafe_encode(raw_response.key_handle), publicKey=websafe_encode(raw_response.pub_key) ), raw_response.certificate def start_authenticate(device, challenge=None): device = DeviceRegistration.wrap(device) if challenge is None: challenge = rand_bytes(32) return SignRequest( version=VERSION, appId=device.appId, keyHandle=device.keyHandle, challenge=websafe_encode(challenge) ) def verify_authenticate(device, request, response, valid_facets=None): device = DeviceRegistration.wrap(device) request = SignRequest.wrap(request) response = SignResponse.wrap(response) _validate_client_data(response.clientData, request.challenge, "navigator.id.getAssertion", valid_facets) raw_response = RawAuthenticationResponse( device.appParam, response.clientParam, response.signatureData ) raw_response.verify_signature(websafe_decode(device.publicKey)) return raw_response.counter_int, raw_response.user_presence python-u2flib-server-3.2.0/COPYING0000644000175000017500000000243012537565370016441 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. python-u2flib-server-3.2.0/setup.py0000775000175000017500000000443412537757235017136 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.yubicommon import setup 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', setup_requires=['nose>=1.0'], install_requires=['M2Crypto', 'pyasn1>=0.1.7', 'pyasn1-modules'], test_suite='test', tests_require=[], classifiers=[ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Topic :: Internet', 'Topic :: Security :: Cryptography', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) python-u2flib-server-3.2.0/MANIFEST.in0000664000175000017500000000010212537752307017136 0ustar daindain00000000000000include COPYING include NEWS include ChangeLog include examples/* python-u2flib-server-3.2.0/python_u2flib_server.egg-info/0000775000175000017500000000000012537764006023252 5ustar daindain00000000000000python-u2flib-server-3.2.0/python_u2flib_server.egg-info/PKG-INFO0000664000175000017500000000134112537764006024346 0ustar daindain00000000000000Metadata-Version: 1.1 Name: python-u2flib-server Version: 3.2.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: Programming Language :: Python Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Topic :: Internet Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules python-u2flib-server-3.2.0/python_u2flib_server.egg-info/requires.txt0000664000175000017500000000004612537764006025652 0ustar daindain00000000000000M2Crypto pyasn1>=0.1.7 pyasn1-modules python-u2flib-server-3.2.0/python_u2flib_server.egg-info/SOURCES.txt0000664000175000017500000000144712537764006025144 0ustar daindain00000000000000COPYING ChangeLog MANIFEST.in NEWS README setup.cfg setup.py examples/u2f_server.py examples/yubiauth_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_serialization.py test/test_u2f_highlevel.py test/test_u2f_v2.py u2flib_server/__init__.py u2flib_server/jsapi.py u2flib_server/u2f.py u2flib_server/u2f_v2.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/resolvers.py u2flib_server/yubicommon/__init__.pypython-u2flib-server-3.2.0/python_u2flib_server.egg-info/top_level.txt0000664000175000017500000000002312537764006025777 0ustar daindain00000000000000test u2flib_server python-u2flib-server-3.2.0/python_u2flib_server.egg-info/dependency_links.txt0000664000175000017500000000000112537764006027320 0ustar daindain00000000000000 python-u2flib-server-3.2.0/README0000664000175000017500000001007212537763701016267 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]. 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 M2Crypto. On a Ubuntu system, this can be installed with the following command: $ apt-get install python-m2crypto === 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 submodules init git submodules 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 {"authenticateRequests": [], "registerRequests": [{"challenge": "9s80ruHc6q9shJM5WLfOmz-ejb_Rm8dmWCnOvgZ2ovw", "version": "U2F_V2", "appId": "http://localhost:8081"}]} ---- 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={"clientData": "eyJvcmlnaW4iOiAiaHR0cDovL2xvY2FsaG9zdDo4MDgxIiwgImNoYWxsZW5nZSI6ICJEMnB6VFBaYTdicTY5QUJ1aUdRSUxvOXpjc1RVUlAyNlJMaWZUeUNraWxjIiwgInR5cCI6ICJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCJ9", "registrationData": "BQSivQtJ6-lAgZ2qQ0aUGLEiJSRoLWUSGcmMO8C-GuibA0-xTvmuQfTqKyFJZWOUjGzEIgF4xV6gJ6itcagsyuUWQEQh9noDSu-WtzTOMhK_lKHxwHtQgJHCkzs4mukfpf310K5Dq9k6zBNtZ2RMBWgJhI7hJo4JiFn3k2GUNLwKZpwwggGHMIIBLqADAgECAgkAmb7osQyi7BwwCQYHKoZIzj0EATAhMR8wHQYDVQQDDBZZdWJpY28gVTJGIFNvZnQgRGV2aWNlMB4XDTEzMDcxNzE0MjEwM1oXDTE2MDcxNjE0MjEwM1owITEfMB0GA1UEAwwWWXViaWNvIFUyRiBTb2Z0IERldmljZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDvhl91zfpg9n7DeCedcQ8gGXUnemiXoi-JEAxz-EIhkVsMPAyzhtJZ4V3CqMZ-MOUgICt2aMxacMX9cIa8dgS2jUDBOMB0GA1UdDgQWBBQNqL-TV04iaO6mS5tjGE6ShfexnjAfBgNVHSMEGDAWgBQNqL-TV04iaO6mS5tjGE6ShfexnjAMBgNVHRMEBTADAQH_MAkGByqGSM49BAEDSAAwRQIgXJWZdbvOWdhVaG7IJtn44o21Kmi8EHsDk4cAfnZ0r38CIQD6ZPi3Pl4lXxbY7BXFyrpkiOvCpdyNdLLYbSTbvIBQOTBFAiEA1uwJKNez6_BHdA2d-DPmRFJj19biYNkhN86SFH5Z_lYCICld2L3ZAVsm_uNFRt13_N9dlhGu50pb1ql8-_3_p5v1"}' 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 {"authenticateRequests": [{"challenge": "EHuxwx0Ayh5F8g7sFmSUFdfY945EWWK4hyhOKivjv7g", "version": "U2F_V2", "keyHandle": "bxXoPvBA6WPHbqGMjrGBDclMXh8O_qqPXGBPlAkmuIh8CO3ttWWLDzX27xGzemMBxpI6kQXKgURztp9sqEBrCA", "appId": "http://localhost:8081"}]} ---- 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={"clientData": "eyJvcmlHR0cDovL2xvY2FsaG9zdDo4MDgxIiwgImNoYWxsZW5nZSI6ICJlNGtScWk3eTdmUHdtZGZ1RnJ5WkxyVUhYby1BdF91YUFwWHdxdkV2UmxzIiwgInR5cCI6ICJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIn0", "challenge": "e4kRqi7y7fPwmdfuFryZLrUHXo-At_uaApXwqvEvRls", "keyHandle": "RCH2egNK75a3NM4yEr-UofHAe1CAkcKTOzia6R-l_fXQrkOr2TrME21nZEwFaAmEjuEmjgmIWfeTYZQ0vApmnA", "signatureData": "AQAAAAIwRQIhAIyr0y4xg-pI8NhAUHJmaluGXwZ7yd5i0e7FQE4l9OaEAiB68JP-df7ro8ohxCcgyxfRiKrsY1J67kLcEuYb0MCrDg"}' {"touch": "\u0001", "counter": 2} ---- The response indicates success, giving the U2F devices internal counter value, as well as the value of the user presence parameter. python-u2flib-server-3.2.0/NEWS0000664000175000017500000000217712537751213016110 0ustar daindain00000000000000* 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-3.2.0/setup.cfg0000664000175000017500000000007312537764006017227 0ustar daindain00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0