mirror of
https://github.com/trussed-dev/fido2-tests.git
synced 2026-06-20 04:16:30 -07:00
add tests for dicekeys style backup
This commit is contained in:
Vendored
+126
-9
@@ -1,14 +1,35 @@
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
import ecdsa
|
||||
import hashlib
|
||||
from fido2.ctap1 import ApduError
|
||||
from fido2.ctap2 import CtapError
|
||||
from fido2.utils import sha256
|
||||
from solo.client import SoloClient
|
||||
from fido2.utils import hmac_sha256, sha256
|
||||
try:
|
||||
from solo.client import SoloClient
|
||||
except:
|
||||
from solo.devices.solo_v1 import Client as SoloClient
|
||||
|
||||
from solo.commands import SoloExtension
|
||||
|
||||
from tests.utils import shannon_entropy, verify, FidoRequest
|
||||
|
||||
# Is there no RFC for this?
|
||||
def keypair_from_seed(seed: bytes):
|
||||
assert isinstance(seed, bytes)
|
||||
assert len(seed) == 32
|
||||
|
||||
P256 = ecdsa.NIST256p
|
||||
scalar = int.from_bytes(seed, "little")
|
||||
iterations = 0
|
||||
while not (1 <= scalar < P256.order):
|
||||
seed = sha256(seed)
|
||||
scalar = int.from_bytes(seed, "little")
|
||||
iterations += 1
|
||||
|
||||
keypair = ecdsa.SigningKey.from_secret_exponent(scalar, P256)
|
||||
return keypair, iterations
|
||||
|
||||
@pytest.fixture(scope="module", params=["u2f"])
|
||||
def solo(request, device):
|
||||
@@ -76,7 +97,24 @@ class TestSolo(object):
|
||||
def test_load_external_key_wrong_length(self,solo, ):
|
||||
ext_key_cmd = 0x62
|
||||
with pytest.raises(CtapError) as e:
|
||||
solo.send_data_hid(ext_key_cmd, b'\x01\x00\x00\x00' + b'wrong length'*2)
|
||||
solo.send_data_hid(ext_key_cmd, b'\x01' + b'wrong length'*2)
|
||||
assert(e.value.code == CtapError.ERR.INVALID_LENGTH)
|
||||
|
||||
@pytest.mark.skipif(not IS_EXPERIMENTAL, reason="Experimental")
|
||||
def test_load_external_key_wrong_length_ext_state(self,solo, ):
|
||||
|
||||
key_A = b'A' * 32
|
||||
key_B = b'B' * 32
|
||||
ext_state1 = b"C" * 43
|
||||
ext_state2 = b"C" * 44
|
||||
version = b'\x01'
|
||||
|
||||
ext_key_cmd = 0x62
|
||||
print ('Enter user presence THREE times.')
|
||||
solo.send_data_hid(ext_key_cmd, version + key_A + ext_state1)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
solo.send_data_hid(ext_key_cmd, version + key_A + ext_state2)
|
||||
assert(e.value.code == CtapError.ERR.INVALID_LENGTH)
|
||||
|
||||
@pytest.mark.skipif(not IS_EXPERIMENTAL, reason="Experimental")
|
||||
@@ -84,7 +122,7 @@ class TestSolo(object):
|
||||
ext_key_cmd = 0x62
|
||||
verify(MCRes, GARes)
|
||||
print ('Enter user presence THREE times.')
|
||||
solo.send_data_hid(ext_key_cmd, b'\x01\x00\x00\x00' + b'Z' * 96)
|
||||
solo.send_data_hid(ext_key_cmd, b'\x01' + b'Z' * 32 + b'dicekeys key')
|
||||
|
||||
# Old credential should not exist now.
|
||||
with pytest.raises(CtapError) as e:
|
||||
@@ -93,14 +131,18 @@ class TestSolo(object):
|
||||
assert(e.value.code == CtapError.ERR.NO_CREDENTIALS)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skipif(not IS_EXPERIMENTAL, reason="Experimental")
|
||||
def test_load_external_key(self,solo, device,):
|
||||
|
||||
key_A = b'A' * 96
|
||||
key_B = b'B' * 96
|
||||
key_A = b'A' * 32
|
||||
key_B = b'B' * 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, b'\x01\x00\x00\x00' + key_A)
|
||||
solo.send_data_hid(ext_key_cmd, version + key_A + ext_state)
|
||||
|
||||
# New credential works.
|
||||
mc_A_req = FidoRequest()
|
||||
@@ -114,14 +156,89 @@ class TestSolo(object):
|
||||
|
||||
# Load up Key B and verify cred A doesn't exist.
|
||||
print ('Enter user presence THREE times.')
|
||||
solo.send_data_hid(ext_key_cmd, b'\x01\x00\x00\x00' + key_B)
|
||||
solo.send_data_hid(ext_key_cmd, version + key_B + ext_state)
|
||||
with pytest.raises(CtapError) as e:
|
||||
ga_A_res = device.sendGA(*FidoRequest(ga_A_req).toGA())
|
||||
assert(e.value.code == CtapError.ERR.NO_CREDENTIALS)
|
||||
|
||||
# Load up Key A and verify cred A is back.
|
||||
print ('Enter user presence THREE times.')
|
||||
solo.send_data_hid(ext_key_cmd, b'\x01\x00\x00\x00' + key_A)
|
||||
solo.send_data_hid(ext_key_cmd, version + key_A + ext_state)
|
||||
ga_A_res = device.sendGA(*FidoRequest(ga_A_req).toGA())
|
||||
verify(mc_A_res, ga_A_res, ga_A_req.cdh)
|
||||
|
||||
@pytest.mark.skipif(not IS_EXPERIMENTAL, reason="Experimental")
|
||||
def test_ext_state_in_credential_id(self,solo, device,):
|
||||
|
||||
key_A = b'A' * 32
|
||||
ext_state = b"I'm a dicekey key abc1234!!@@##"
|
||||
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())
|
||||
|
||||
assert ext_state in mc_A_res.auth_data.credential_data.credential_id
|
||||
|
||||
@pytest.mark.skipif(not IS_EXPERIMENTAL, reason="Experimental")
|
||||
def test_backup_credential_is_generated_correctly(self,solo, device,):
|
||||
|
||||
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())
|
||||
|
||||
credId = mc_A_res.auth_data.credential_data.credential_id
|
||||
uniqueId = credId[1:1+32]
|
||||
|
||||
extStateInCredId = credId[33:33 + len(ext_state)]
|
||||
|
||||
soloExtStateFieldSize = 43
|
||||
credMacInCredId = credId[33 + soloExtStateFieldSize: 33 + soloExtStateFieldSize + 32]
|
||||
|
||||
rpIdHash = sha256(mc_A_req.rp['id'].encode('utf8'))
|
||||
print("rp:", mc_A_req.rp['id'])
|
||||
from binascii import hexlify
|
||||
print("rpIdHash:", hexlify(rpIdHash))
|
||||
print("uniqueId:", hexlify(uniqueId))
|
||||
|
||||
assert version[0] == credId[0]
|
||||
assert ext_state == extStateInCredId
|
||||
|
||||
credMac = hmac_sha256(key_A, rpIdHash + version + uniqueId + ext_state)
|
||||
print("recomputed mac:", hexlify(credMac))
|
||||
print("recv'd mac :", hexlify(credMacInCredId))
|
||||
|
||||
assert credMac == credMacInCredId
|
||||
|
||||
credentialSeed = hmac_sha256(key_A, credMac)
|
||||
print('Computed private key:', hexlify(credentialSeed))
|
||||
key_pair = keypair_from_seed(credentialSeed)
|
||||
assert key_pair[1] == 0
|
||||
key_pair = key_pair[0]
|
||||
|
||||
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)
|
||||
|
||||
key_pair.verifying_key.verify(
|
||||
ga_res.signature,
|
||||
ga_res.auth_data + ga_req.cdh,
|
||||
sigdecode=ecdsa.util.sigdecode_der,
|
||||
hashfunc=hashlib.sha256
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user