Added tests for PIN.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2022-10-04 00:00:13 +02:00
parent c2e16fda41
commit ae237db9ca

247
tests/pico-fido/test_pin.py Normal file
View 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)