def test_sign_await_touch(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = 'U2F_V2' client.ctap.authenticate.side_effect = [ ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), SIG_DATA ] event = Event() event.wait = mock.MagicMock() resp = client.sign(APP_ID, 'challenge', [{ 'version': 'U2F_V2', 'keyHandle': 'a2V5' }], timeout=event) event.wait.assert_called() client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called() client_param, app_param, key_handle = \ client.ctap.authenticate.call_args[0] self.assertEqual(client_param, sha256(websafe_decode(resp['clientData']))) self.assertEqual(app_param, sha256(APP_ID.encode())) self.assertEqual(key_handle, b'key') self.assertEqual(websafe_decode(resp['signatureData']), SIG_DATA)
def test_sign(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = 'U2F_V2' client.ctap.authenticate.return_value = SIG_DATA resp = client.sign( APP_ID, 'challenge', [{ 'version': 'U2F_V2', 'keyHandle': 'a2V5' }], ) client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client_param, app_param, key_handle = \ client.ctap.authenticate.call_args[0] self.assertEqual(client_param, sha256(websafe_decode(resp['clientData']))) self.assertEqual(app_param, sha256(APP_ID.encode())) self.assertEqual(key_handle, b'key') self.assertEqual(websafe_decode(resp['signatureData']), SIG_DATA)
def test_sign_await_touch(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = [ ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), SIG_DATA, ] event = Event() event.wait = mock.MagicMock() resp = client.sign( APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}], event=event, ) event.wait.assert_called() client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called() client_param, app_param, key_handle = client.ctap.authenticate.call_args[0] self.assertEqual(client_param, sha256(websafe_decode(resp["clientData"]))) self.assertEqual(app_param, sha256(APP_ID.encode())) self.assertEqual(key_handle, b"key") self.assertEqual(websafe_decode(resp["signatureData"]), SIG_DATA)
def test_register(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA) client.ctap.register.return_value = REG_DATA resp = client.register( APP_ID, [{ "version": "U2F_V2", "challenge": "foobar" }], [{ "version": "U2F_V2", "keyHandle": "a2V5" }], ) client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client.ctap.register.assert_called_once() client_param, app_param = client.ctap.register.call_args[0] self.assertEqual(sha256(websafe_decode(resp["clientData"])), client_param) self.assertEqual(websafe_decode(resp["registrationData"]), REG_DATA) self.assertEqual(sha256(APP_ID.encode()), app_param)
def test_register_await_touch(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = 'U2F_V2' client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA) client.ctap.register.side_effect = [ ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), REG_DATA ] event = Event() event.wait = mock.MagicMock() resp = client.register(APP_ID, [{ 'version': 'U2F_V2', 'challenge': 'foobar' }], [{ 'version': 'U2F_V2', 'keyHandle': 'a2V5' }], timeout=event) event.wait.assert_called() client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client.ctap.register.assert_called() client_param, app_param = client.ctap.register.call_args[0] self.assertEqual(sha256(websafe_decode(resp['clientData'])), client_param) self.assertEqual(websafe_decode(resp['registrationData']), REG_DATA) self.assertEqual(sha256(APP_ID.encode()), app_param)
def test_u2f(self, ): chal = sha256(b"AAA") appid = sha256(b"BBB") for i in range(0, 5): reg = self.ctap1.register(chal, appid) reg.verify(appid, chal) auth = self.ctap1.authenticate(chal, appid, reg.key_handle) # check endianness assert auth.counter < 0x10000 print("U2F reg + auth pass %d/5" % (i + 1))
def test_sha256_vectors(self): self.assertEqual( sha256(b'abc'), a2b_hex( b'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad' )) # noqa self.assertEqual( sha256( b'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'), a2b_hex( b'248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1' )) # noqa
def test_u2f(self, ): chal = sha256(b"AAA") appid = sha256(b"BBB") lastc = 0 for i in range(0, 5): reg = self.ctap1.register(chal, appid) reg.verify(appid, chal) auth = self.ctap1.authenticate(chal, appid, reg.key_handle) # check endianness if lastc: assert (auth.counter - lastc) < 10 lastc = auth.counter print(hex(lastc)) print("U2F reg + auth pass %d/5" % (i + 1))
def test_sha256_vectors(self): self.assertEqual( sha256(b"abc"), bytes.fromhex( "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" ), ) self.assertEqual( sha256( b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), bytes.fromhex( "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" ), )
def list(self): token = self.client.pin_protocol.get_pin_token(self.pin) pin_protocol = 1 cm = CredentialManagement(self.ctap2, pin_protocol, token) meta = cm.get_metadata() existing = meta[CredentialManagement.RESULT.EXISTING_CRED_COUNT] if existing == 0: print("No PGP keys found") return creds = cm.enumerate_creds(sha256(RP_ID.encode('ascii'))) for cred in creds: user_id = cred[CredentialManagement.RESULT.USER]['id'] created = int.from_bytes(user_id, 'big', signed=False) username = cred[CredentialManagement.RESULT.USER]['name'] pubkey_x = cred[CredentialManagement.RESULT.PUBLIC_KEY][-2] pubkey_y = cred[CredentialManagement.RESULT.PUBLIC_KEY][-3] pubkey = (pubkey_x, pubkey_y) pubkey_pkt = self._pubkey_packet(pubkey, created) fp = self._fingerprint(pubkey_pkt) key_id = fp[-8:] created_date = datetime.utcfromtimestamp(created).strftime( '%Y-%m-%d %H:%M:%S') print("Created: {}".format(created_date)) print("User: {}".format(username)) print("ID: {}".format(key_id.hex().upper())) print("Fingerprint: {}".format(fp.hex().upper())) print()
def _ctap1_get_assertion(self, client_data, rp_id, allow_list, extensions, uv, pin, event, on_keepalive): if uv or not allow_list: raise CtapError(CtapError.ERR.UNSUPPORTED_OPTION) app_param = sha256(rp_id.encode()) client_param = client_data.hash for cred in allow_list: try: auth_resp = _call_polling( self.ctap1_poll_delay, event, on_keepalive, self.ctap1.authenticate, client_param, app_param, cred["id"], ) return [ AssertionResponse.from_ctap1(app_param, cred, auth_resp) ] except ClientError as e: if e.code == ClientError.ERR.TIMEOUT: raise # Other errors are ignored so we move to the next. raise ClientError.ERR.DEVICE_INELIGIBLE()
def get_firmware_object(): sk = SigningKey.from_pem(open("signing_key.pem").read()) h = open(HEX_FILE, 'r').read() h = base64.b64encode(h.encode()) h = to_websafe(h.decode()) num_pages = 64 START = 0x4000 END = 2048 * (num_pages - 3) - 4 ih = IntelHex(HEX_FILE) segs = ih.segments() arr = ih.tobinarray(start=START, size=END - START) im_size = END - START print('im_size: ', im_size) print('firmware_size: ', len(arr)) byts = (arr).tobytes() if hasattr(arr, 'tobytes') else (arr).tostring() sig = sha256(byts) print('hash', binascii.hexlify(sig)) sig = sk.sign_digest(sig) print('sig', binascii.hexlify(sig)) sig = base64.b64encode(sig) sig = to_websafe(sig.decode()) #msg = {'data': read()} msg = {'firmware': h, 'signature': sig} return msg
def test_make_credential_ctap1(self): dev = mock.Mock() dev.capabilities = 0 # No CTAP2 client = Fido2Client(dev, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = 'U2F_V2' client.ctap.register.return_value = REG_DATA attestation, client_data = client.make_credential(rp, user, challenge, timeout=1) self.assertIsInstance(attestation, AttestationObject) self.assertIsInstance(client_data, ClientData) client.ctap.register.assert_called_with( client_data.hash, sha256(rp['id'].encode()), ) self.assertEqual(client_data.get('origin'), APP_ID) self.assertEqual(client_data.get('type'), 'webauthn.create') self.assertEqual(client_data.get('challenge'), challenge) self.assertEqual(attestation.fmt, 'fido-u2f')
def test_make_credential_ctap1(self): dev = mock.Mock() dev.capabilities = 0 # No CTAP2 client = Fido2Client(dev, APP_ID) client.ctap1 = mock.MagicMock() client.ctap1.get_version.return_value = "U2F_V2" client.ctap1.register.return_value = REG_DATA attestation, client_data = client.make_credential(rp, user, challenge, timeout=1) self.assertIsInstance(attestation, AttestationObject) self.assertIsInstance(client_data, ClientData) client.ctap1.register.assert_called_with(client_data.hash, sha256(rp["id"].encode())) self.assertEqual(client_data.get("origin"), APP_ID) self.assertEqual(client_data.get("type"), "webauthn.create") self.assertEqual(client_data.get("challenge"), challenge) self.assertEqual(attestation.fmt, "fido-u2f")
def test_make_credential_ctap1(self): dev = mock.Mock() dev.capabilities = 0 # No CTAP2 client = Fido2Client(dev, APP_ID) client.ctap1 = mock.MagicMock() client.ctap1.get_version.return_value = "U2F_V2" client.ctap1.register.return_value = REG_DATA response = client.make_credential( PublicKeyCredentialCreationOptions(rp, user, challenge, [{ "type": "public-key", "alg": -7 }])) self.assertIsInstance(response.attestation_object, AttestationObject) self.assertIsInstance(response.client_data, ClientData) client_data = response.client_data client.ctap1.register.assert_called_with(client_data.hash, sha256(rp["id"].encode())) self.assertEqual(client_data.get("origin"), APP_ID) self.assertEqual(client_data.get("type"), "webauthn.create") self.assertEqual(client_data.challenge, challenge) self.assertEqual(response.attestation_object.fmt, "fido-u2f")
def __init__(self, request=None, **kwargs): if not isinstance(request, FidoRequest) and request is not None: request = request.request self.request = request for i in ( "cdh", "key_params", "allow_list", "challenge", "rp", "user", "pin_protocol", "options", "appid", "exclude_list", "extensions", "pin_auth", "timeout", "on_keepalive", ): self.save_attr(i, kwargs.get(i, Empty), request) if isinstance(self.rp, dict) and "id" in self.rp: if hasattr(self.rp["id"], "encode"): self.appid = sha256(self.rp["id"].encode("utf8"))
def sign(self, key_id, data): key_id = bytes.fromhex(key_id) token = self.client.pin_protocol.get_pin_token(self.pin) pin_protocol = 1 cm = CredentialManagement(self.ctap2, pin_protocol, token) creds = cm.enumerate_creds(sha256(RP_ID.encode('ascii'))) for cred in creds: user_id = cred[CredentialManagement.RESULT.USER]['id'] created = int.from_bytes(user_id, 'big', signed=False) username = cred[CredentialManagement.RESULT.USER]['name'] cred_id = cred[CredentialManagement.RESULT.CREDENTIAL_ID]['id'] pubkey_x = cred[CredentialManagement.RESULT.PUBLIC_KEY][-2] pubkey_y = cred[CredentialManagement.RESULT.PUBLIC_KEY][-3] pubkey = (pubkey_x, pubkey_y) pubkey_pkt = self._pubkey_packet(pubkey, created) fp = self._fingerprint(pubkey_pkt) curr_key_id = fp[-8:] if curr_key_id == key_id: break else: print("Key {} not found".format(key_id)) return None created = int(time.time()) hashed_subpkts = [ SubPacket(0x21, b'\x04' + fp), SubPacket(0x02, struct.pack('>I', created)) ] unhashed_subpkts = [SubPacket(0x10, key_id)] # issuer sig_pkt = self._signature_packet_data(cred_id, data, hashed_subpkts, unhashed_subpkts) armored = self._ascii_armor(sig_pkt) print('\n[GNUPG:] SIG_CREATED ', file=sys.stderr) print('-----BEGIN PGP SIGNATURE-----\n\n{}-----END PGP SIGNATURE-----'. format(armored))
def test_sign(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.return_value = SIG_DATA resp = client.sign( APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}] ) client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client_param, app_param, key_handle = client.ctap.authenticate.call_args[0] self.assertEqual(client_param, sha256(websafe_decode(resp["clientData"]))) self.assertEqual(app_param, sha256(APP_ID.encode())) self.assertEqual(key_handle, b"key") self.assertEqual(websafe_decode(resp["signatureData"]), SIG_DATA)
def sign(self, client_data): authenticator_data = AuthenticatorData.create( sha256(self.app_id), flags=AuthenticatorData.FLAG.USER_PRESENT, counter=0) signature = self.priv_key.sign(authenticator_data + client_data.hash, ec.ECDSA(hashes.SHA256())) return authenticator_data, signature
def __init__(self, ui, appId, nonce, credentialId): """ :param appId: Base URL string for Okta IDP e.g. https://xxxx.okta.com' :param nonce: nonce :param credentialid: credentialid """ self.ui = ui self._clients = None self._has_prompted = False self._cancel = Event() self._credentialId = base64.urlsafe_b64decode(credentialId) self._appId = sha256(appId.encode()) self._version = 'U2F_V2' self._signature = None self._clientData = json.dumps({ "challenge": nonce, "origin": appId, "typ": "navigator.id.getAssertion" }).encode() self._nonce = sha256(self._clientData)
def get(self, options, origin): """get authentication credential aka assertion""" if self.rp_id != options['publicKey']['rpId']: raise ValueError( 'Requested rpID does not match current credential') self.sign_count += 1 # prepare signature client_data = json.dumps({ 'type': 'webauthn.get', 'challenge': urlsafe_b64encode( options['publicKey']['challenge']).decode('ascii'), 'origin': origin }).encode('utf-8') client_data_hash = sha256(client_data) rp_id_hash = sha256(self.rp_id.encode('ascii')) flags = b'\x01' sign_count = pack('>I', self.sign_count) authenticator_data = rp_id_hash + flags + sign_count signature = self.private_key.sign( authenticator_data + client_data_hash, ec.ECDSA(hashes.SHA256())) # generate assertion return { 'id': urlsafe_b64encode(self.credential_id), 'rawId': self.credential_id, 'response': { 'authenticatorData': authenticator_data, 'clientDataJSON': client_data, 'signature': signature, 'userHandle': self.user_handle }, 'type': 'public-key' }
def test_backup_credential_is_generated_correctly( self, solo, device, ): import seedweed from binascii import hexlify key_A = b"A" * 32 ext_state = b"I'm a dicekey key!" version = b"\x01" ext_key_cmd = 0x62 print("Enter user presence THREE times.") solo.send_data_hid(ext_key_cmd, version + key_A + ext_state) # New credential works. mc_A_req = FidoRequest() mc_A_res = device.sendMC(*mc_A_req.toMC()) rpIdHash = sha256(mc_A_req.rp["id"].encode("utf8")) credId = mc_A_res.auth_data.credential_data.credential_id ( uniqueId, extStateInCredId, credMacInCredId, ) = seedweed.nonce_extstate_mac_from_credential_id(credId) seedweed.validate_credential_id(key_A, credId, rpIdHash) credMac = hmac_sha256(key_A, rpIdHash + version + uniqueId + ext_state) allow_list = [{ "id": mc_A_res.auth_data.credential_data.credential_id, "type": "public-key", }] ga_req = FidoRequest(allow_list=allow_list) ga_res = device.sendGA(*ga_req.toGA()) verify(mc_A_res, ga_res, ga_req.cdh) # Independently create the key and verify _, _, keypair, iterations = seedweed.keypair_from_seed_mac( key_A, credMac) assert iterations == 1 keypair.verifying_key.verify( ga_res.signature, ga_res.auth_data + ga_req.cdh, sigdecode=ecdsa.util.sigdecode_der, hashfunc=hashlib.sha256, )
def create(self, options, origin): """create credential and return PublicKeyCredential object aka attestation""" if { 'alg': -7, 'type': 'public-key' } not in options['publicKey']['pubKeyCredParams']: raise ValueError( 'Requested pubKeyCredParams does not contain supported type') if ('attestation' in options['publicKey']) and ( options['publicKey']['attestation'] != 'none'): raise ValueError('Only none attestation supported') # prepare new key self.cred_init(options['publicKey']['rp']['id'], options['publicKey']['user']['id']) # generate credential response client_data = { 'type': 'webauthn.create', 'challenge': urlsafe_b64encode( options['publicKey']['challenge']).decode('ascii'), 'origin': origin } rp_id_hash = sha256(self.rp_id.encode('ascii')) flags = b'\x41' # attested_data + user_present sign_count = pack('>I', self.sign_count) credential_id_length = pack('>H', len(self.credential_id)) cose_key = cbor.encode( ES256.from_cryptography_key(self.private_key.public_key())) attestation_object = { 'authData': rp_id_hash + flags + sign_count + self.aaguid + credential_id_length + self.credential_id + cose_key, 'fmt': 'none', 'attStmt': {} } return { 'id': urlsafe_b64encode(self.credential_id), 'rawId': self.credential_id, 'response': { 'clientDataJSON': json.dumps(client_data).encode('utf-8'), 'attestationObject': cbor.encode(attestation_object) }, 'type': 'public-key' }
def u2f_complete(): data = cbor.decode(request.get_data()) reg_data = RegistrationData.from_b64(data["registrationData"]) print("clientData", websafe_decode(data["clientData"])) print("U2F RegistrationData:", reg_data) att_obj = AttestationObject.from_ctap1(sha256(b"https://localhost:5000"), reg_data) print("AttestationObject:", att_obj) auth_data = att_obj.auth_data credentials.append(auth_data.credential_data) print("REGISTERED U2F CREDENTIAL:", auth_data.credential_data) return cbor.encode({"status": "OK"})
def test_get(): """test get""" device = SoftWebauthnDevice() device.cred_init(PKCRO['publicKey']['rpId'], b'randomhandle') assertion = device.get(PKCRO, 'https://example.org') assert assertion device.private_key.public_key().verify( assertion['response']['signature'], assertion['response']['authenticatorData'] + sha256(assertion['response']['clientDataJSON']), ec.ECDSA(hashes.SHA256()))
def get_auth_webauthn(user: "******") -> Authenticator: return Authenticator.objects.create( type=3, # u2f user=user, config={ "devices": [ { "binding": { "publicKey": "aowekroawker", "keyHandle": "devicekeyhandle", "appId": "https://dev.getsentry.net:8000/auth/2fa/u2fappid.json", }, "name": "Amused Beetle", "ts": 1512505334, }, { "binding": { "publicKey": "publickey", "keyHandle": "aowerkoweraowerkkro", "appId": "https://dev.getsentry.net:8000/auth/2fa/u2fappid.json", }, "name": "Sentry", "ts": 1512505334, }, { "name": "Alert Escargot", "ts": 1512505334, "binding": AuthenticatorData.create( sha256(b"test"), 0x41, 1, create_credential_object({ "publicKey": "webauthn", "keyHandle": "webauthn", }), ), }, ] }, )
def _ctap1_make_credential( self, client_data, rp, user, key_params, exclude_list, extensions, rk, uv, pin, event, on_keepalive, ): if rk or uv or ES256.ALGORITHM not in [p.alg for p in key_params]: raise CtapError(CtapError.ERR.UNSUPPORTED_OPTION) app_param = sha256(rp["id"].encode()) dummy_param = b"\0" * 32 for cred in exclude_list or []: key_handle = cred["id"] try: self.ctap1.authenticate(dummy_param, app_param, key_handle, True) raise ClientError.ERR.OTHER_ERROR() # Shouldn't happen except ApduError as e: if e.code == APDU.USE_NOT_SATISFIED: _call_polling( self.ctap1_poll_delay, event, on_keepalive, self.ctap1.register, dummy_param, dummy_param, ) raise ClientError.ERR.DEVICE_INELIGIBLE() return AttestationObject.from_ctap1( app_param, _call_polling( self.ctap1_poll_delay, event, on_keepalive, self.ctap1.register, client_data.hash, app_param, ), )
def u2f_complete(): data = cbor.loads(request.get_data())[0] client_data = ClientData.from_b64(data['clientData']) reg_data = RegistrationData.from_b64(data['registrationData']) print('clientData', client_data) print('U2F RegistrationData:', reg_data) att_obj = AttestationObject.from_ctap1(sha256(b'https://localhost:5000'), reg_data) print('AttestationObject:', att_obj) auth_data = att_obj.auth_data credentials.append(auth_data.credential_data) print('REGISTERED U2F CREDENTIAL:', auth_data.credential_data) return cbor.dumps({'status': 'OK'})
def test_solo(self, ): """ Solo specific tests """ # RNG command sc = SoloClient() sc.find_device(self.dev) sc.use_u2f() memmap = (0x08005000, 0x08005000 + 198 * 1024 - 8) total = 1024 * 16 with Test("Gathering %d random bytes..." % total): entropy = b"" while len(entropy) < total: entropy += sc.get_rng() with Test("Test entropy is close to perfect"): s = shannon_entropy(entropy) assert s > 7.98 print("Entropy is %.5f bits per byte." % s) with Test("Test Solo version command"): assert len(sc.solo_version()) == 3 with Test("Test bootloader is not active"): try: sc.write_flash(memmap[0], b"1234") except ApduError: pass sc.exchange = sc.exchange_fido2 req = SoloClient.format_request(SoloExtension.version, 0, b"A" * 16) a = sc.ctap2.get_assertion(sc.host, b"B" * 32, [{ "id": req, "type": "public-key" }]) with Test("Test custom command returned valid assertion"): assert a.auth_data.rp_id_hash == sha256(sc.host.encode("utf8")) assert a.credential["id"] == req assert (a.auth_data.flags & 0x5) == 0x5 with Test("Test Solo version and random commands with fido2 layer"): assert len(sc.solo_version()) == 3 sc.get_rng()
def test_fido2_bridge(self, solo): exchange = solo.exchange solo.exchange = solo.exchange_fido2 req = SoloClient.format_request(SoloExtension.version, 0, b"A" * 16) a = solo.ctap2.get_assertion(solo.host, b"B" * 32, [{ "id": req, "type": "public-key" }]) assert a.auth_data.rp_id_hash == sha256(solo.host.encode("utf8")) assert a.credential["id"] == req assert (a.auth_data.flags & 0x5) == 0x5 solo.get_rng() solo.exchange = exchange