diff --git a/tests/conftest.py b/tests/conftest.py index b8a509e..14f3a74 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -290,7 +290,7 @@ class TestDevice: return self.ctap2.client_pin(*args, **kwargs) def sendPP(self, *args, **kwargs): - return self.client.pin_protocol.get_pin_token(test, *args, **kwargs) + return self.client.pin_protocol.get_pin_token(*args, **kwargs) def delay(secs): diff --git a/tests/fido2/pin/test_lockout.py b/tests/fido2/pin/test_lockout.py new file mode 100644 index 0000000..0a1644b --- /dev/null +++ b/tests/fido2/pin/test_lockout.py @@ -0,0 +1,49 @@ +import pytest + +from fido2.ctap import CtapError +from fido2.ctap2 import ES256, PinProtocolV1, AttestedCredentialData + +from tests.utils import * + + +def test_lockout(device,resetDevice): + pin = 'TestPin' + device.client.pin_protocol.set_pin(pin) + + pin_token = device.client.pin_protocol.get_pin_token(pin) + req = FidoRequest(pin_token = pin_token) + + req.pin_auth = hmac_sha256(pin_token, req.cdh)[:16] + + for i in range(1, 10): + err = CtapError.ERR.PIN_INVALID + if i in (3, 6): + err = CtapError.ERR.PIN_AUTH_BLOCKED + elif i >= 8: + err = [CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_INVALID] + + with pytest.raises(CtapError) as e: + device.sendPP( + "WrongPin", + ) + assert e.value.code == err or e.value.code in err + + attempts = 8 - i + if i > 8: + attempts = 0 + + res = device.ctap2.client_pin(1, PinProtocolV1.CMD.GET_RETRIES) + assert res[3] == attempts + + if err == CtapError.ERR.PIN_AUTH_BLOCKED: + device.reboot() + + with pytest.raises(CtapError) as e: + device.sendMC(*req.toMC()) + + device.reboot() + + with pytest.raises(CtapError) as e: + device.sendPP(pin) + assert e.value.code == CtapError.ERR.PIN_BLOCKED + diff --git a/tests/fido2/pin/test_pin.py b/tests/fido2/pin/test_pin.py new file mode 100644 index 0000000..aeb1c66 --- /dev/null +++ b/tests/fido2/pin/test_pin.py @@ -0,0 +1,160 @@ +import pytest + +from fido2.ctap import CtapError +from fido2.ctap2 import ES256, PinProtocolV1, AttestedCredentialData + +from tests.utils import * + +PIN1 = '123456789A' +PIN2 = 'ABCDEF' + +@pytest.fixture(scope="module", params = [PIN1]) +def SetPinRes(request,device): + device.reset() + + pin = request.param + req = FidoRequest() + + device.client.pin_protocol.set_pin(pin) + pin_token = device.client.pin_protocol.get_pin_token(pin) + pin_auth = hmac_sha256(pin_token, req.cdh)[:16] + + req = FidoRequest(req, pin_protocol = 1, pin_auth = pin_auth) + + res = device.sendMC( + *req.toMC(), + ) + setattr(res,'request',req) + setattr(res,'PIN',pin) + return res + + +@pytest.fixture(scope="module") +def CPRes(request,device,SetPinRes): + res = device.sendCP(1, PinProtocolV1.CMD.GET_KEY_AGREEMENT) + return res + +@pytest.fixture(scope="module") +def MCPinRes(device,SetPinRes): + req = FidoRequest(SetPinRes) + res = device.sendMC( + *req.toMC(), + ) + setattr(res,'request',req) + return res + +@pytest.fixture(scope='class') +def GAPinRes(device,MCPinRes): + req = FidoRequest(MCPinRes) + res = device.sendGA( + *req.toGA(), + ) + setattr(res,'request',req) + return res + +class TestPin(object): + def test_pin(self,CPRes): + pass + + def test_get_key_agreement_fields(self,CPRes): + key = CPRes[1] + assert "Is public key" and key[1] == 2 + assert "Is P256" and key[-1] == 1 + assert "Is ALG_ECDH_ES_HKDF_256" and key[3] == -25 + + assert "Right key" and len(key[-3]) == 32 and isinstance(key[-3], bytes) + + def test_verify_flag(self, device, SetPinRes): + reg = device.sendMC(*FidoRequest(SetPinRes).toMC()) + assert reg.auth_data.flags & (1 << 2) + + + def test_change_pin(self, device, SetPinRes, ): + device.client.pin_protocol.change_pin(PIN1, PIN2) + + pin_token = device.client.pin_protocol.get_pin_token(PIN2) + pin_auth = hmac_sha256(pin_token, SetPinRes.request.cdh)[:16] + + SetPinRes.request.pin_token = pin_token + SetPinRes.request.pin_auth = pin_auth + SetPinRes.PIN = PIN2 + + reg = device.sendMC(*FidoRequest(SetPinRes).toMC()) + auth = device.sendGA( + *FidoRequest(SetPinRes, allow_list = [{'type': 'public-key', 'id': reg.auth_data.credential_data.credential_id}] + ).toGA()) + + assert reg.auth_data.flags & (1 << 2) + assert auth.auth_data.flags & (1 << 2) + + verify(reg,auth, cdh = SetPinRes.request.cdh) + + def test_get_no_pin_auth(self, device, SetPinRes): + + reg = device.sendMC(*FidoRequest(SetPinRes).toMC()) + allow_list = [{'type': 'public-key', 'id': reg.auth_data.credential_data.credential_id}] + auth = device.sendGA( + *FidoRequest(SetPinRes, allow_list = allow_list, pin_auth = None, pin_protocol = None + ).toGA()) + + assert not (auth.auth_data.flags & (1 << 2)) + + + with pytest.raises(CtapError) as e: + reg = device.sendMC(*FidoRequest(SetPinRes, pin_auth = None, pin_protocol = None).toMC()) + + assert(e.value.code == CtapError.ERR.PIN_REQUIRED) + + def test_zero_length_pin_auth(self, device, SetPinRes): + with pytest.raises(CtapError) as e: + reg = device.sendMC(*FidoRequest(SetPinRes, pin_auth = b'',).toMC()) + assert(e.value.code == CtapError.ERR.PIN_AUTH_INVALID) + + with pytest.raises(CtapError) as e: + reg = device.sendGA(*FidoRequest(SetPinRes, pin_auth = b'',).toGA()) + assert(e.value.code == CtapError.ERR.PIN_AUTH_INVALID) + + def test_make_credential_no_pin(self, device, SetPinRes): + with pytest.raises(CtapError) as e: + reg = device.sendMC(*FidoRequest().toMC()) + assert(e.value.code == CtapError.ERR.PIN_REQUIRED) + + def test_get_assertion_no_pin(self, device, SetPinRes): + with pytest.raises(CtapError) as e: + reg = device.sendGA(*FidoRequest().toGA()) + assert(e.value.code == CtapError.ERR.NO_CREDENTIALS) + + + +def test_pin_attempts(device, SetPinRes): + # Flip 1 bit + pin = SetPinRes.PIN + pin_wrong = list(pin) + c = pin[len(pin) // 2] + + pin_wrong[len(pin) // 2] = chr(ord(c) ^ 1) + pin_wrong = "".join(pin_wrong) + + for i in range(1, 3): + with pytest.raises(CtapError) as e: + device.sendPP(pin_wrong) + assert e.value.code == CtapError.ERR.PIN_INVALID + + print("Check there is %d pin attempts left" % (8 - i)) + res = device.ctap2.client_pin(1, PinProtocolV1.CMD.GET_RETRIES) + assert res[3] == (8 - i) + + for i in range(1, 3): + with pytest.raises(CtapError) as e: + device.sendPP(pin_wrong) + assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED + + device.reboot() + + SetPinRes.request.pin_token = device.client.pin_protocol.get_pin_token(pin) + SetPinRes.request.pin_auth = hmac_sha256(SetPinRes.request.pin_token, SetPinRes.request.cdh)[:16] + + reg = device.sendMC(*FidoRequest(SetPinRes).toMC()) + + res = device.ctap2.client_pin(1, PinProtocolV1.CMD.GET_RETRIES) + assert res[3] == (8) diff --git a/tests/fido2/pin/test_set_pin.py b/tests/fido2/pin/test_set_pin.py new file mode 100644 index 0000000..e5393d1 --- /dev/null +++ b/tests/fido2/pin/test_set_pin.py @@ -0,0 +1,57 @@ +import pytest + +from fido2.ctap import CtapError +from fido2.ctap2 import ES256, PinProtocolV1, AttestedCredentialData + +from tests.utils import * + +class TestSetPin(object): + def test_send_zero_length_pin_auth(self,resetDevice): + with pytest.raises(CtapError) as e: + reg = resetDevice.sendMC(*FidoRequest(pin_auth = b'',).toMC()) + assert(e.value.code == CtapError.ERR.PIN_NOT_SET) + + with pytest.raises(CtapError) as e: + reg = resetDevice.sendGA(*FidoRequest(pin_auth = b'',).toGA()) + assert(e.value.code in (CtapError.ERR.PIN_NOT_SET, CtapError.ERR.NO_CREDENTIALS)) + + + def test_set_pin(self,device): + device.client.pin_protocol.set_pin('TestPin') + device.reset() + + + def test_set_pin_too_big(self, device): + with pytest.raises(CtapError) as e: + device.client.pin_protocol.set_pin('A' * 64) + assert e.value.code == CtapError.ERR.PIN_POLICY_VIOLATION + + + def test_get_pin_token_but_no_pin_set(self, device): + with pytest.raises(CtapError) as e: + device.client.pin_protocol.get_pin_token('TestPin') + assert e.value.code == CtapError.ERR.PIN_NOT_SET + + def test_change_pin_but_no_pin_set(self, device): + with pytest.raises(CtapError) as e: + device.client.pin_protocol.change_pin('TestPin', "1234") + assert e.value.code == CtapError.ERR.PIN_NOT_SET + + def test_setting_pin_and_get_info(self, device): + device.client.pin_protocol.set_pin('TestPin') + + with pytest.raises(CtapError) as e: + device.client.pin_protocol.set_pin('TestPin') + + info = device.ctap2.get_info() + + assert info.options['clientPin'] + + pin_token = device.client.pin_protocol.get_pin_token('TestPin') + + res = device.sendCP(1, PinProtocolV1.CMD.GET_RETRIES) + assert res[3] == 8 + + device.reset() + +