微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

DUO-LABS WebAuthn:服务器验证凭据失败:注册失败错误:注册被拒绝错误:无法验证来源

如何解决DUO-LABS WebAuthn:服务器验证凭据失败:注册失败错误:注册被拒绝错误:无法验证来源

我尝试使用 DUO-lab 的 Python 的 webauthn 包实现基于指纹的身份验证。但是我遇到了这个错误

server validation of credential Failed: registration Failed. error: registration rejected. error: unable to verify origin..

当我检查包的源代码时,我注意到这个错误 unable to verify origin.. 可能是由于您的身份验证器配置不正确而引发的。

有没有一种方法可以明确说明我只需要 platform 身份验证器而不是 roaming 身份验证器,而无需使用包的源代码?如果有,请包含 Flask 的完整工作代码(这是我在错误将我赶出 Django 之后现在使用的代码)。我目前的配置是:

RP_ID = 'nacesdecide.herokuapp.com' #The app is currently hosted on heroku
RP_NAME = 'nacesdecides nacesdecide'
ORIGIN = 'https://nacesdecide.herokuapp.com/'

该应用程序目前在 heroku 上,可以通过 naces register 实时访问。我希望应用程序单独使用 platform authenticators

更新:

客户端代码的某些部分(从 duo-lab's python webauthn flask demon js 起草,是:

/**
 * REGISTRATION FUNCTIONS
 */

/**
 * Callback after the registration form is submitted.
 * @param {Event} e
 */
const didClickRegister = async (e) => {
  e.preventDefault();

  // gather the data in the form
  const form = document.querySelector("#register-form");
  const formData = new FormData(form);

  // post the data to the server to generate the PublicKeyCredentialCreateOptions
  let credentialCreateOptionsFromServer;
  try {
    credentialCreateOptionsFromServer = await getCredentialCreateOptionsFromServer(
      formData
    );
  } catch (err) {
    showErrorAlert(`Failed to generate credential request options: ${err}`);
    return console.error("Failed to generate credential request options:",err);
  }

  // convert certain members of the PublicKeyCredentialCreateOptions into
  // byte arrays as expected by the spec.
  const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(
    credentialCreateOptionsFromServer
  );

  // request the authenticator(s) to create a new credential keypair.
  let credential;
  *try {
    credential = await navigator.credentials.create({
      publicKey: publicKeyCredentialCreateOptions,});*
  } catch (err) {
    showErrorAlert(`Error creating credential: ${err}`);
    return console.error("Error creating credential:",err);
  }

  // we Now have a new credential! We Now need to encode the byte arrays
  // in the credential into strings,for posting to our server.
  const newAssertionForServer = transformNewAssertionForServer(credential);

  // post the transformed credential data to the server for validation
  // and storing the public key
  let assertionValidationResponse;
  try {
    assertionValidationResponse = await postNewAssertionToServer(
      newAssertionForServer
    );
  } catch (err) {
    showErrorAlert(`Server validation of credential Failed: ${err}`);
    return console.error("Server validation of credential Failed:",err);
  }

  // reload the page after a successful result
  setTimeout(function () {
    window.location.href = Flask.url_for("accounts.login");
  },1000);
  //   window.location.reload();
};

在服务器端,我们有:

def webauthn_begin_activate():
    # MakeCredentialOptions
    username = request.form.get('register_username')
    display_name = request.form.get('register_display_name')

    if not util.validate_username(username):
        return make_response(jsonify({'fail': 'Invalid username.'}),401)
    if not util.validate_display_name(display_name):
        return make_response(jsonify({'fail': 'Invalid display name.'}),401)

    if User.query.filter_by(username=username).first():
        return make_response(jsonify({'fail': 'User already exists.'}),401)

    #clear session variables prior to starting a new registration
    session.pop('register_ukey',None)
    session.pop('register_username',None)
    session.pop('register_display_name',None)
    session.pop('challenge',None)

    session['register_username'] = username
    session['register_display_name'] = display_name

    challenge = util.generate_challenge(32)
    ukey = util.generate_ukey()

    # We strip the saved challenge of padding,so that we can do a byte
    # comparison on the URL-safe-without-padding challenge we get back
    # from the browser.
    # We will still pass the padded version down to the browser so that the JS
    # can decode the challenge into binary without too much trouble.
    session['challenge'] = challenge.rstrip('=')
    session['register_ukey'] = ukey

    *make_credential_options = webauthn.WebAuthnMakeCredentialOptions(
        challenge,RP_NAME,RP_ID,ukey,username,display_name,'https://example.com')*

    return jsonify(make_credential_options.registration_dict)

这个函数可能也很有趣:

def verify_credential_info():
    challenge = session['challenge']
    username = session['register_username']
    display_name = session['register_display_name']
    ukey = session['register_ukey']

    registration_response = request.form
    trust_anchor_dir = os.path.join(
        os.path.dirname(os.path.abspath(__file__)),TRUST_ANCHOR_DIR)
    trusted_attestation_cert_required = True
    self_attestation_permitted = True
    none_attestation_permitted = True

    webauthn_registration_response = webauthn.WebAuthnRegistrationResponse(
        RP_ID,ORIGIN,registration_response,challenge,trust_anchor_dir,trusted_attestation_cert_required,self_attestation_permitted,none_attestation_permitted,uv_required=False)  # User Verification

    try:
        webauthn_credential = webauthn_registration_response.verify()
    except Exception as e:
        return jsonify({'fail': 'Registration Failed. Error: {}'.format(e)})

    # Step 17.
    #
    # Check that the credentialId is not yet registered to any other user.
    # If registration is requested for a credential that is already registered
    # to a different user,the Relying Party SHOULD fail this registration
    # ceremony,or it MAY decide to accept the registration,e.g. while deleting
    # the older registration.
    credential_id_exists = User.query.filter_by(
        credential_id=webauthn_credential.credential_id).first()
    if credential_id_exists:
        return make_response(
            jsonify({
                'fail': 'Credential ID already exists.'
            }),401)

    existing_user = User.query.filter_by(username=username).first()
    if not existing_user:
        if sys.version_info >= (3,0):
            webauthn_credential.credential_id = str(
                webauthn_credential.credential_id,"utf-8")
            webauthn_credential.public_key = str(
                webauthn_credential.public_key,"utf-8")
        user = User(
            ukey=ukey,username=username,display_name=display_name,pub_key=webauthn_credential.public_key,credential_id=webauthn_credential.credential_id,sign_count=webauthn_credential.sign_count,rp_id=RP_ID,icon_url='https://example.com')
        db.session.add(user)
        db.session.commit()
    else:
        return make_response(jsonify({'fail': 'User already exists.'}),401)

    flash('Successfully registered as {}.'.format(username))

    return jsonify({'success': 'User successfully registered.'})

第二次更新:下面是我得到的完整日志:

webauthn.js:101 
{id: "ATdDPQneoYF3tA6HYW8_dr2eBDy53VNoEIHRWUDfnmT2URKIs0SQ_lQ7BujdmcfM9Hc2xNH8bvLf4k3lQJ-7RX4",rawId: "ATdDPQneoYF3tA6HYW8_dr2eBDy53VNoEIHRWUDfnmT2URKIs0SQ_lQ7BujdmcfM9Hc2xNH8bvLf4k3lQJ-7RX4",type: "public-key",attObj: "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFD32HDgTSvc6zIlggmLLxXTKQyiabSwuLWNiTpJ3WQfmMoC_qX_QTuWPWHo4",clientData: "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIj9pZFBhY2thZ2VOYW1lIjoiY29tLmFuZHJvaWQuY2hyb21lIn0", …}
attObj: "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFD32HDgTSvcJxUiuiT6ViS4biCWKTR25PIW3beO9V5NdFAAAAALk_2WHy5kYvsSKCACJH3ngAQQE3Qz0J3qGBd7QOh2FvP3a9ngQ8ud1TaBCB0VlA355k9lESiLNEkP5UOwbo3ZnHzPR3NsTR_G7y3-JN5UCfu0V-pQECAyYgASFYID93HTRf5UtMsCsW9D5TyWQDSgMW2MDhiYWKnz3sq16zIlggmLLxXTKQyiabSwuLWNiTpJ3WQfmMoC_qX_QTuWPWHo4"
clientData: "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidFNOS3g5RnVyWFI4dlhVdVBkVms5azhDcEhlMWMydnlrbkdwYUhseXZKYyIsIm9yaWdpbiI6Imh0dHBzOlwvXC9uYWNlc2RlY2lkZS5oZXJva3VhcHAuY29tIiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoiY29tLmFuZHJvaWQuY2hyb21lIn0"
id: "ATdDPQneoYF3tA6HYW8_dr2eBDy53VNoEIHRWUDfnmT2URKIs0SQ_lQ7BujdmcfM9Hc2xNH8bvLf4k3lQJ-7RX4"
rawId: "ATdDPQneoYF3tA6HYW8_dr2eBDy53VNoEIHRWUDfnmT2URKIs0SQ_lQ7BujdmcfM9Hc2xNH8bvLf4k3lQJ-7RX4"
registrationClientExtensions: "{}"
type: "public-key"__proto__: Object

webauthn.js:107 Server validation of credential Failed: Registration Failed. Error: Registration rejected. Error: Unable to verify origin..
didClickRegister @ webauthn.js:107
async function (async)
didClickRegister @ webauthn.js:68

解决方法

我认为问题在于您的 ORIGIN 值有一个尾部斜杠。

Peering into the attestation response's cliendDataJSON,来源报告为"https://nacesdecide.herokuapp.com"

clientDataJSON origin value

Looking at how the Duo WebAuthn library verifies this response,基本来源比较失败,因为您的 ORIGIN"https://nacesdecide.herokuapp.com/" 不等于响应的来源:

Response: "https://nacesdecide.herokuapp.com"
ORIGIN:   "https://nacesdecide.herokuapp.com/"

如果您删除尾部斜杠,那么我敢打赌一切都会按预期验证。

,

@IAmKale 的回答解决了最初的问题。但是,重要的是要注意您可能会遇到 server error: unexpected token < in JSON at position 0。我还没有找到具体的解决方案,但确保使用不同的 username 进行注册修复了它。此外,似乎多次注册需要不同的设备 - 每次注册一台设备。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。