Example #1
0
async def add_resident_credential(
    ctx: wire.Context, msg: WebAuthnAddResidentCredential
) -> Success:
    if not msg.credential_id:
        raise wire.ProcessError("Missing credential ID parameter.")

    try:
        cred = Fido2Credential.from_cred_id(bytes(msg.credential_id), None)
    except Exception:
        text = Text("Import credential", ui.ICON_WRONG, ui.RED)
        text.normal(
            "The credential you are",
            "trying to import does",
            "not belong to this",
            "authenticator.",
        )
        await require_confirm(ctx, text, confirm=None, cancel="Close")
        raise wire.ActionCancelled("Cancelled") from None

    content = ConfirmContent(ConfirmAddCredential(cred))
    await require_confirm(ctx, content)

    if store_resident_credential(cred):
        return Success(message="Credential added")
    else:
        raise wire.ProcessError("Internal credential storage is full.")
Example #2
0
def store_resident_credential(cred: Fido2Credential) -> bool:
    slot = None
    for i in range(
            _RESIDENT_CREDENTIAL_START_KEY,
            _RESIDENT_CREDENTIAL_START_KEY + _MAX_RESIDENT_CREDENTIALS,
    ):
        stored_cred_data = common._get(common._APP_FIDO2, i)
        if stored_cred_data is None:
            if slot is None:
                slot = i
            continue

        stored_rp_id_hash = stored_cred_data[:32]
        stored_cred_id = stored_cred_data[32:]

        if cred.rp_id_hash != stored_rp_id_hash:
            # Stored credential is not for this RP ID.
            continue

        stored_cred = Fido2Credential.from_cred_id(stored_cred_id,
                                                   stored_rp_id_hash)
        if stored_cred is None:
            # Stored credential is not for this RP ID.
            continue

        # If a credential for the same RP ID and user ID already exists, then overwrite it.
        if stored_cred.user_id == cred.user_id:
            slot = i
            break

    if slot is None:
        return False

    common._set(common._APP_FIDO2, slot, cred.rp_id_hash + cred.id)
    return True
Example #3
0
def get_resident_credential(
        index: int,
        rp_id_hash: Optional[bytes] = None) -> Optional[Credential]:
    if not (0 <= index < _MAX_RESIDENT_CREDENTIALS):
        return None

    stored_cred_data = common.get(common.APP_WEBAUTHN,
                                  index + _RESIDENT_CREDENTIAL_START_KEY)
    if stored_cred_data is None:
        return None

    stored_rp_id_hash = stored_cred_data[:32]
    stored_cred_id = stored_cred_data[32:]

    if rp_id_hash is not None and rp_id_hash != stored_rp_id_hash:
        # Stored credential is not for this RP ID.
        return None

    stored_cred = Fido2Credential.from_cred_id(stored_cred_id,
                                               stored_rp_id_hash)
    if stored_cred is None:
        return None

    stored_cred.index = index
    return stored_cred
Example #4
0
    def test_fido2_credential_decode(self):
        mnemonic_secret = b"all all all all all all all all all all all all"
        mnemonic.get_secret = lambda: mnemonic_secret
        storage.is_initialized = lambda: True

        cred_id = (
            b"f1d0020013e65c865634ad8abddf7a66df56ae7d8c3afd356f76426801508b2e"
            b"579bcb3496fe6396a6002e3cd6d80f6359dfa9961e24c544bfc2f26acec1b8d8"
            b"78ba56727e1f6a7b5176c607552aea63a5abe5d826d69fab3063edfa0201d9a5"
            b"1013d69eddb2eff37acdd5963f"
        )

        rp_id = "example.com"
        rp_id_hash = sha256(rp_id).digest()

        user_id = (
            b"3082019330820138a0030201023082019330820138a003020102308201933082"
        )

        user_name = "*****@*****.**"

        creation_time = 2

        public_key = (
            b"0451f0d4c307bc737c90ac605c6279f7d01e451798aa7b74df550fdb43a7760c"
            b"7c02b5107fef42094d00f52a9b1e90afb90e1b9decbf15a6f13d4f882de857e2"
            b"f4"
        )

        cred_random = (
            b"36a9b5d71c13ed54594474b54073af1fb03ea91cd056588909dae43ae2f35dbf"
        )

        # Load credential.
        cred = Fido2Credential.from_cred_id(unhexlify(cred_id), rp_id_hash)
        self.assertIsNotNone(cred)

        # Check credential data.
        self.assertEqual(hexlify(cred.id), cred_id)
        self.assertEqual(cred.rp_id, rp_id)
        self.assertEqual(cred.rp_id_hash, rp_id_hash)
        self.assertEqual(hexlify(cred.user_id), user_id)
        self.assertEqual(cred.user_name, user_name)
        self.assertEqual(cred.creation_time, creation_time)
        self.assertTrue(cred.hmac_secret)
        self.assertIsNone(cred.rp_name)
        self.assertIsNone(cred.user_display_name)

        # Check credential keys.
        self.assertEqual(hexlify(cred.hmac_secret_key()), cred_random)

        cred_public_key = nist256p1.publickey(cred.private_key(), False)
        self.assertEqual(hexlify(cred_public_key), public_key)
    def test_truncation(self):
        cred = Fido2Credential()
        cred.truncate_names()
        self.assertIsNone(cred.rp_name)
        self.assertIsNone(cred.user_name)
        self.assertIsNone(cred.user_display_name)

        cred.rp_name = "a" * (NAME_MAX_LENGTH - 2) + "\u0123"
        cred.user_name = "a" * (NAME_MAX_LENGTH - 1) + "\u0123"
        cred.user_display_name = "a" * NAME_MAX_LENGTH + "\u0123"
        cred.truncate_names()
        self.assertEqual(cred.rp_name, "a" * (NAME_MAX_LENGTH - 2) + "\u0123")
        self.assertEqual(cred.user_name, "a" * (NAME_MAX_LENGTH - 1))
        self.assertEqual(cred.user_display_name, "a" * NAME_MAX_LENGTH)
    def test_allow_list_processing(self):
        a1 = Fido2Credential()
        a1.user_id = b"user-a"
        a1.user_name = "user-a"
        a1.creation_time = 1

        a2 = Fido2Credential()
        a2.user_id = b"user-a"
        a2.user_display_name = "User A"
        a2.creation_time = 3

        a3 = Fido2Credential()
        a3.user_id = b"user-a"
        a3.user_name = "User A"
        a3.creation_time = 4

        b1 = Fido2Credential()
        b1.user_id = b"user-b"
        b1.creation_time = 2

        b2 = Fido2Credential()
        b2.user_id = b"user-b"
        b2.creation_time = 5

        b3 = Fido2Credential()
        b3.user_id = b"user-b"
        b3.creation_time = 5

        c1 = U2fCredential()

        c2 = U2fCredential()

        self.assertEqual(
            sorted(distinguishable_cred_list([a1, a2, a3, b1, b2, c1, c2])),
            [b2, a3, a1, c1])
        self.assertEqual(
            sorted(distinguishable_cred_list([c2, c1, b2, b1, a3, a2, a1])),
            [b2, a3, a1, c2])

        # Test input by creation time.
        self.assertEqual(
            sorted(distinguishable_cred_list([b2, a3, c1, a2, b1, a1, c2])),
            [b2, a3, a1, c1])
        self.assertEqual(
            sorted(distinguishable_cred_list([c2, a1, b1, a2, c1, a3, b2])),
            [b2, a3, a1, c2])

        # Test duplicities.
        self.assertEqual(
            sorted(distinguishable_cred_list([c1, a1, a1, c2, c1])), [a1, c1])
        self.assertEqual(sorted(distinguishable_cred_list([b2, b3])), [b2])
        self.assertEqual(sorted(distinguishable_cred_list([b3, b2])), [b3])
Example #7
0
def get_resident_credentials(rp_id_hash: Optional[bytes]) -> List[Credential]:
    creds = []  # type: List[Credential]
    for i in range(
            _RESIDENT_CREDENTIAL_START_KEY,
            _RESIDENT_CREDENTIAL_START_KEY + _MAX_RESIDENT_CREDENTIALS,
    ):
        stored_cred_data = common._get(common._APP_FIDO2, i)
        if stored_cred_data is None:
            continue

        stored_rp_id_hash = stored_cred_data[:32]
        stored_cred_id = stored_cred_data[32:]

        if rp_id_hash is not None and rp_id_hash != stored_rp_id_hash:
            # Stored credential is not for this RP ID.
            continue

        stored_cred = Fido2Credential.from_cred_id(stored_cred_id,
                                                   stored_rp_id_hash)
        if stored_cred is not None:
            creds.append(stored_cred)

    return creds
Example #8
0
def _credential_from_data(index: int, data: bytes) -> Fido2Credential:
    rp_id_hash = data[:RP_ID_HASH_LENGTH]
    cred_id = data[RP_ID_HASH_LENGTH:]
    cred = Fido2Credential.from_cred_id(cred_id, rp_id_hash)
    cred.index = index
    return cred