python-u2flib-server-4.0.1/0000775000175000017500000000000012700714561015377 5ustar daindain00000000000000python-u2flib-server-4.0.1/setup.cfg0000664000175000017500000000013012700714561017212 0ustar daindain00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-u2flib-server-4.0.1/test/0000775000175000017500000000000012700714561016356 5ustar daindain00000000000000python-u2flib-server-4.0.1/test/test_matchers.py0000664000175000017500000000336412664043420021601 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-4.0.1/test/test_u2f_highlevel.py0000664000175000017500000000550312664043420022513 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-4.0.1/test/test_u2f_v2.py0000664000175000017500000002543712676712722021116 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] SAMPLE_REG_DATA = b'''\x05\x04E\'\x9c@\xa5n\xae\x06\xcb\x92\'\xf1`Q\xc2\xfe\xb1\xe2\x9er\x16\xbb\xd6\xaf\x1e\xbe\n\x9fC\x079B\xea\x90\x86\xfe*&\x82\xf7*\x94\x94)}S\xe9uI\x12\xac\x8e\x92s\xa4\xdf\xde\xf2\xd9\x18y\x9b\xfc\xe4@?p\xaeX\x0c[\xd1\x068i\x81CIJ!\xae\xfd\xbd\xc4%\xda\xd3\x05\xee\x19a62\x06\xac\x91\xf0\xed\x1a\xa0\xf3\x06\xe7)d\xafB;9\xca\xd2\xb7\xb3u\xfda\x1b\x8dq\x8d\xe1\xb7\x9aIa\x96\xc6k\xee0\x82\x01\x870\x82\x01.\xa0\x03\x02\x01\x02\x02\t\x00\x99\xbe\xe8\xb1\x0c\xa2\xec\x1c0\t\x06\x07*\x86H\xce=\x04\x010!1\x1f0\x1d\x06\x03U\x04\x03\x0c\x16Yubico U2F Soft Device0\x1e\x17\r130717142103Z\x17\r160716142103Z0!1\x1f0\x1d\x06\x03U\x04\x03\x0c\x16Yubico U2F Soft Device0Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\x00\x04;\xe1\x97\xdds~\x98=\x9f\xb0\xde\t\xe7\\C\xc8\x06]I\xde\x9a%\xe8\x8b\xe2D\x03\x1c\xfe\x10\x88dV\xc3\x0f\x03,\xe1\xb4\x96xWp\xaa1\x9f\x8c9H\x08\n\xdd\x9a3\x16\x9c1\x7f\\!\xaf\x1d\x81-\xa3P0N0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\r\xa8\xbf\x93WN"h\xee\xa6K\x9bc\x18N\x92\x85\xf7\xb1\x9e0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14\r\xa8\xbf\x93WN"h\xee\xa6K\x9bc\x18N\x92\x85\xf7\xb1\x9e0\x0c\x06\x03U\x1d\x13\x04\x050\x03\x01\x01\xff0\t\x06\x07*\x86H\xce=\x04\x01\x03H\x000E\x02 \\\x95\x99u\xbb\xceY\xd8Uhn\xc8&\xd9\xf8\xe2\x8d\xb5*h\xbc\x10{\x03\x93\x87\x00~vt\xaf\x7f\x02!\x00\xfad\xf8\xb7>^%_\x16\xd8\xec\x15\xc5\xca\xbad\x88\xeb\xc2\xa5\xdc\x8dt\xb2\xd8m$\xdb\xbc\x80P90F\x02!\x00\xb2\x9d\xe26%\xda\x17\xd9\xa4\xeaa\xf3&\xab\xda\xfa\x18\xbb\x02T\xfe\xfe\x8a\xfe\xd8wJP\x9c\x9c\xa9\xa5\x02!\x00\x8f\xf4\xfe\x1a\x8e\xee"\xab\xc5:\x85\x1c\x88"Yi\xa7<\x1f+\xdf\xfb@\xdb\xff\xef\xf3\x95\xee\x941\xba''' SAMPLE_REG_DATA_NEEDS_FIX = b'''\x05\x04\x11\xd9\x0f6\xc0\x9fQ,\xe7\x07\xe5;\xd0\xbe\xf7\xbb.\xb0g\x0e\x9d\x18\x88$\x83\xc4\xa3\xe2\x1e\xc8?\x01\x8d[\xefA\x8a\xf9\xd7\x02\xb7\xe1\xea\nJ\x89f)\x00\\\xbe3-\x80\xac*k\x9cs\\gk\xdfA@\x03\x0er\xecW:\x98\x86\xf8\xc2H\x07|\xd4\xa6\xf4\xf5\x84\xed\xaf\xd7\xc9\xc1b\xc4Q\x164O!+A\xc4\\\xa1\xe8\xf3\x0c\x8c\rti\x91_\xeb\xcf\xc4\x16\x98\x1c\xfc \x1f\x12!*\xaf\x92\xf8\xd5\xa9\xcb\'q0\x82\x02\x1c0\x82\x01\x06\xa0\x03\x02\x01\x02\x02\x048f\xdfu0\x0b\x06\t*\x86H\x86\xf7\r\x01\x01\x0b0.1,0*\x06\x03U\x04\x03\x13#Yubico U2F Root CA Serial 4572006310 \x17\r140801000000Z\x18\x0f20500904000000Z0+1)0\'\x06\x03U\x04\x03\x0c Yubico U2F EE Serial 138311678610Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\x00\x047\x8d\xfct\x0cs\x9b\x94rN\xd3\xd5#\xb9\xb8v\x81\x06V\xc1=\xe8o\xaf\xae\xcf8\xd9\x0fU\xe2\xc8\n\x1b\xfe\x0b0\xdcS\xb3]\xe7\xd0E\xb9m\xcb\x8f+\xf9O\xa8\xe0\xb9\x03\x16<\x7fn\xdc.H{q\xa3\x120\x100\x0e\x06\n+\x06\x01\x04\x01\x82\xc4\n\x01\x01\x04\x000\x0b\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x03\x82\x01\x01\x02\x1aGd\xca\x00\x89\xcf\x92\xad\xb8\x7f\xa8HS\x8er\xcc>\xfd\xbb4y)C\x04{\x82\x16\xa99\xba\xf4\xc1\x13V*4[aGYyiyG\xbc\xe6q\xaaj|\x06yn\xd4\xeb\xb1\xb8\xfd`\'\x19\xb7\x1d\xeb<\xf6B\xe9\x8d\xb1\xd9fo\xf0\x1em\xb7OE\xafyg\xc0F\xd6\xe6\xffKN\t\xa3\x14\x184\xb6\x9a\xf1de\xcc\xde\xcf:\n\x80\x9c\n\xa4\x9a{\x19C\xf5\xbdN=\xae;\xdc\xcf\xdejq:I&\x9e\xac\xfb?\x9c\xed\xe0\xbay\xc6\xbb\xfb\xa7^a\x18\xe2\x0f\x0f\x95~\xa6\x1e\xedRh\x82&\xca\xb4-\xf7\x91\x03~\x97\xed\xa5\xe2\xdf`)\xd2\xbb\x7f\xc3\'\xe7E\xe7\xf9\xf5\x86+\xed)\xb0h\xcb\x97*6\xc8e"\xde\xb2\xc7\x19e33]\xdf\xae\xb8\xb6\xfa\r\xb5\x02j\xca\x84T\x19\x06\x1a\xa4\xd1|\x07\x0e\x98\xfa/\xd6q\xd4\xac\xd0\xc2\x90\xe4t\xa1\xb4x>\xc2F\xe0\xf8\x9a\x98\x87\xc0\xa4\xd7\xa8\\f)\x19\xba$\xea{\x9c0E\x02!\x00\x82/h\xda\x98\x1cr%\x0e\xa4\xb7\x18\tT\xd1Q)\xfe)P{\xe5\xbcX2]4:4\xe6%\x03\x02 I\rS\x07\xae\x15\xdb\x97^\x1c\xa6\xa4\xd5A\x19\x03`\x1b\x91\x1fs\x9c\xed\x97$\xa7}\xde\xfdAc\xd6''' SAMPLE_SIG_DATA = b'''\x00\x00\x00\x00\x010F\x02!\x00\xf5A\x03x\xbd\x02\x0e\x949\xefI\xbf\xe7\xf3\xd0\x9f\x07,\x81\x9f\x01-\xbax\xff&\xd0Thf\xfb\x9f\x02!\x00\xcelT\xfebq\x0c\x95\xce\xbd_\x852_\xd4\x83j\x98PaY\xea\x1b\xbd\x9beXu9\x1b\xbe\x00''' class RawRegistrationResponseTest(unittest.TestCase): def test_invalid_data(self): self.assertRaises(ValueError, u2f.RawRegistrationResponse, b'', b'', b'abc') def test_str(self): rawresponse = u2f.RawRegistrationResponse(b'', b'', SAMPLE_REG_DATA) self.assertTrue(isinstance(str(rawresponse), str)) self.assertEqual('050445279c', str(rawresponse)[:10]) def test_str_needs_fix(self): rawresponse = u2f.RawRegistrationResponse(b'', b'', SAMPLE_REG_DATA_NEEDS_FIX) self.assertTrue(isinstance(str(rawresponse), str)) self.assertEqual('050411d90f', str(rawresponse)[:10]) class RawAuthenticationResponse(unittest.TestCase): def test_str(self): rawresponse = u2f.RawAuthenticationResponse(b'', b'', SAMPLE_SIG_DATA) self.assertTrue(isinstance(str(rawresponse), str)) self.assertEqual('0000000001', str(rawresponse)[:10]) class U2fV2Test(unittest.TestCase): def test_register_fixed_values(self): request = {"challenge": "KEzvDDdHwnXtPHIMb0Uh43hgOJ-wQTsdLujGkeg6JxM", "version": "U2F_V2", "appId": "http://localhost:8081"} response = {"registrationData": "BQS94xQL46G4vheJPkYSuEteM6Km4-MwgBAu1zZ6MAbjDDgqhYbpHuIhhGOKjedeDd58qqktqOJsby9wMdHGnUtVQD8ISPywVi3J6SaKebCVQdHPu3_zQigRS8LhoDwKT5Ed3tg8AWuNw9XBZEh4doEDxKGuInFazirUw8acOu2qDcEwggIjMIIBDaADAgECAgRyuHt0MAsGCSqGSIb3DQEBCzAPMQ0wCwYDVQQDEwR0ZXN0MB4XDTE1MDkwNDA3MTAyNloXDTE2MDkwMzA3MTAyNlowKjEoMCYGA1UEAxMfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTkyNDY5Mjg1MjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABC37i_h-xmEtGfWnuvj_BmuhtU18MKShNP_vZ7C2WJwj8OHaSLnzAfha14CMUPaKPtRFfP6w9CFGhvEizH33XZKjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4yMBMGCysGAQQBguUcAgEBBAQDAgQwMAsGCSqGSIb3DQEBCwOCAQEAab7fWlJ-lOR1sqIxawPU5DWZ1b9nQ0QmNNoetPHJ_fJC95r0esRq5axfmGufbNktNWanHww7i9n5WWxSaMTWuJSF0eAXUajo8odYA8nB4_0I6z615MWa9hTU64Pl9HlqkR5ez5jndmJNuAfhaIF4h062Jw051kMo_aENxuLixnybTfJG7Q5KRE00o2MFs5b9L9fzhDtBzv5Z-vGOefuiohowpwnxIA9l0tGqrum9plUdx06K9TqKMRDQ8naosy01rbouA6i5xVjl-tHT3z-r__FYcSZ_dQ5-SCPOh4F0w6T0UwzymQmeqYN3pP-UUgnJ-ihD-uhEWklKNYRy0K0G0jBGAiEA7rbbx2jwC1YGICkZMR07ggKWaHCwFBxNDW3OwhLNNzUCIQCSq0sjGSUnWMQgPEImrmd3tMKcbrjI995rti6UYozqsg", "clientData": "eyJvcmlnaW4iOiAiaHR0cDovL2xvY2FsaG9zdDo4MDgxIiwgImNoYWxsZW5nZSI6ICJLRXp2RERkSHduWHRQSElNYjBVaDQzaGdPSi13UVRzZEx1akdrZWc2SnhNIiwgInR5cCI6ICJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCJ9"} u2f.complete_register(request, response) def test_authenticate_fixed_values(self): device = {'publicKey': 'BBCcnAOknoMgokEGuTdfpNLQ-uylwlKp_xbEW8urjJsXKv9XZSL-V8C2nwcPEckav1mKZFr5K96uAoLtuxOUf-E', 'keyHandle': 'BIarIKfyMqyf4bEI6tOqGInAfHrrQkMA2eyPJlNnInbAG1tXNpdRs48ef92_b1-mfN4VhaTWxo1SGoxT6CIanw', 'appId': 'http://www.example.com/appid'} challenge = {'challenge': 'oIeu-nPxx9DcF7L_DCE3kvYox-c4UuvFb8lNG6th10o', 'version': 'U2F_V2', 'keyHandle': 'BIarIKfyMqyf4bEI6tOqGInAfHrrQkMA2eyPJlNnInbAG1tXNpdRs48ef92_b1-mfN4VhaTWxo1SGoxT6CIanw', 'appId': 'http://www.example.com/appid'} response = {'keyHandle': 'BIarIKfyMqyf4bEI6tOqGInAfHrrQkMA2eyPJlNnInbAG1tXNpdRs48ef92_b1-mfN4VhaTWxo1SGoxT6CIanw', 'signatureData': 'AAAAAAEwRQIhAJrcBSpaDprFzXmVw60r6x-_gOZ0t-8v7DGiiKmar0SAAiAYKKEX41nWUCLLoKiBYuHYdPP1MPPNQ0cX_JIybPtThA', 'clientData': 'eyJvcmlnaW4iOiAiaHR0cHM6Ly93d3cuZXhhbXBsZS5jb20iLCAiY2hhbGxlbmdlIjogIm9JZXUtblB4eDlEY0Y3TF9EQ0Uza3ZZb3gtYzRVdXZGYjhsTkc2dGgxMG8iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24ifQ'} assert u2f.verify_authenticate(device, challenge, response) 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-4.0.1/test/test_attestation.py0000664000175000017500000001275412665264051022343 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, Transport from u2flib_server.attestation.resolvers import create_resolver from u2flib_server.attestation.data import YUBICO 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== """) YUBICO_RESOLVER = create_resolver(YUBICO) EMPTY_RESOLVER = create_resolver([]) class AttestationTest(unittest.TestCase): def test_resolver(self): cert = x509.load_der_x509_certificate(ATTESTATION_CERT, default_backend()) metadata = YUBICO_RESOLVER.resolve(cert) assert metadata.identifier == '2fb54029-7613-4f1d-94f1-fb876c14a6fe' def test_provider(self): provider = MetadataProvider(YUBICO_RESOLVER) cert = x509.load_der_x509_certificate(ATTESTATION_CERT, default_backend()) 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_der_x509_certificate(ATTESTATION_CERT, default_backend()) 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_der_x509_certificate(ATTESTATION_CERT, default_backend()) metadata = resolver.resolve(cert) assert metadata.identifier == '2fb54029-7613-4f1d-94f1-fb876c14a6fe' def test_transports_from_cert(self): provider = MetadataProvider(EMPTY_RESOLVER) cert = x509.load_der_x509_certificate(ATTESTATION_CERT_WITH_TRANSPORT, default_backend()) attestation = provider.get_attestation(cert) assert attestation.transports == 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) assert attestation.transports == Transport.USB python-u2flib-server-4.0.1/test/test_serialization.py0000664000175000017500000000342412660616235022653 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-4.0.1/test/test_utils.py0000664000175000017500000000263612664043420021134 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-4.0.1/test/soft_u2f_v2.py0000664000175000017500000001462512664043420021074 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, rand_bytes) from u2flib_server.jsapi import (RegisterRequest, RegisterResponse, SignRequest, SignResponse, ClientData) from u2flib_server.yubicommon.compat import int2byte from base64 import b64decode import struct 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, 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.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 = rand_bytes(64) app_param = request.appParam 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 + int2byte(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.encode('utf-8') client_param = sha_256(client_data) # Unwrap: priv_key, app_param = self.keys[key_handle] # Increment counter self.counter += 1 # Create signature touch = int2byte(1 if touch else 0) 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=request.keyHandle ) python-u2flib-server-4.0.1/test/test_jsapi.py0000664000175000017500000002053612664043420021101 0ustar daindain00000000000000# Copyright (c) 2015 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.jsapi import ( JSONDict, DeviceRegistration, ClientData, RegisterRequest, RegisterResponse, SignRequest, SignResponse, RegisterRequestData, AuthenticateRequestData, VendorInfo, Selector, DeviceInfo, MetadataObject, ) 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_wrong_type(self): # NB: This is differs from dict behaviour self.assertRaises(TypeError, JSONDict, []) self.assertRaises(TypeError, 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)) def test_getattr_unknown(self): self.assertRaises(AttributeError, lambda: JSONDict().foo) def test_getattr(self): self.assertEqual(1, JSONDict(a=1).a) class DeviceRegistrationTest(unittest.TestCase): def test_appParam(self): obj = DeviceRegistration(appId='https://example.com') self.assertEqual(b'\x10\x06\x80\xadTl\xe6\xa5w\xf4/R\xdf3\xb4\xcf' b'\xdc\xa7V\x85\x9efK\x8d}\xe3)\xb1P\xd0\x9c\xe9', obj.appParam) class ClientDataTest(unittest.TestCase): def test_challenge(self): obj = ClientData(challenge='Zm9vYmFy') self.assertEqual(b'foobar', obj.challenge) class RegisterRequestTest(unittest.TestCase): def test_appParam(self): req = RegisterRequest(appId='https://example.com') self.assertEqual(b'\x10\x06\x80\xadTl\xe6\xa5w\xf4/R\xdf3\xb4\xcf' b'\xdc\xa7V\x85\x9efK\x8d}\xe3)\xb1P\xd0\x9c\xe9', req.appParam) def test_challenge(self): req = RegisterRequest(challenge='Zm9vYmFy') self.assertEqual(b'foobar', req.challenge) class RegisterResponseTest(unittest.TestCase): def test_clientData(self): obj = RegisterResponse(clientData='eyJhIjoxfQ') self.assertEqual({'a': 1}, obj.clientData) self.assertTrue(isinstance(obj.clientData, ClientData)) def test_clientParam(self): obj = RegisterResponse(clientData='eyJhIjoxfQ') self.assertEqual(b"\x01Z\xbd\x7f\\\xc5z-\xd9Ku\x90\xf0J\xd8\x08" b"Bs\x90^\xe3>\xc5\xce\xbe\xaeb'j\x97\xf8b", obj.clientParam) def test_registrationData(self): pass class SignRequestTest(unittest.TestCase): def test_appParam(self): req = SignRequest(appId='https://example.com') self.assertEqual(b'\x10\x06\x80\xadTl\xe6\xa5w\xf4/R\xdf3\xb4\xcf' b'\xdc\xa7V\x85\x9efK\x8d}\xe3)\xb1P\xd0\x9c\xe9', req.appParam) def test_challenge(self): req = SignRequest(challenge='Zm9vYmFy') self.assertEqual(b'foobar', req.challenge) class SignResponseTest(unittest.TestCase): def test_clientData(self): obj = SignResponse(clientData='eyJhIjoxfQ') self.assertEqual({'a': 1}, obj.clientData) self.assertTrue(isinstance(obj.clientData, ClientData)) def test_clientParam(self): obj = SignResponse(clientData='eyJhIjoxfQ') self.assertEqual(b"\x01Z\xbd\x7f\\\xc5z-\xd9Ku\x90\xf0J\xd8\x08" b"Bs\x90^\xe3>\xc5\xce\xbe\xaeb'j\x97\xf8b", obj.clientParam) def test_signatureData(self): response = SignResponse(signatureData='eyJhIjoxfQ') self.assertEqual(b'{"a":1}', response.signatureData) class RegisterRequestDataTest(unittest.TestCase): def test_authenticateRequests(self): reqdata = RegisterRequestData(authenticateRequests=[{}, {'a': 1}, {'a': 1, 'b': 2}]) self.assertEqual([{}, {'a': 1}, {'a': 1, 'b': 2}], reqdata.authenticateRequests) self.assertTrue(isinstance(reqdata.authenticateRequests[0], SignRequest)) def test_registerRequests(self): reqdata = RegisterRequestData(registerRequests=[{}, {'a': 1}, {'a': 1, 'b': 2}]) self.assertEqual([{}, {'a': 1}, {'a': 1, 'b': 2}], reqdata.registerRequests) self.assertTrue(isinstance(reqdata.registerRequests[0], RegisterRequest)) def test_getRegisterRequest(self): reqdata = RegisterRequestData(registerRequests=[{}, {'a': 1}, {'a': 1, 'b': 2}]) response = None self.assertEqual({}, reqdata.getRegisterRequest(response)) self.assertTrue(isinstance(reqdata.getRegisterRequest(response), RegisterRequest)) class AuthenticateRequestDataTest(unittest.TestCase): def test_authenticateRequests(self): reqdata = AuthenticateRequestData(authenticateRequests=[{}, {'a': 1}, {'a': 1, 'b': 2}]) self.assertEqual([{}, {'a': 1}, {'a': 1, 'b': 2}], reqdata.authenticateRequests) self.assertTrue(isinstance(reqdata.authenticateRequests[0], SignRequest)) def test_getAuthenticateRequest(self): reqdata = AuthenticateRequestData(authenticateRequests=[{'keyHandle': 'a'}, {'keyHandle': 'b'}]) response = SignResponse(keyHandle='b') self.assertEqual({'keyHandle': 'b'}, reqdata.getAuthenticateRequest(response)) self.assertTrue(isinstance(reqdata.getAuthenticateRequest(response), SignRequest)) 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-4.0.1/README0000664000175000017500000001071612664043420016262 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.6-2.7, Python 3.3+ and PyPy 2.6+. 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 {"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-4.0.1/examples/0000775000175000017500000000000012700714561017215 5ustar daindain00000000000000python-u2flib-server-4.0.1/examples/yubiauth_server.py0000664000175000017500000001412612664034241023012 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 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-4.0.1/examples/u2f_server.py0000775000175000017500000001514512676713212021666 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.jsapi import DeviceRegistration from u2flib_server.u2f import (start_register, complete_register, start_authenticate, verify_authenticate) 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] devices = [DeviceRegistration.wrap(device) for device in 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 = [DeviceRegistration.wrap(device) for device in 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.public_bytes(Encoding.PEM)) return json.dumps(True) def sign(self, username): user = self.users[username] devices = [DeviceRegistration.wrap(device) for device in 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 = [DeviceRegistration.wrap(device) for device in 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-4.0.1/MANIFEST.in0000664000175000017500000000010212660616237017134 0ustar daindain00000000000000include COPYING include NEWS include ChangeLog include examples/* python-u2flib-server-4.0.1/PKG-INFO0000664000175000017500000000212512700714561016474 0ustar daindain00000000000000Metadata-Version: 1.1 Name: python-u2flib-server Version: 4.0.1 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-4.0.1/NEWS0000664000175000017500000000375312700714410016077 0ustar daindain00000000000000* 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-4.0.1/python_u2flib_server.egg-info/0000775000175000017500000000000012700714561023243 5ustar daindain00000000000000python-u2flib-server-4.0.1/python_u2flib_server.egg-info/requires.txt0000664000175000017500000000017412700714561025645 0ustar daindain00000000000000cryptography>=1.2 enum34 [u2f_server] WebOb [u2f_server:python_version=="2.6"] argparse [yubiauth_server] yubiauth WebOb python-u2flib-server-4.0.1/python_u2flib_server.egg-info/top_level.txt0000664000175000017500000000001612700714561025772 0ustar daindain00000000000000u2flib_server python-u2flib-server-4.0.1/python_u2flib_server.egg-info/PKG-INFO0000664000175000017500000000212512700714561024340 0ustar daindain00000000000000Metadata-Version: 1.1 Name: python-u2flib-server Version: 4.0.1 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-4.0.1/python_u2flib_server.egg-info/SOURCES.txt0000664000175000017500000000210412700714561025124 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/soft_u2f_v2.py test/test_attestation.py test/test_jsapi.py test/test_matchers.py test/test_serialization.py test/test_u2f_highlevel.py test/test_u2f_v2.py test/test_utils.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__.py u2flib_server/yubicommon/compat.py u2flib_server/yubicommon/setup/__init__.py u2flib_server/yubicommon/setup/cli.py u2flib_server/yubicommon/setup/exe.py u2flib_server/yubicommon/setup/pyinstaller_spec.py u2flib_server/yubicommon/setup/qt.pypython-u2flib-server-4.0.1/python_u2flib_server.egg-info/dependency_links.txt0000664000175000017500000000000112700714561027311 0ustar daindain00000000000000 python-u2flib-server-4.0.1/ChangeLog0000664000175000017500000004317512700714561017163 0ustar daindain000000000000002016-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_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-4.0.1/u2flib_server/0000775000175000017500000000000012700714561020150 5ustar daindain00000000000000python-u2flib-server-4.0.1/u2flib_server/u2f.py0000664000175000017500000000611012660616235021220 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-4.0.1/u2flib_server/yubicommon/0000775000175000017500000000000012700714561022331 5ustar daindain00000000000000python-u2flib-server-4.0.1/u2flib_server/yubicommon/setup/0000775000175000017500000000000012700714561023471 5ustar daindain00000000000000python-u2flib-server-4.0.1/u2flib_server/yubicommon/setup/cli.py0000664000175000017500000000257312660616243024624 0ustar daindain00000000000000# Copyright (c) 2016 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. __dependencies__ = ['docopt'] python-u2flib-server-4.0.1/u2flib_server/yubicommon/setup/pyinstaller_spec.py0000664000175000017500000001345512660616243027436 0ustar daindain00000000000000# -*- mode: python -*- # -*- encoding: utf-8 -*- from __future__ import absolute_import from __future__ import print_function import os import sys import json import errno import pkg_resources from glob import glob from .. import compat VS_VERSION_INFO = """ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four # items: (1, 2, 3, 4) # Set not needed items to zero 0. filevers=%(ver_tup)r, prodvers=%(ver_tup)r, # Contains a bitmask that specifies the valid bits 'flags'r mask=0x0, # Contains a bitmask that specifies the Boolean attributes # of the file. flags=0x0, # The operating system for which this file was designed. # 0x4 - NT and there is no need to change it. OS=0x4, # The general type of file. # 0x1 - the file is an application. fileType=0x1, # The function of the file. # 0x0 - the function is not defined for this fileType subtype=0x0, # Creation date and time stamp. date=(0, 0) ), kids=[ StringFileInfo( [ StringTable( u'040904E4', [StringStruct(u'FileDescription', u'%(name)s'), StringStruct(u'FileVersion', u'%(ver_str)s'), StringStruct(u'InternalName', u'%(internal_name)s'), StringStruct(u'LegalCopyright', u'Copyright © 2015 Yubico'), StringStruct(u'OriginalFilename', u'%(exe_name)s'), StringStruct(u'ProductName', u'%(name)s'), StringStruct(u'ProductVersion', u'%(ver_str)s')]) ]), VarFileInfo([VarStruct(u'Translation', [1033, 1252])]) ] )""" data = json.loads(os.environ['pyinstaller_data']) try: data = dict((k, v.encode('ascii') if isinstance(v, compat.string_types) else v) for k, v in data.items()) except NameError: pass # Python 3, encode not needed. dist = pkg_resources.get_distribution(data['name']) DEBUG = bool(data['debug']) NAME = data['long_name'] WIN = sys.platform in ['win32', 'cygwin'] OSX = sys.platform in ['darwin'] ver_str = dist.version if data['package_version'] > 0: ver_str += '.%d' % data['package_version'] file_ext = '.exe' if WIN else '' if WIN: icon_ext = 'ico' elif OSX: icon_ext = 'icns' else: icon_ext = 'png' ICON = os.path.join('resources', '%s.%s' % (data['name'], icon_ext)) if not os.path.isfile(ICON): ICON = None # Generate scripts from entry_points. merge = [] entry_map = dist.get_entry_map() console_scripts = entry_map.get('console_scripts', {}) gui_scripts = entry_map.get('gui_scripts', {}) for ep in list(gui_scripts.values()) + list(console_scripts.values()): script_path = os.path.join(WORKPATH, ep.name + '-script.py') with open(script_path, 'w') as fh: fh.write("import %s\n" % ep.module_name) fh.write("%s.%s()\n" % (ep.module_name, '.'.join(ep.attrs))) merge.append( (Analysis([script_path], [dist.location], None, None, None, None), ep.name, ep.name + file_ext) ) MERGE(*merge) # Read version information on Windows. VERSION = None if WIN: VERSION = 'build/file_version_info.txt' global int_or_zero # Needed due to how this script is invoked def int_or_zero(v): try: return int(v) except ValueError: return 0 ver_tup = tuple(int_or_zero(v) for v in ver_str.split('.')) # Windows needs 4-tuple. if len(ver_tup) < 4: ver_tup += (0,) * (4-len(ver_tup)) elif len(ver_tup) > 4: ver_tup = ver_tup[:4] # Write version info. with open(VERSION, 'w') as f: f.write(VS_VERSION_INFO % { 'name': NAME, 'internal_name': data['name'], 'ver_tup': ver_tup, 'ver_str': ver_str, 'exe_name': data['name'] + file_ext }) pyzs = [PYZ(m[0].pure) for m in merge] exes = [] for (a, a_name, a_name_ext), pyz in zip(merge, pyzs): exe = EXE(pyz, a.scripts, exclude_binaries=True, name=a_name_ext, debug=DEBUG, strip=None, upx=True, console=DEBUG or a_name in console_scripts, append_pkg=not OSX, version=VERSION, icon=ICON) exes.append(exe) # Sign the executable if WIN: os.system("signtool.exe sign /t http://timestamp.verisign.com/scripts/timstamp.dll \"%s\"" % (exe.name)) collect = [] for (a, _, a_name), exe in zip(merge, exes): collect += [exe, a.binaries, a.zipfiles, a.datas] # Data files collect.append([(os.path.basename(fn), fn, 'DATA') for fn in data['data_files']]) # DLLs, dylibs and executables should go here. collect.append([(fn[4:], fn, 'BINARY') for fn in glob('lib/*')]) coll = COLLECT(*collect, strip=None, upx=True, name=NAME) # Write package version for app to display pversion_fn = os.path.join('dist', NAME, 'package_version.txt') with open(pversion_fn, 'w') as f: f.write(str(data['package_version'])) # Create .app for OSX if OSX: app = BUNDLE(coll, name="%s.app" % NAME, version=ver_str, icon=ICON) qt_conf = 'dist/%s.app/Contents/Resources/qt.conf' % NAME qt_conf_dir = os.path.dirname(qt_conf) try: os.makedirs(qt_conf_dir) except OSError as e: if not (e.errno == errno.EEXIST and os.path.isdir(qt_conf_dir)): raise with open(qt_conf, 'w') as f: f.write('[Path]\nPlugins = plugins') # Create Windows installer if WIN: installer_cfg = 'resources/win-installer.nsi' if os.path.isfile(installer_cfg): os.system('makensis.exe -D"VERSION=%s" %s' % (ver_str, installer_cfg)) installer = "dist/%s-%s-win.exe" % (data['name'], ver_str) os.system("signtool.exe sign /t http://timestamp.verisign.com/scripts/timstamp.dll \"%s\"" % (installer)) print("Installer created: %s" % installer) python-u2flib-server-4.0.1/u2flib_server/yubicommon/setup/qt.py0000664000175000017500000000567012660616243024502 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 Command from distutils.errors import DistutilsSetupError import os __dependencies__ = ['PySide'] __all__ = ['qt_resources'] class _qt_resources(Command): description = "convert file resources into code" user_options = [] boolean_options = [] _source = 'qt_resources' _target = '' def initialize_options(self): pass def finalize_options(self): self.cwd = os.getcwd() self.source = os.path.join(self.cwd, self._source) self.target = os.path.join(self.cwd, self._target) def _create_qrc(self): qrc = os.path.join(self.source, 'qt_resources.qrc') with open(qrc, 'w') as f: f.write('\n\n') for fname in os.listdir(self.source): f.write('%s\n' % fname) f.write('\n\n') return qrc def run(self): if os.getcwd() != self.cwd: raise DistutilsSetupError("Must be in package root!") qrc = self._create_qrc() self.execute(os.system, ('pyside-rcc "%s" -o "%s"' % (qrc, self.target),)) os.unlink(qrc) self.announce("QT resources compiled into %s" % self.target) def qt_resources(target, sourcedir='qt_resources'): target = target.replace('.', os.path.sep) if os.path.isdir(target): target = os.path.join(target, 'qt_resources.py') else: target += '.py' return type('qt_resources', (_qt_resources, object), { '_source': sourcedir, '_target': target }) python-u2flib-server-4.0.1/u2flib_server/yubicommon/setup/exe.py0000664000175000017500000000562712660616243024641 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 Command from distutils.errors import DistutilsSetupError import os import json import tempfile class executable(Command): description = "create an executable" user_options = [ ('debug', None, "build with debug flag"), ('data-files=', None, "data files to include"), ('package-version=', None, "package version") ] boolean_options = ['debug'] def initialize_options(self): self.debug = 0 self.data_files = '' self.package_version = '0' def finalize_options(self): self.cwd = os.getcwd() self.data_files = self.data_files.split() self.package_version = int(self.package_version) def run(self): if os.getcwd() != self.cwd: raise DistutilsSetupError("Must be in package root!") from PyInstaller.main import run as pyinst_run os.environ['pyinstaller_data'] = json.dumps({ 'debug': self.debug, 'name': self.distribution.get_name(), 'long_name': os.environ['setup_long_name'], 'data_files': self.data_files, 'package_version': self.package_version }) spec = tempfile.NamedTemporaryFile(suffix='.spec', delete=False) source = os.path.join(os.path.dirname(__file__), 'pyinstaller_spec.py') with open(source) as f: spec.write(f.read()) spec_name = spec.name spec.close() pyinst_run([spec_name]) os.unlink(spec_name) self.announce("Executable created!") python-u2flib-server-4.0.1/u2flib_server/yubicommon/setup/__init__.py0000664000175000017500000002013112660616243025602 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 __dependencies__ = [] __all__ = ['get_version', 'setup', 'release'] 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*['\"](.+)['\"]$") DEPENDENCY_PATTERN = re.compile( r"(?m)__dependencies__\s*=\s*\[((['\"].+['\"]\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 get_dependencies(module): basedir = os.path.dirname(__file__) fn = os.path.join(basedir, module + '.py') if os.path.isfile(fn): with open(fn, 'r') as f: match = DEPENDENCY_PATTERN.search(f.read()) if match: return [s.strip().strip('"\'') for s in match.group(1).split(',')] return [] def get_package(module): return base_module + '.' + module def setup(**kwargs): # TODO: Find a better way to pass this to a command. os.environ['setup_long_name'] = kwargs.pop('long_name', kwargs.get('name')) if 'version' not in kwargs: kwargs['version'] = get_version() packages = kwargs.setdefault( 'packages', find_packages(exclude=['test', 'test.*', base_module + '.*'])) packages.append(__name__) install_requires = kwargs.setdefault('install_requires', []) yc_blacklist = kwargs.pop('yc_requires_exclude', []) 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 and dep not in yc_blacklist: install_requires.append(dep) 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') # Run if available: if 'qt_resources' in self.distribution.cmdclass: self.run_command('qt_resources') 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-4.0.1/u2flib_server/yubicommon/compat.py0000664000175000017500000000145612663612050024172 0ustar daindain00000000000000"""Compatibility constants and helpers for Python 2.x and 3.x. """ import sys # NB If this module grows to more than a handful of items it is probably # to bite the bullet and depend on the six package. __all__ = [ 'string_types', 'binary_type', 'text_type', 'int2byte', 'byte2int' ] # Needed for isinstance() checks # Same behaviour as six.string_types https://pythonhosted.org/six/#constants if sys.version_info < (3, 0): # Python 2.x _PY2 = True string_types = (basestring,) binary_type = str text_type = unicode else: # Python 3.x _PY2 = False string_types = (str,) binary_type = bytes text_type = str def int2byte(i): if _PY2: return chr(i) return bytes((i,)) def byte2int(i): if _PY2: return ord(i) return i python-u2flib-server-4.0.1/u2flib_server/yubicommon/__init__.py0000664000175000017500000000256212660616243024452 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' python-u2flib-server-4.0.1/u2flib_server/jsapi.py0000664000175000017500000001171612665264051021642 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 from u2flib_server.yubicommon.compat import binary_type, text_type 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, text_type): self.update(json.loads(data)) elif isinstance(data, binary_type): self.update(json.loads(data.decode('utf-8'))) elif isinstance(data, dict): self.update(data) else: raise TypeError("Unexpected type! Expected one of string, bytes, or dict") 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 [SignRequest(req) for req in self['authenticateRequests']] @property def registerRequests(self): return [RegisterRequest(req) for req in self['registerRequests']] def getRegisterRequest(self, response): return self.registerRequests[0] class AuthenticateRequestData(JSONDict): @property def authenticateRequests(self): return [SignRequest(req) for req in 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 [Selector(selector) for selector in selectors] @property def transports(self): return self.get('transports', 0) class MetadataObject(JSONDict): @property def vendorInfo(self): return VendorInfo(self['vendorInfo']) @property def devices(self): return [DeviceInfo(dev) for dev in self['devices']] python-u2flib-server-4.0.1/u2flib_server/u2f_v2.py0000664000175000017500000001762112676713212021640 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.jsapi import (RegisterRequest, RegisterResponse, SignRequest, SignResponse, DeviceRegistration) from u2flib_server.utils import (certificate_from_der, pub_key_from_der, subject_from_certificate, websafe_decode, websafe_encode, rand_bytes, verify_ecdsa_signature) from u2flib_server.yubicommon.compat import byte2int import codecs import struct from cryptography.hazmat.primitives.serialization import Encoding __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 byte2int(data[0]) != 0x05: raise ValueError("Invalid data: %r" % (data,)) data = data[1:] self.pub_key = data[:self.PUBKEY_LEN] data = data[self.PUBKEY_LEN:] kh_len = byte2int(data[0]) data = data[1:] self.key_handle = data[:kh_len] data = data[kh_len:] self.certificate = self._fixsig(certificate_from_der(data)) self.signature = data[len(self.certificate.public_bytes(Encoding.DER)):] def __str__(self): # N.B. Ensure this returns a str() on both Python 2 and Python 3 hex_bytes = codecs.encode(self.data, 'hex_codec') hex_text = codecs.decode(hex_bytes, 'ascii') return str(hex_text) def verify_csr_signature(self): data = (b'\x00' + self.app_param + self.chal_param + self.key_handle + self.pub_key) pub_key = self.certificate.public_key() verify_ecdsa_signature(data, pub_key, self.signature) def _fixsig(self, cert): subject = 'CN=' + subject_from_certificate(cert) if subject in FIXSIG: # Set unused bits in signature to 0 der = cert.public_bytes(Encoding.DER) der = der[:-257] + b'\x00' + der[-256:] cert = certificate_from_der(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:1] self.counter = data[1:5] self.counter_int = struct.unpack('>I', self.counter)[0] self.signature = data[5:] def __str__(self): # N.B. Ensure this returns a str() on both Python 2 and Python 3 hex_bytes = codecs.encode(self.data, 'hex_codec') hex_text = codecs.decode(hex_bytes, 'ascii') return str(hex_text) def verify_signature(self, pubkey): data = (self.app_param + self.user_presence + self.counter + self.chal_param) pub_key = pub_key_from_der(pubkey) verify_ecdsa_signature(data, pub_key, self.signature) 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: %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)) 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-4.0.1/u2flib_server/utils.py0000664000175000017500000000721212664043420021662 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.compat import text_type 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, rsa, padding from cryptography.hazmat.primitives.serialization import load_der_public_key from cryptography.x509.oid import NameOID from base64 import urlsafe_b64decode, urlsafe_b64encode from hashlib import sha256 import os PUB_KEY_DER_PREFIX = b'\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01' \ b'\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00' def certificate_from_der(der): return x509.load_der_x509_certificate(der, default_backend()) def subject_from_certificate(cert): return cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value def pub_key_from_der(der): return load_der_public_key(PUB_KEY_DER_PREFIX + der, default_backend()) def websafe_decode(data): if isinstance(data, text_type): data = data.encode('ascii') data += b'=' * (-len(data) % 4) return urlsafe_b64decode(data) def websafe_encode(data): if isinstance(data, text_type): data = data.encode('ascii') return urlsafe_b64encode(data).replace(b'=', b'').decode('ascii') def sha_256(data): h = sha256() h.update(data) return h.digest() def rand_bytes(n_bytes): return os.urandom(n_bytes) def verify_ecdsa_signature(payload, pubkey, signature): verifier = pubkey.verifier(signature, ec.ECDSA(hashes.SHA256())) verifier.update(payload) verifier.verify() def verify_cert_signature(cert, pubkey): 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 python-u2flib-server-4.0.1/u2flib_server/attestation/0000775000175000017500000000000012700714561022507 5ustar daindain00000000000000python-u2flib-server-4.0.1/u2flib_server/attestation/resolvers.py0000664000175000017500000001142312664043420025104 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 u2flib_server.jsapi import MetadataObject from u2flib_server.attestation.data import YUBICO from u2flib_server.utils import verify_cert_signature from u2flib_server.yubicommon.compat import string_types, text_type import os import json from cryptography import x509 from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.x509.oid import NameOID 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, 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 if subject not in self._certs: self._certs[subject] = [] self._certs[subject].append(cert) self._metadata[cert] = metadata def _verify_cert(self, cert, key): """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. """ try: verify_cert_signature(cert, key) return True except InvalidSignature: return False def resolve(self, cert): 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, 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-4.0.1/u2flib_server/attestation/metadata.py0000664000175000017500000001147212665264051024652 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 from u2flib_server.jsapi import DeviceInfo from u2flib_server.yubicommon.compat import byte2int from cryptography.x509 import ObjectIdentifier, ExtensionNotFound from enum import IntEnum __all__ = ['Attestation', 'MetadataProvider', 'Transport'] TRANSPORTS_EXT_OID = ObjectIdentifier('1.3.6.1.4.1.45724.2.1.1') class Transport(IntEnum): BT_CLASSIC = 0x01 # Bluetooth Classic BLE = 0x02 # Bluetooth Low Energy USB = 0x04 NFC = 0x08 class Attestation(object): def __init__(self, trusted, vendor_info=None, device_info=None, cert_transports=0): self._trusted = trusted self._vendor_info = vendor_info self._device_info = device_info self._transports = cert_transports | device_info.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 def get_transports(cert): """Parses transport extension from attestation cert. As the information is stored as a bitstring, which is a bit unwieldy to work with, we convert it into an integer where each bit represents a transport flag (as defined in the Transport IntEnum). """ try: ext = cert.extensions.get_extension_for_oid(TRANSPORTS_EXT_OID) der_bitstring = ext.value.value int_bytes = [byte2int(b) for b in der_bitstring[3:]] # Mask away unused bits (should already be 0, but make sure) unused_bits = byte2int(der_bitstring[2]) unused_bit_mask = 0xff for _ in range(unused_bits): unused_bit_mask <<= 1 int_bytes[-1] &= unused_bit_mask # 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 transports except ExtensionNotFound: return 0 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 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 = get_transports(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-4.0.1/u2flib_server/attestation/data.py0000664000175000017500000001166312665264051024005 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", "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-4.0.1/u2flib_server/attestation/matchers.py0000664000175000017500000000517312665264105024701 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) except ExtensionNotFound: return None return extension.value.value 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-4.0.1/u2flib_server/attestation/__init__.py0000664000175000017500000000305312660616235024625 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-4.0.1/u2flib_server/__init__.py0000664000175000017500000000256212700714356022270 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__ = "4.0.1" python-u2flib-server-4.0.1/setup.py0000775000175000017500000000556312665264146017136 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.setup import setup import sys install_requires = ['cryptography>=1.2'] 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'], 'u2f_server:python_version=="2.6"': ['argparse'], 'yubiauth_server': ['yubiauth', '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-4.0.1/COPYING0000664000175000017500000000243012660616235016435 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.