示例#1
0
    def test_send_cbor_ok(self):
        ctap = CTAP2(mock.MagicMock())
        ctap.device.call.return_value = b'\0' + cbor.dumps({1: b'response'})

        self.assertEqual({1: b'response'}, ctap.send_cbor(2, b'foobar'))
        ctap.device.call.assert_called_with(0x10,
                                            b'\2' + cbor.dumps(b'foobar'),
                                            None, None)
示例#2
0
def authenticate_begin():
    if not credentials:
        abort(404)

    auth_data, state = server.authenticate_begin(credentials)
    session['state'] = state
    return cbor.dumps(auth_data)
def generate_get_info_response_message():
    _AAGUID = a2b_hex('F8A011F38C0A4D15800617111F9EDC7D')
    # f8a011f38c0a4d15800617111f9edc7d
    _INFO = a2b_hex(
        'a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101'
    )  # noqa
    # VERSIONS = 1
    # EXTENSIONS = 2
    # AAGUID = 3
    # OPTIONS = 4
    # MAX_MSG_SIZE = 5
    # PIN_PROTOCOLS = 6
    info_data = {
        1: ['U2F_V2', 'FIDO_2_0'],
        2: ['uvm', 'hmac-secret'],
        3: _AAGUID,
        4: {
            'clientPin': False,
            'plat': False,
            'rk': True,
            'up': True
        },
        5: 1200,
        6: [1]
    }

    ret = b'\0' + cbor.dumps(info_data)
    #ret = b'\0' + _INFO
    return SW_NO_ERROR, ret
示例#4
0
def registration_begin(user, authenticator):
    user_webauthn_tokens = user.credentials.filter(FidoCredential)
    if user_webauthn_tokens.count >= current_app.config['WEBAUTHN_MAX_ALLOWED_TOKENS']:
        current_app.logger.error('User tried to register more than {} tokens.'.format(
            current_app.config['WEBAUTHN_MAX_ALLOWED_TOKENS']))
        return {'_status': 'error', 'message': 'security.webauthn.max_allowed_tokens'}
    creds = make_credentials(user_webauthn_tokens.to_list())
    server = get_webauthn_server(current_app.config['FIDO2_RP_ID'])
    if user.given_name is None or user.surname is None or user.display_name is None:
        return {'_status': 'error', 'message': 'security.webauthn-missing-pdata'}
    registration_data, state = server.register_begin(
            {
                'id': str(user.eppn).encode('ascii'),
                'name': "{} {}".format(user.given_name, user.surname),
                'displayName': user.display_name
            }, credentials=creds,
            user_verification=USER_VERIFICATION.DISCOURAGED,
            authenticator_attachment=authenticator,
            )
    session['_webauthn_state_'] = state

    current_app.logger.info('User {} has started registration of a webauthn token'.format(user))
    current_app.logger.debug('Webauthn Registration data: {}.'.format(registration_data))
    current_app.stats.count(name='webauthn_register_begin')

    encoded_data = base64.urlsafe_b64encode(cbor.dumps(registration_data)).decode('ascii')
    encoded_data = encoded_data.rstrip('=')
    return {
        'csrf_token': session.new_csrf_token(),
        'registration_data': encoded_data
    }
示例#5
0
def authenticate_begin():
    if not credentials:
        abort(404)

    auth_data = server.authenticate_begin(credentials)
    session['challenge'] = auth_data['publicKey']['challenge']
    return cbor.dumps(auth_data)
示例#6
0
def authenticate_begin(request):
    server = getServer()
    credentials = getUserCredentials(
        request.session.get("base_username", request.user.username))
    auth_data, state = server.authenticate_begin(credentials)
    request.session['fido_state'] = state
    return HttpResponse(cbor.dumps(auth_data),
                        content_type="application/octet-stream")
示例#7
0
def begin_registeration(request):
    server = getServer()
    registration_data, state = server.register_begin({
        u'id': request.user.username.encode("utf8"),
        u'name': (request.user.first_name + " " + request.user.last_name),
        u'displayName': request.user.username,
    }, getUserCredentials(request.user.username))
    request.session['fido_state'] = state

    return HttpResponse(cbor.dumps(registration_data))
示例#8
0
def authenticate_complete():
    data = cbor.loads(request.get_data())[0]
    credential_id = data['credentialId']
    client_data = ClientData(data['clientDataJSON'])
    auth_data = AuthenticatorData(data['authenticatorData'])
    signature = data['signature']

    credentials = session['userobj']['credentials']
    fidoserver.authenticate_complete(credentials, credential_id,
                                 session.pop('challenge'), client_data,
                                 auth_data, signature)
    return cbor.dumps({'status': 'OK'})
示例#9
0
def register_begin():

    userobj = session['userobj']
    userreq = {
        'id': userobj['userid'],
        'name': userobj['username'],
        'displayName': userobj['username']
    }
    credentials = userobj['credentials']
    registration_data = fidoserver.register_begin(rp, userreq, credentials)
    session['challenge'] = registration_data['publicKey']['challenge']
    return cbor.dumps(registration_data)
示例#10
0
def register_begin():
    registration_data, state = server.register_begin({
        'id': b'user_id',
        'name': 'a_user',
        'displayName': 'A. User',
        'icon': 'https://example.com/image.png'
    }, credentials)

    session['state'] = state
    print('\n\n\n\n')
    print(registration_data)
    print('\n\n\n\n')
    return cbor.dumps(registration_data)
示例#11
0
def register_complete():
    data = cbor.loads(request.get_data())[0]
    client_data = ClientData(data['clientDataJSON'])
    att_obj = AttestationObject(data['attestationObject'])
    print('clientData', client_data)
    print('AttestationObject:', att_obj)

    auth_data = server.register_complete(session['state'], client_data,
                                         att_obj)

    credentials.append(auth_data.credential_data)
    print('REGISTERED CREDENTIAL:', auth_data.credential_data)
    return cbor.dumps({'status': 'OK'})
示例#12
0
def register_begin():
    registration_data = server.register_begin(
        {
            'id': b'user_id',
            'name': 'a_user',
            'displayName': 'A. User',
            'icon': 'https://example.com/image.png'
        }, credentials)

    session['challenge'] = registration_data['publicKey']['challenge']
    print('\n\n\n\n')
    print(registration_data)
    print('\n\n\n\n')
    return cbor.dumps(registration_data)
示例#13
0
def u2f_complete():
    data = cbor.loads(request.get_data())[0]
    client_data = ClientData.from_b64(data['clientData'])
    reg_data = RegistrationData.from_b64(data['registrationData'])
    print('clientData', client_data)
    print('U2F RegistrationData:', reg_data)
    att_obj = AttestationObject.from_ctap1(sha256(b'https://localhost:5000'),
                                           reg_data)
    print('AttestationObject:', att_obj)

    auth_data = att_obj.auth_data

    credentials.append(auth_data.credential_data)
    print('REGISTERED U2F CREDENTIAL:', auth_data.credential_data)
    return cbor.dumps({'status': 'OK'})
示例#14
0
def authenticate_complete():
    if not credentials:
        abort(404)

    data = cbor.loads(request.get_data())[0]
    credential_id = data['credentialId']
    client_data = ClientData(data['clientDataJSON'])
    auth_data = AuthenticatorData(data['authenticatorData'])
    signature = data['signature']
    print('clientData', client_data)
    print('AuthenticatorData', auth_data)

    server.authenticate_complete(session.pop('state'), credentials,
                                 credential_id, client_data, auth_data,
                                 signature)
    print('ASSERTION OK')
    return cbor.dumps({'status': 'OK'})
示例#15
0
    def test_get_assertion(self):
        ctap = CTAP2(mock.MagicMock())
        ctap.device.call.return_value = b'\0' + _GA_RESP

        resp = ctap.get_assertion(1, 2)
        ctap.device.call.assert_called_with(0x10, b'\2' + cbor.dumps({
            1: 1,
            2: 2
        }), None, None)

        self.assertIsInstance(resp, AssertionResponse)
        self.assertEqual(resp, _GA_RESP)
        self.assertEqual(resp.credential, _CRED)
        self.assertEqual(resp.auth_data, _AUTH_DATA_GA)
        self.assertEqual(resp.signature, _SIGNATURE)
        self.assertIsNone(resp.user)
        self.assertIsNone(resp.number_of_credentials)
示例#16
0
def register_complete():

    data = cbor.loads(request.get_data())[0]
    client_data = ClientData(data['clientDataJSON'])
    att_obj = AttestationObject(data['attestationObject'])


    val = session['challenge']
    auth_data = fidoserver.register_complete(val, client_data, att_obj)

    # Check if key has already been registered

    certificateid = att_obj.auth_data.credential_data.credential_id

    fileid = session['userobj']['userid'] + "_" + b2a_hex(certificateid).decode()

    pickle.dump(att_obj.auth_data.credential_data, open("./users/" + fileid + ".p", "wb"))

    return cbor.dumps({'status': 'OK'})
示例#17
0
def store_credential(cursor, user, mode, credential):
    """
    Store credential in database
    :param credential: The credential to store
    :param user: The user the credential belongs to
    :param cursor: Database cursor
    :param mode: The FIDO2 mode
    :return:
    """
    user_id = user['id']
    sql_command = """
    INSERT INTO credentials(credential_id, aaguid, public_key, user_id, mode)
    VALUES (?, ?, ?, ?, ?);
    """
    data = (memoryview(credential.credential_id), memoryview(
        credential.aaguid), memoryview(dumps(credential.public_key)),
            memoryview(user_id), int(mode))
    cursor.execute(sql_command, data)
    print("New credential stored")
示例#18
0
    def test_make_credential(self):
        ctap = CTAP2(mock.MagicMock())
        ctap.device.call.return_value = b'\0' + _MC_RESP

        resp = ctap.make_credential(1, 2, 3, 4)
        ctap.device.call.assert_called_with(
            0x10, b'\1' + cbor.dumps({
                1: 1,
                2: 2,
                3: 3,
                4: 4
            }), None, None)

        self.assertIsInstance(resp, AttestationObject)
        self.assertEqual(resp, _MC_RESP)
        self.assertEqual(resp.fmt, 'packed')
        self.assertEqual(resp.auth_data, _AUTH_DATA_MC)
        self.assertSetEqual(set(resp.att_statement.keys()),
                            {'alg', 'sig', 'x5c'})
示例#19
0
def autenticate_begin():

    credentials = session['userobj']['credentials']
    auth_data = fidoserver.authenticate_begin(rp['id'], credentials)
    session['challenge'] = auth_data['publicKey']['challenge']
    return cbor.dumps(auth_data)
示例#20
0
    def get_config_for_bundle(self, action):
        if action.old_format:
            userid = action.user_id
            user = current_app.central_userdb.get_user_by_id(
                userid, raise_on_missing=False)
        else:
            eppn = action.eppn
            user = current_app.central_userdb.get_user_by_eppn(
                eppn, raise_on_missing=False)
        current_app.logger.debug('Loaded User {} from db'.format(user))
        if not user:
            raise self.ActionError('mfa.user-not-found')

        credentials = _get_user_credentials(user)
        current_app.logger.debug('FIDO credentials for user {}:\n{}'.format(
            user, pprint.pformat(credentials)))

        # CTAP1/U2F
        # TODO: Only make U2F challenges for U2F tokens?
        challenge = None
        if current_app.config.get('GENERATE_U2F_CHALLENGES') is True:
            u2f_tokens = [v['u2f'] for v in credentials.values()]
            try:
                challenge = begin_authentication(
                    current_app.config['U2F_APP_ID'], u2f_tokens)
                current_app.logger.debug('U2F challenge:\n{}'.format(
                    pprint.pformat(challenge)))
            except ValueError:
                # there is no U2F key registered for this user
                pass

        # CTAP2/Webauthn
        # TODO: Only make Webauthn challenges for Webauthn tokens?
        webauthn_credentials = [v['webauthn'] for v in credentials.values()]
        fido2rp = RelyingParty(current_app.config['FIDO2_RP_ID'], 'eduID')
        fido2server = _get_fido2server(credentials, fido2rp)
        raw_fido2data, fido2state = fido2server.authenticate_begin(
            webauthn_credentials)
        current_app.logger.debug('FIDO2 authentication data:\n{}'.format(
            pprint.pformat(raw_fido2data)))
        fido2data = base64.urlsafe_b64encode(
            cbor.dumps(raw_fido2data)).decode('ascii')
        fido2data = fido2data.rstrip('=')

        config = {'u2fdata': '{}', 'webauthn_options': fido2data}

        # Save the challenge to be used when validating the signature in perform_action() below
        if challenge is not None:
            session[self.PACKAGE_NAME + '.u2f.challenge'] = challenge.json
            config['u2fdata'] = json.dumps(challenge.data_for_client)
            current_app.logger.debug(
                f'FIDO1/U2F challenge for user {user}: {challenge.data_for_client}'
            )

        current_app.logger.debug(
            f'FIDO2/Webauthn state for user {user}: {fido2state}')
        session[self.PACKAGE_NAME + '.webauthn.state'] = json.dumps(fido2state)

        # Explicit check for boolean True
        if current_app.config.get('MFA_TESTING') is True:
            current_app.logger.info('MFA test mode is enabled')
            config['testing'] = True
        else:
            config['testing'] = False

        # Add config for external mfa auth
        config['eidas_url'] = current_app.config['EIDAS_URL']
        config['mfa_authn_idp'] = current_app.config['MFA_AUTHN_IDP']

        return config
def generate_registration_response_message(cbor_data):
    print('''
%s %s

Got an event from some relying party!

A website is asking you to register the authenticator,
and it is claiming itself to be APPID with SHA256(APPID) =
%s''' % (sys.argv[0], V2F_DIR, cbor_data))
    if not user_says_yes('Enter yes to register'):
        return SW_CONDITIONS_NOT_SATISFIED, b''

    print()
    print('Please return to the web page in 3 seconds to avoid timeout!')
    print()
    time.sleep(3)

    _AAGUID = b'f8a011f38c0a4d15800617111f9edc7d'
    _CRED_ID = b'fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783'  # noqa

    # private_key = ROMAN_KEY
    public_key = ROMAN_KEY.public_key()

    # print(signature)
    # public_key.verify(signature, data2, ec.ECDSA(hashes.SHA256()))

    cose_public_key = ES256.from_cryptography_key(public_key)
    cose_public_key_hex = b2a_hex(cbor.dumps(cose_public_key))
    # cose_public_key.verify(data2, signature)

    attest_cred_data = b''.join([
        _AAGUID,
        #b'F8A011F38C0A4D15800617111F9EDC7D', #AAGUID
        b'0040',  # CRED_ID length
        _CRED_ID,
        #b'fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783',
        cose_public_key_hex,
        #cbor2hex(pk_cose).encode(), #Credentail PubK CBORtoHEX and encoded to bytes
        # b'a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290'
    ])
    # print(cbor_data[0].get(2))
    RP_ID = cbor_data[0].get(2).get('id')

    RP_ID_hash = hashlib.sha256(str.encode(RP_ID)).digest()
    # print(b2a_hex(RP_ID_hash))

    auth_data = b''.join([
        b2a_hex(RP_ID_hash),  #RP ID HASH
        #b'0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12'  # RP ID HASH
        b'41',  # FLAG
        b'00000003',  # counter
        attest_cred_data
        #b'f8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290'
    ])

    #ATT_STMT

    #SIGNATURE
    #find clientDataHash from request
    clientDataHash = cbor_data[0].get(1)

    # data_to_sign = auth_data + client_data_hash
    # data_to_sign = b'0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000002C' + b'7B89F12A9088B0F5EE0EF8F6718BCCC374249C31AEEBAEB79BD0450132CD536C'
    data_to_sign = a2b_hex(auth_data) + clientDataHash
    attStmt_signature_ = ROMAN_KEY.sign(data_to_sign,
                                        ec.ECDSA(hashes.SHA256()))
    # print("***678***")
    # print(b2a_hex(signature))
    # print(b2a_hex(data_to_sign))

    #CERTIFICATE x509
    #created on init and saved in global var - see above

    attStmt_certificate = ROMAN_CERT

    statement2 = {
        'alg': -7,
        # 'sig': a2b_hex(b'304502200D15DAF337D727AB4719B4027114A2AC43CD565D394CED62C3D9D1D90825F0B3022100989615E7394C87F4AD91F8FDAE86F7A3326DF332B3633DB088AAC76BFFB9A46B'),
        'sig': attStmt_signature_,
        'x5c': [attStmt_certificate.public_bytes(serialization.Encoding.DER)]
        # 'x5c': [a2b_hex(
        #     b'308202B73082019FA00302010202041D31330D300D06092A864886F70D01010B0500302A3128302606035504030C1F59756269636F2050726576696577204649444F204174746573746174696F6E301E170D3138303332383036333932345A170D3139303332383036333932345A306E310B300906035504061302534531123010060355040A0C0959756269636F20414231223020060355040B0C1941757468656E74696361746F72204174746573746174696F6E3127302506035504030C1E59756269636F205532462045452053657269616C203438393736333539373059301306072A8648CE3D020106082A8648CE3D030107034200047D71E8367CAFD0EA6CF0D61E4C6A416BA5BB6D8FAD52DB2389AD07969F0F463BFDDDDDC29D39D3199163EE49575A3336C04B3309D607F6160C81E023373E0197A36C306A302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C0201010404030204303021060B2B0601040182E51C01010404120410F8A011F38C0A4D15800617111F9EDC7D300C0603551D130101FF04023000300D06092A864886F70D01010B050003820101009B904CEADBE1F1985486FEAD02BAEAA77E5AB4E6E52B7E6A2666A4DC06E241578169193B63DADEC5B2B78605A128B2E03F7FE2A98EAEB4219F52220995F400CE15D630CF0598BA662D7162459F1AD1FC623067376D4E4091BE65AC1A33D8561B9996C0529EC1816D1710786384D5E8783AA1F7474CB99FE8F5A63A79FF454380361C299D67CB5CC7C79F0D8C09F8849B0500F6D625408C77CBBC26DDEE11CB581BEB7947137AD4F05AAF38BD98DA10042DDCAC277604A395A5B3EAA88A5C8BB27AB59C8127D59D6BBBA5F11506BF7B75FDA7561A0837C46F025FD54DCF1014FC8D17C859507AC57D4B1DEA99485DF0BA8F34D00103C3EEF2EF3BBFEC7A6613DE')]
        # # noqa
    }

    attest_obj = {1: 'packed', 2: a2b_hex(auth_data), 3: statement2}

    att_obj = cbor2hex(attest_obj)

    return SW_NO_ERROR, b'\0' + a2b_hex(att_obj)
示例#22
0
def cbor2hex(data):
    return b2a_hex(cbor.dumps(data)).decode()
示例#23
0
def args(*args):
    """
    Constructs a dict from a list of arguments for sending a CBOR command.
    """
    if args:
        return dict((i, v) for i, v in enumerate(args, 1) if v is not None)
    return None

def _parse_cbor(data):
    resp, rest = cbor.loads(data)
    if rest:
        raise ValueError('Extraneous data')
    return resp

print(cbor.dumps(a2b_hex(sent)))

def dict_values(d, indent=0):
    for key, value in d.items():
        print((value), indent+1)

def cbor2hex(data):
    return b2a_hex(cbor.dumps(data)).decode()

def hex2cbor(data):
    return cbor.loads(a2b_hex(data))

cbor1 = {'3': 0,
    b'2': 0,
    1: 0
    }
示例#24
0
key = CoseKey.parse(cbor.loads(_ES256_KEY)[0])
key.verify(
    a2b_hex(
        b'0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000002C'
        +  # noqa
        b'7B89F12A9088B0F5EE0EF8F6718BCCC374249C31AEEBAEB79BD0450132CD536C'
    ),  # noqa
    a2b_hex(
        b'304402202B3933FE954A2D29DE691901EB732535393D4859AAA80D58B08741598109516D0220236FBE6B52326C0A6B1CFDC6BF0A35BDA92A6C2E41E40C3A1643428D820941E0'
    )  # noqa
)
print()
print(
    b'a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290'
)
print(b2a_hex(cbor.dumps(cose_public_key)))
cos_key = {
    1:
    2,
    3:
    -7,
    -1:
    1,
    -2:
    a2b_hex(b'A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1'
            ),  # noqa
    -3:
    a2b_hex(b'FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C'
            )  # noqa
}
print(cose_public_key)
示例#25
0
 def render(self, data, media_type=None, renderer_context=None):
     return cbor.dumps(data)
示例#26
0
    return b2a_hex(cbor.dumps(data)).decode()


_AAGUID = a2b_hex('F8A011F38C0A4D15800617111F9EDC7D')
_INFO = a2b_hex(
    'a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101'
)  # noqa
ret1 = "ret1: " + b'\0' + _INFO + " END"
print(ret1)
info_data = {
    'versions': ['U2F_V2', 'FIDO_2_0'],
    'extensions': ['uvm', 'hmac-secret'],
    'aaguid': _AAGUID,
    'options': {
        'clientPin': False,
        'plat': False,
        'rk': True,
        'up': True
    },
    'max_msg_size': 1200,
    'pin_protocols': [1]
}
# print(cbor.dumps(info_data))
ret2 = "ret2: " + b'\0' + cbor.dumps(info_data) + " END"
# ret = b'\0' + _INFO
print(ret2)
# # Serialize an object as a bytestring
# data = dumps(['hello', 'world'])
# #
# # Deserialize a bytestring
# obj = loads(data)
示例#27
0
文件: mfa.py 项目: SUNET/eduid-webapp
    def get_config_for_bundle(self, action):
        if action.old_format:
            userid = action.user_id
            user = current_app.central_userdb.get_user_by_id(
                userid, raise_on_missing=False)
        else:
            eppn = action.eppn
            user = current_app.central_userdb.get_user_by_eppn(
                eppn, raise_on_missing=False)
        current_app.logger.debug('Loaded User {} from db'.format(user))
        if not user:
            raise self.ActionError('mfa.user-not-found')

        credentials = _get_user_credentials(user)
        current_app.logger.debug('FIDO credentials for user {}:\n{}'.format(user, pprint.pformat(credentials)))

        # CTAP1/U2F
        # TODO: Only make U2F challenges for U2F tokens?
        challenge = None
        if current_app.config.get('GENERATE_U2F_CHALLENGES') is True:
            u2f_tokens = [v['u2f'] for v in credentials.values()]
            try:
                challenge = begin_authentication(current_app.config['U2F_APP_ID'], u2f_tokens)
                current_app.logger.debug('U2F challenge:\n{}'.format(pprint.pformat(challenge)))
            except ValueError:
                # there is no U2F key registered for this user
                pass

        # CTAP2/Webauthn
        # TODO: Only make Webauthn challenges for Webauthn tokens?
        webauthn_credentials = [v['webauthn'] for v in credentials.values()]
        fido2rp = RelyingParty(current_app.config['FIDO2_RP_ID'], 'eduID')
        fido2server = _get_fido2server(credentials, fido2rp)
        raw_fido2data, fido2state = fido2server.authenticate_begin(webauthn_credentials)
        current_app.logger.debug('FIDO2 authentication data:\n{}'.format(pprint.pformat(raw_fido2data)))
        fido2data = base64.urlsafe_b64encode(cbor.dumps(raw_fido2data)).decode('ascii')
        fido2data = fido2data.rstrip('=')

        config = {'u2fdata': '{}', 'webauthn_options': fido2data}

        # Save the challenge to be used when validating the signature in perform_action() below
        if challenge is not None:
            session[self.PACKAGE_NAME + '.u2f.challenge'] = challenge.json
            config['u2fdata'] = json.dumps(challenge.data_for_client)
            current_app.logger.debug(f'FIDO1/U2F challenge for user {user}: {challenge.data_for_client}')

        current_app.logger.debug(f'FIDO2/Webauthn state for user {user}: {fido2state}')
        session[self.PACKAGE_NAME + '.webauthn.state'] = json.dumps(fido2state)

        # Explicit check for boolean True
        if current_app.config.get('MFA_TESTING') is True:
            current_app.logger.info('MFA test mode is enabled')
            config['testing'] = True
        else:
            config['testing'] = False

        # Add config for external mfa auth
        config['eidas_url'] = current_app.config['EIDAS_URL']
        config['mfa_authn_idp'] = current_app.config['MFA_AUTHN_IDP']

        return config