def check_error(self, data, err=None): assert (len(data) == 1) if err is None: if data[0] != 0: raise CtapError(data[0]) elif data[0] != err: raise ValueError('Unexpected error: %02x' % data[0])
def check_error(data, err=None): assert len(data) == 1 if err is None: if data[0] != 0: raise CtapError(data[0]) elif data[0] != err: raise ValueError("Unexpected error: %02x" % data[0])
def test_make_credential_existing_key(self, PatchedCTAP2): dev = mock.Mock() dev.capabilities = CAPABILITY.CBOR ctap2 = mock.MagicMock() ctap2.get_info.return_value = Info(_INFO_NO_PIN) ctap2.make_credential.side_effect = CtapError( CtapError.ERR.CREDENTIAL_EXCLUDED) PatchedCTAP2.return_value = ctap2 client = Fido2Client(dev, APP_ID) try: client.make_credential( PublicKeyCredentialCreationOptions( rp, user, challenge, [{ "type": "public-key", "alg": -7 }], authenticator_selection={ "userVerification": "discouraged" }, )) self.fail("make_credential did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE) ctap2.get_info.assert_called_with() ctap2.make_credential.assert_called_once()
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 exchange_hid(self, cmd, addr=0, data=b"A" * 16): req = SoloClient.format_request(cmd, addr, data) data = self.send_data_hid(SoloBootloader.HIDCommandBoot, req) ret = data[0] if ret != CtapError.ERR.SUCCESS: raise CtapError(ret) return data[1:]
def exchange_u2f(self, cmd, addr=0, data=b"A" * 16): appid = b"A" * 32 chal = b"B" * 32 req = SoloClient.format_request(cmd, addr, data) res = self.ctap1.authenticate(chal, appid, req) ret = res.signature[0] if ret != CtapError.ERR.SUCCESS: raise CtapError(ret) return res.signature[1:]
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 get_assertion(self, get_assertion_request: dict) -> AssertionResponse: request = CtapGetAssertionRequest.create(get_assertion_request) if request.user_verification_required: self._verify_user(request.rp_id) creds = self._find_credentials(request.allow_list, request.rp_id) if len(creds) == 0: raise CtapError(CtapError.ERR.NO_CREDENTIALS) # Note: consecutive calls to get_assertion will override this context self._next_assertions_ctx = CtapGetNextAssertionContext( request=request, creds=creds, cred_counter=0) return self._get_assertion(request, self._next_assertions_ctx)
def test_make_credential_existing_key(self): dev = mock.Mock() dev.capabilities = CAPABILITY.CBOR client = Fido2Client(dev, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_info.return_value = Info(_INFO_NO_PIN) client.ctap.make_credential.side_effect = CtapError( CtapError.ERR.CREDENTIAL_EXCLUDED) try: client.make_credential(rp, user, challenge, timeout=1) self.fail('make_credential did not raise error') except ClientError as e: self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE) client.ctap.get_info.assert_called_with() client.ctap.make_credential.assert_called_once()
def _ctap2_get_assertion(self, client_data, rp_id, allow_list, extensions, uv, up, pin, event, on_keepalive): pin_auth = None pin_protocol = None if pin: pin_protocol = self.pin_protocol.VERSION pin_token = self.pin_protocol.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, client_data.hash)[:16] elif self.info.options.get("clientPin") and not uv: raise ClientError.ERR.BAD_REQUEST("PIN required but not provided") if uv: options = {"uv": True} if not up: options = {"uv": True, "up": False} else: options = None if not up: options = {"up": False} if allow_list: # Filter out credential IDs which are too long max_len = self.info.max_cred_id_length if max_len: allow_list = [e for e in allow_list if len(e) <= max_len] if not allow_list: raise CtapError(CtapError.ERR.NO_CREDENTIALS) # Reject the request if too many credentials remain. max_creds = self.info.max_creds_in_list if max_creds and len(allow_list) > max_creds: raise ClientError.ERR.BAD_REQUEST("allow_list too long") return self.ctap2.get_assertions( rp_id, client_data.hash, allow_list if allow_list else None, extensions, options, pin_auth, pin_protocol, event, on_keepalive, )
def _find_credentials(cls, allow_list: List[PublicKeyCredentialDescriptor], rp_id: str) -> List[Credential]: service_name = cls.get_service_name(rp_id) if not allow_list: # Currently, we only support get assertion flows where a credential id is supplied; # because we ought to pass in a user-id to the backend keyring; this user-id is encoded in the cred-id. raise CtapError(CtapError.ERR.MISSING_PARAMETER) res = [] for allowed_cred in allow_list: valid_cred = allowed_cred.id and len(allowed_cred.id) == 32 if not valid_cred: continue user_uuid, key_password = allowed_cred.id[:16], allowed_cred.id[ 16:] # noinspection PyBroadException try: encoded_password = keyring.get_password( service_name=service_name, username=user_uuid.hex()) if not encoded_password: continue decoded_password = base64.b64decode(encoded_password) alg = int.from_bytes(decoded_password[:2], 'big', signed=True) cose_key_cls = cose.CoseKey.for_alg(alg) if cose_key_cls == cose.UnsupportedKey: continue private_key_bytes = decoded_password[2:] private_key = serialization.load_der_private_key( private_key_bytes, password=key_password) signer = CtapPrivateKeyWrapper.create(cose_key_cls, private_key) cred = Credential(allowed_cred.id, signer) res.append(cred) except Exception: # Best effort continue return res
def _verify_user(self, rp_id: str): verified = self._user_verifier.verify_user(rp_id) if not verified: raise CtapError(CtapError.ERR.NOT_ALLOWED)
def get_next_assertion(self) -> AssertionResponse: if not self._next_assertions_ctx: raise CtapError(CtapError.ERR.NOT_ALLOWED) return self._get_assertion(self._next_assertions_ctx.request, self._next_assertions_ctx)