예제 #1
0
    def test_backup_credential_is_generated_correctly(
        self,
        solo,
        device,
    ):
        import seedweed
        from binascii import hexlify

        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())

        rpIdHash = sha256(mc_A_req.rp["id"].encode("utf8"))

        credId = mc_A_res.auth_data.credential_data.credential_id

        (
            uniqueId,
            extStateInCredId,
            credMacInCredId,
        ) = seedweed.nonce_extstate_mac_from_credential_id(credId)

        seedweed.validate_credential_id(key_A, credId, rpIdHash)
        credMac = hmac_sha256(key_A, rpIdHash + version + uniqueId + ext_state)

        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)

        # Independently create the key and verify
        _, _, keypair, iterations = seedweed.keypair_from_seed_mac(
            key_A, credMac)
        assert iterations == 1
        keypair.verifying_key.verify(
            ga_res.signature,
            ga_res.auth_data + ga_req.cdh,
            sigdecode=ecdsa.util.sigdecode_der,
            hashfunc=hashlib.sha256,
        )
예제 #2
0
    def test_hmac_secret_different_with_uv(self, device, MCHmacSecret, cipher,
                                           sharedSecret):
        salts = [salt1]
        key_agreement, shared_secret = sharedSecret
        salt_enc, salt_auth = get_salt_params(cipher, shared_secret, salts)
        req = FidoRequest(extensions={
            "hmac-secret": {
                1: key_agreement,
                2: salt_enc,
                3: salt_auth
            }
        })
        auth_no_uv = device.sendGA(*req.toGA())
        assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0

        ext_no_uv = auth_no_uv.auth_data.extensions
        assert ext_no_uv
        assert "hmac-secret" in ext_no_uv
        assert isinstance(ext_no_uv["hmac-secret"], bytes)
        assert len(ext_no_uv["hmac-secret"]) == len(salts) * 32

        verify(MCHmacSecret, auth_no_uv, req.cdh)

        # Now get same auth with UV
        pin = '1234'
        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,
                          extensions={
                              "hmac-secret": {
                                  1: key_agreement,
                                  2: salt_enc,
                                  3: salt_auth
                              }
                          })

        auth_uv = device.sendGA(*req.toGA())
        assert auth_uv.auth_data.flags & (1 << 2)
        ext_uv = auth_uv.auth_data.extensions
        assert ext_uv
        assert "hmac-secret" in ext_uv
        assert isinstance(ext_uv["hmac-secret"], bytes)
        assert len(ext_uv["hmac-secret"]) == len(salts) * 32

        verify(MCHmacSecret, auth_uv, req.cdh)

        # Now see if the hmac-secrets are different
        assert ext_no_uv['hmac-secret'] != ext_uv['hmac-secret']
예제 #3
0
    def test_get_next_assertion_has_extension(self, device, MCHmacSecret,
                                              cipher, sharedSecret, salts,
                                              fixed_users):
        """ Check that get_next_assertion properly returns extension information for multiple accounts. """
        accounts = 3
        regs = []
        auths = []
        rp = {"id": "example_2.org", "name": "ExampleRP_2"}

        for i in range(0, accounts):
            req = FidoRequest(extensions={"hmac-secret": True},
                              options={"rk": True},
                              rp=rp,
                              user=fixed_users[i])
            res = device.sendMC(*req.toMC())
            regs.append(res)

        key_agreement, shared_secret = sharedSecret
        salt_enc, salt_auth = get_salt_params(cipher, shared_secret, salts)
        req = FidoRequest(
            extensions={
                "hmac-secret": {
                    1: key_agreement,
                    2: salt_enc,
                    3: salt_auth
                }
            },
            rp=rp,
        )

        auth = device.sendGA(*req.toGA())
        assert auth.number_of_credentials == accounts

        auths.append(auth)
        for i in range(0, accounts - 1):
            auths.append(device.ctap2.get_next_assertion())

        for x in auths:
            assert x.auth_data.flags & (1 << 7)  # has extension
            ext = auth.auth_data.extensions
            assert ext
            assert "hmac-secret" in ext
            assert isinstance(ext["hmac-secret"], bytes)
            assert len(ext["hmac-secret"]) == len(salts) * 32
            dec = cipher.decryptor()
            key = dec.update(ext["hmac-secret"]) + dec.finalize()

        auths.reverse()
        for x, y in zip(regs, auths):
            verify(x, y, req.cdh)
    def test_exclude_list2(self, device, MCRes):
        req = FidoRequest(
            MCRes,
            exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}],
        )

        device.sendMC(*req.toMC())
    def test_unsupported_algorithm(self, device, MCRes):
        req = FidoRequest(MCRes, key_params=[{"alg": 1337, "type": "public-key"}])

        with pytest.raises(CtapError) as e:
            device.sendMC(*req.toMC())

        assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
    def test_missing_pubKeyCredParams_type(self, device, MCRes):
        req = FidoRequest(MCRes, key_params=[{"alg": ES256.ALGORITHM}])

        with pytest.raises(CtapError) as e:
            device.sendMC(*req.toMC())

        assert e.value.code == CtapError.ERR.MISSING_PARAMETER
    def test_bad_type_user_displayName(self, device, MCRes):
        req = FidoRequest(
            MCRes, user={"id": "user_id", "name": "name", "displayName": 8}
        )

        with pytest.raises(CtapError) as e:
            device.sendMC(*req.toMC())
예제 #8
0
    def test_hmac_secret_entropy(self, device, MCHmacSecret, cipher,
                                 sharedSecret, salts):
        key_agreement, shared_secret = sharedSecret
        salt_enc, salt_auth = get_salt_params(cipher, shared_secret, salts)
        req = FidoRequest(extensions={
            "hmac-secret": {
                1: key_agreement,
                2: salt_enc,
                3: salt_auth
            }
        })
        auth = device.sendGA(*req.toGA())

        ext = auth.auth_data.extensions
        assert ext
        assert "hmac-secret" in ext
        assert isinstance(ext["hmac-secret"], bytes)
        assert len(ext["hmac-secret"]) == len(salts) * 32

        verify(MCHmacSecret, auth, req.cdh)

        dec = cipher.decryptor()
        key = dec.update(ext["hmac-secret"]) + dec.finalize()

        print(shannon_entropy(ext["hmac-secret"]))
        if len(salts) == 1:
            assert shannon_entropy(ext["hmac-secret"]) > 4.6
            assert shannon_entropy(key) > 4.6
        if len(salts) == 2:
            assert shannon_entropy(ext["hmac-secret"]) > 5.4
            assert shannon_entropy(key) > 5.4
예제 #9
0
    def test_seedweed_vectors_get_assertion(
        self,
        solo,
        device,
    ):
        import seedweed
        from binascii import hexlify
        version = b'\x01'

        for i, v in enumerate(seedweed.load_test_vectors(shortlist=True)):
            print(f'{i}) Enter user presence THREE times.')
            ext_key_cmd = 0x62
            solo.send_data_hid(ext_key_cmd, version + v['seed'] + b'')

            allow_list = [{"id": v['credential_id'], "type": "public-key"}]
            ga_req = FidoRequest(rp={
                "id": v['rp_id'],
                "name": "seedweed"
            },
                                 allow_list=allow_list)
            ga_res = device.sendGA(*ga_req.toGA())
            # print(v)
            # print(ga_res.auth_data + ga_req.cdh)

            # assert ga_res.auth_data.rp_id_hash == reg.auth_data.rp_id_hash
            assert ga_res.credential["id"] == v['credential_id']
            # reg.auth_data.credential_data.credential_id

            seedweed.conformance.verify_get_assertion(
                v,
                convert_der_sig_to_padded_binary(ga_res.signature),
                # ga_res.signature,
                ga_res.auth_data + ga_req.cdh,
            )
예제 #10
0
    def test_bad_type_exclude_list_type(self, device, MCRes, GARes):
        req = FidoRequest(MCRes, exclude_list=GARes.request.allow_list)

        with pytest.raises(CtapError) as e:
            device.sendMC(*req.toMC())

        assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
예제 #11
0
    def get_output(self, device, MCHmacSecret, cipher, sharedSecret, salts):
        key_agreement, shared_secret = sharedSecret
        salt_enc, salt_auth = get_salt_params(cipher, shared_secret, salts)
        req = FidoRequest(extensions={
            "hmac-secret": {
                1: key_agreement,
                2: salt_enc,
                3: salt_auth
            }
        })
        auth = device.sendGA(*req.toGA())

        ext = auth.auth_data.extensions
        assert ext
        assert "hmac-secret" in ext
        assert isinstance(ext["hmac-secret"], bytes)
        assert len(ext["hmac-secret"]) == len(salts) * 32

        verify(MCHmacSecret, auth, req.cdh)

        dec = cipher.decryptor()
        output = dec.update(ext["hmac-secret"]) + dec.finalize()

        if len(salts) == 2:
            return (output[0:32], output[32:64])
        else:
            return output
예제 #12
0
    def test_missing_pubKeyCredParams(self, device, MCRes):
        req = FidoRequest(MCRes, key_params=None)

        with pytest.raises(CtapError) as e:
            device.sendMC(*req.toMC())

        assert e.value.code == CtapError.ERR.MISSING_PARAMETER
예제 #13
0
def MCHmacSecret(
    resetDevice,
):
    req = FidoRequest(extensions={"hmac-secret": True}, options={"rk": True})
    res = resetDevice.sendMC(*req.toMC())
    setattr(res, "request", req)
    return res
예제 #14
0
    def test_exclude_list(self, device, MCRes):
        req = FidoRequest(MCRes,
                          exclude_list=[{
                              "id": b"1234",
                              "type": "rot13"
                          }])

        device.sendMC(*req.toMC())
예제 #15
0
    def test_bad_type_exclude_list_type(self, device, MCRes):
        req = FidoRequest(MCRes,
                          exclude_list=[{
                              "type": b"public-key",
                              "id": b"1234"
                          }])

        with pytest.raises(CtapError) as e:
            device.sendMC(*req.toMC())
예제 #16
0
    def test_bad_type_pubKeyCredParams_alg(self, device, MCRes):
        req = FidoRequest(MCRes,
                          key_params=[{
                              "alg": "7",
                              "type": "public-key"
                          }])

        with pytest.raises(CtapError) as e:
            device.sendMC(*req.toMC())
예제 #17
0
    def test_missing_keyAgreement(self, device, cipher, sharedSecret):
        key_agreement, shared_secret = sharedSecret

        salt_enc, salt_auth = get_salt_params(cipher, shared_secret, (salt3,))

        req = FidoRequest(extensions={"hmac-secret": {2: salt_enc, 3: salt_auth}})

        with pytest.raises(CtapError):
            device.sendGA(*req.toGA())
예제 #18
0
    def test_multiple_key_params_eddsa(self, device, MCRes):
        key_params = [
            {"type": "public-key", "alg": EdDSA.ALGORITHM},
            {"type": "public-key", "alg": ES256.ALGORITHM},
        ]

        req = FidoRequest(MCRes, key_params=key_params)
        resp = device.sendMC(*req.toMC())
        assert resp.auth_data.credential_data.public_key[3] == EdDSA.ALGORITHM
예제 #19
0
    def test_missing_saltEnc(self, device, cipher, sharedSecret):
        key_agreement, shared_secret = sharedSecret

        salt_enc, salt_auth = get_salt_params(cipher, shared_secret, (salt3,))

        req = FidoRequest(extensions={"hmac-secret": {1: key_agreement, 3: salt_auth}})

        with pytest.raises(CtapError) as e:
            device.sendGA(*req.toGA())
        assert e.value.code == CtapError.ERR.MISSING_PARAMETER
예제 #20
0
    def test_bad_type_rp_name(self, device, MCRes):
        req = FidoRequest(MCRes,
                          rp={
                              "id": "test.org",
                              "name": 8,
                              "icon": "icon"
                          })

        with pytest.raises(CtapError) as e:
            device.sendMC(*req.toMC())
예제 #21
0
    def test_missing_pubKeyCredParams_alg(self, device, MCRes):
        req = FidoRequest(MCRes, key_params=[{"type": "public-key"}])

        with pytest.raises(CtapError) as e:
            device.sendMC(*req.toMC())

        assert e.value.code in [
            CtapError.ERR.MISSING_PARAMETER,
            CtapError.ERR.UNSUPPORTED_ALGORITHM,
        ]
예제 #22
0
    def test_u2f(self, device, iterations):
        lastc = 0
        chal = FidoRequest().challenge
        appid = FidoRequest().appid

        regs = []

        for i in range(0, iterations):
            print("U2F reg + auth %d/%d (count: %02x)" %
                  (i + 1, iterations, lastc))
            reg = device.register(chal, appid)
            reg.verify(appid, chal)
            auth = device.authenticate(chal, appid, reg.key_handle)
            auth.verify(appid, chal, reg.public_key)

            regs.append(reg)
            # check endianness
            if lastc:
                assert (auth.counter - lastc) < 10
            lastc = auth.counter
            if lastc > 0x80000000:
                print("WARNING: counter is unusually high: %04x" % lastc)
                assert 0

        for i in range(0, iterations):
            auth = device.authenticate(chal, appid, regs[i].key_handle)
            auth.verify(appid, chal, regs[i].public_key)

        device.reboot()

        for i in range(0, iterations):
            auth = device.authenticate(chal, appid, regs[i].key_handle)
            auth.verify(appid, chal, regs[i].public_key)

        print("Check that all previous credentials are registered...")
        for i in range(0, iterations):
            with pytest.raises(ApduError) as e:
                auth = device.ctap1.authenticate(chal,
                                                 appid,
                                                 regs[i].key_handle,
                                                 check_only=True)
            assert e.value.code == APDU.USE_NOT_SATISFIED
예제 #23
0
    def test_hmac_secret_and_credProtect_make_credential(
            self, resetDevice, MCCredProtect):

        req = FidoRequest(extensions={"credProtect": 1, "hmac-secret": True})
        res = resetDevice.sendMC(*req.toMC())
        setattr(res, "request", req)

        for ext in ["credProtect", "hmac-secret"]:
            assert res.auth_data.extensions
            assert ext in res.auth_data.extensions
            assert res.auth_data.extensions[ext] == True
예제 #24
0
    def test_invalid_salt_length(self, device, cipher, sharedSecret, salts):
        key_agreement, shared_secret = sharedSecret
        salt_enc, salt_auth = get_salt_params(cipher, shared_secret, salts)

        req = FidoRequest(
            extensions={"hmac-secret": {1: key_agreement, 2: salt_enc, 3: salt_auth}}
        )

        with pytest.raises(CtapError) as e:
            device.sendGA(*req.toGA())
        assert e.value.code == CtapError.ERR.INVALID_LENGTH
예제 #25
0
    def test_ctap1_authenticate(self, MCRes, device):
        req = FidoRequest()
        key_handle = MCRes.auth_data.credential_data.credential_id
        res = device.authenticate(req.challenge, req.appid, key_handle)

        credential_data = AttestedCredentialData(
            MCRes.auth_data.credential_data)
        pubkey_string = b'\x04' + credential_data.public_key[
            -2] + credential_data.public_key[-3]

        res.verify(req.appid, req.challenge, pubkey_string)
예제 #26
0
    def test_authenticate_ctap1_through_ctap2(self, device, RegRes):
        req = FidoRequest(allow_list=[{
            "id": RegRes.key_handle,
            "type": "public-key"
        }])

        auth = device.sendGA(*req.toGA())

        credential_data = AttestedCredentialData.from_ctap1(
            RegRes.key_handle, RegRes.public_key)
        auth.verify(req.cdh, credential_data.public_key)
        assert auth.credential["id"] == RegRes.key_handle
예제 #27
0
    def test_load_external_key_invalidate_old_cred(self, solo, device, MCRes,
                                                   GARes):
        ext_key_cmd = 0x62
        verify(MCRes, GARes)
        print('Enter user presence THREE times.')
        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:
            ga_bad_req = FidoRequest(GARes)
            device.sendGA(*ga_bad_req.toGA())
        assert (e.value.code == CtapError.ERR.NO_CREDENTIALS)
예제 #28
0
    def test_load_external_key(
        self,
        solo,
        device,
    ):

        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, version + key_A + ext_state)

        # New credential works.
        mc_A_req = FidoRequest()
        mc_A_res = device.sendMC(*mc_A_req.toMC())

        allow_list = [{
            "id": mc_A_res.auth_data.credential_data.credential_id,
            "type": "public-key"
        }]
        ga_A_req = FidoRequest(mc_A_req, allow_list=allow_list)
        ga_A_res = device.sendGA(*FidoRequest(ga_A_req).toGA())

        verify(mc_A_res, ga_A_res, ga_A_req.cdh)

        # Load up Key B and verify cred A doesn't exist.
        print('Enter user presence THREE times.')
        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, 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)
예제 #29
0
    def test_eddsa(self, device):
        mc_req = FidoRequest(key_params=[{
            "type": "public-key",
            "alg": EdDSA.ALGORITHM
        }])
        try:
            mc_res = device.sendMC(*mc_req.toMC())
        except CtapError as e:
            if e.code == CtapError.ERR.UNSUPPORTED_ALGORITHM:
                print("ed25519 is not supported.  Skip this test.")
                return

        setattr(mc_res, "request", mc_req)

        allow_list = [{
            "id": mc_res.auth_data.credential_data.credential_id[:],
            "type": "public-key",
        }]

        ga_req = FidoRequest(allow_list=allow_list)
        ga_res = device.sendGA(*ga_req.toGA())
        setattr(ga_res, "request", ga_req)

        try:
            verify(mc_res, ga_res)
        except:
            # Print out extra details on failure
            from binascii import hexlify

            print("authdata", hexlify(ga_res.auth_data))
            print("cdh", hexlify(ga_res.request.cdh))
            print("sig", hexlify(ga_res.signature))
            from fido2.ctap2 import AttestedCredentialData

            credential_data = AttestedCredentialData(
                mc_res.auth_data.credential_data)
            print("public key:", hexlify(credential_data.public_key[-2]))
            verify(mc_res, ga_res)
예제 #30
0
    def test_ctap1_authenticate(self, MCRes, device):
        req = FidoRequest()
        key_handle = MCRes.auth_data.credential_data.credential_id
        if len(key_handle) <= 255:
            res = device.authenticate(req.challenge, req.appid, key_handle)

            credential_data = AttestedCredentialData(
                MCRes.auth_data.credential_data)
            pubkey_string = b'\x04' + credential_data.public_key[
                -2] + credential_data.public_key[-3]

            res.verify(req.appid, req.challenge, pubkey_string)
        else:
            print(
                "ctap2 credId is longer than 255 bytes, cannot use with U2F.")