mirror of
https://github.com/polhenarejos/pico-fido.git
synced 2026-01-05 04:58:22 +08:00
Added tests for PIN.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
247
tests/pico-fido/test_pin.py
Normal file
247
tests/pico-fido/test_pin.py
Normal file
@@ -0,0 +1,247 @@
|
||||
import os
|
||||
import pytest
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2.pin import PinProtocolV1
|
||||
from fido2.client import ClientPin
|
||||
from fido2.webauthn import UserVerificationRequirement
|
||||
from fido2.utils import hmac_sha256
|
||||
|
||||
from utils import *
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def client_pin(resetdevice):
|
||||
return ClientPin(resetdevice.client()._backend.ctap2)
|
||||
|
||||
def test_lockout(device, resetdevice, client_pin):
|
||||
pin = "TestPin"
|
||||
client_pin.set_pin(pin)
|
||||
|
||||
pin_token = client_pin.get_pin_token(pin)
|
||||
|
||||
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:
|
||||
client_pin.get_pin_token("WrongPin")
|
||||
assert e.value.code == err or e.value.code in err
|
||||
|
||||
attempts = 8 - i
|
||||
if i > 8:
|
||||
attempts = 0
|
||||
|
||||
res = client_pin.get_pin_retries()
|
||||
assert res[0] == attempts
|
||||
|
||||
if err == CtapError.ERR.PIN_AUTH_BLOCKED:
|
||||
device.reboot()
|
||||
client_pin = ClientPin(resetdevice.client()._backend.ctap2)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC()
|
||||
|
||||
device.reboot()
|
||||
client_pin = ClientPin(resetdevice.client()._backend.ctap2)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.get_pin_token(pin)
|
||||
assert e.value.code == CtapError.ERR.PIN_BLOCKED
|
||||
|
||||
def test_send_zero_length_pin_auth(device):
|
||||
device.reset()
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.MC(pin_uv_param=b"")
|
||||
assert e.value.code == CtapError.ERR.PIN_NOT_SET
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.GA(pin_uv_param=b"")
|
||||
assert e.value.code in (CtapError.ERR.PIN_NOT_SET, CtapError.ERR.NO_CREDENTIALS)
|
||||
|
||||
def test_set_pin(device, client_pin):
|
||||
device.reset()
|
||||
client_pin.set_pin("TestPin")
|
||||
device.reset()
|
||||
|
||||
def test_set_pin_too_big(client_pin):
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.set_pin("A" * 64)
|
||||
assert e.value.code == CtapError.ERR.PIN_POLICY_VIOLATION
|
||||
|
||||
def test_get_pin_token_but_no_pin_set(resetdevice, client_pin):
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.get_pin_token("TestPin")
|
||||
assert e.value.code == CtapError.ERR.PIN_NOT_SET
|
||||
|
||||
def test_change_pin_but_no_pin_set(device, client_pin):
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.change_pin("TestPin", "1234")
|
||||
assert e.value.code == CtapError.ERR.PIN_NOT_SET
|
||||
|
||||
def test_setting_pin_and_get_info(device, client_pin, info):
|
||||
device.reset()
|
||||
client_pin.set_pin("TestPin")
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.set_pin("TestPin")
|
||||
|
||||
assert info.options["clientPin"]
|
||||
|
||||
pin_token = client_pin.get_pin_token("TestPin")
|
||||
|
||||
res = client_pin.get_pin_retries()
|
||||
assert res[0] == 8
|
||||
|
||||
device.reset()
|
||||
|
||||
PIN1 = "12345678"
|
||||
PIN2 = "ABCDEF"
|
||||
|
||||
@pytest.fixture(scope="class", params=[PIN1])
|
||||
def SetPinRes(request, device, client_pin):
|
||||
device.reset()
|
||||
|
||||
pin = request.param
|
||||
|
||||
client_pin.set_pin(pin)
|
||||
|
||||
res = device.doMC(user_verification=UserVerificationRequirement.REQUIRED)
|
||||
return res
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def CPRes(request, device, client_pin):
|
||||
res = client_pin.ctap.client_pin(2, ClientPin.CMD.GET_KEY_AGREEMENT)
|
||||
return res
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def MCPinRes(device):
|
||||
res = device.doMC(user_verification=UserVerificationRequirement.REQUIRED)
|
||||
return res
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def GAPinRes(device, MCPinRes):
|
||||
res = device.doGA()
|
||||
return res
|
||||
|
||||
def test_pin(CPRes):
|
||||
pass
|
||||
|
||||
def test_set_pin_twice(device, client_pin):
|
||||
""" Setting pin when a pin is already set should result in error NotAllowed. """
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.set_pin('1234')
|
||||
client_pin.set_pin('1234')
|
||||
|
||||
assert e.value.code == CtapError.ERR.NOT_ALLOWED
|
||||
|
||||
|
||||
def test_get_key_agreement_fields(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(device, SetPinRes):
|
||||
reg = device.doMC(user_verification=UserVerificationRequirement.REQUIRED)['res'].attestation_object
|
||||
assert reg.auth_data.flags & (1 << 2)
|
||||
|
||||
def test_get_no_pin_auth(device):
|
||||
|
||||
reg = device.doMC()['res'].attestation_object
|
||||
allow_list = [
|
||||
{"type": "public-key", "id": reg.auth_data.credential_data.credential_id}
|
||||
]
|
||||
auth = device.GA(allow_list=allow_list)['res']
|
||||
assert not (auth.auth_data.flags & (1 << 2))
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.MC()
|
||||
|
||||
assert e.value.code == CtapError.ERR.PUAT_REQUIRED
|
||||
|
||||
def test_zero_length_pin_auth(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.MC(pin_uv_param=b"")
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.GA(pin_uv_param=b"")
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
|
||||
|
||||
def test_make_credential_no_pin(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.MC()
|
||||
assert e.value.code == CtapError.ERR.PUAT_REQUIRED
|
||||
|
||||
def test_get_assertion_no_pin(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.GA()
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
def test_change_pin(device, client_pin):
|
||||
device.reset()
|
||||
client_pin.set_pin(PIN1)
|
||||
client_pin.change_pin(PIN1, PIN2)
|
||||
|
||||
pin_token = client_pin.get_pin_token(pin=PIN2)
|
||||
cdh = os.urandom(32)
|
||||
pin_auth = hmac_sha256(pin_token, cdh)[:32]
|
||||
|
||||
reg = device.MC(pin_uv_param=pin_auth, pin_uv_protocol=2, client_data_hash=cdh)['res']
|
||||
|
||||
pin_token = client_pin.get_pin_token(pin=PIN2)
|
||||
pin_auth = hmac_sha256(pin_token, cdh)[:32]
|
||||
auth = device.GA(pin_uv_param=pin_auth, pin_uv_protocol=2, client_data_hash=cdh, allow_list=[{
|
||||
"type": "public-key",
|
||||
"id": reg.auth_data.credential_data.credential_id,
|
||||
}])['res']
|
||||
|
||||
assert reg.auth_data.flags & (1 << 2)
|
||||
assert auth.auth_data.flags & (1 << 2)
|
||||
|
||||
verify(reg, auth, client_data_hash=cdh)
|
||||
|
||||
def test_pin_attempts(device, client_pin):
|
||||
# Flip 1 bit
|
||||
pin = PIN1
|
||||
device.reset()
|
||||
client_pin.set_pin(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:
|
||||
pin_token = client_pin.get_pin_token(pin=pin_wrong)
|
||||
assert e.value.code == CtapError.ERR.PIN_INVALID
|
||||
|
||||
print("Check there is %d pin attempts left" % (8 - i))
|
||||
res = client_pin.get_pin_retries()
|
||||
assert res[0] == (8 - i)
|
||||
|
||||
for i in range(1, 3):
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.get_pin_token(pin_wrong)
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED
|
||||
|
||||
device.reboot()
|
||||
client_pin = ClientPin(device.client()._backend.ctap2)
|
||||
|
||||
pin_token = client_pin.get_pin_token(pin=pin)
|
||||
cdh = os.urandom(32)
|
||||
pin_auth = hmac_sha256(pin_token, cdh)[:32]
|
||||
|
||||
reg = device.MC(pin_uv_param=pin_auth, pin_uv_protocol=2, client_data_hash=cdh)['res']
|
||||
|
||||
res = client_pin.get_pin_retries()
|
||||
assert res[0] == (8)
|
||||
Reference in New Issue
Block a user