DUO-LABS WebAuthn: Server validation of credential failed: registration failed. error: registration rejected. error: unable to verify origin
DUO-LABS WebAuthn: Server validation of credential failed: registration failed. error: registration rejected. error: unable to verify origin
我尝试使用 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"
:
Looking at how the Duo WebAuthn library verifies this response,基本原点比较失败,因为 "https://nacesdecide.herokuapp.com/"
的 ORIGIN
不等同于响应的原点:
Response: "https://nacesdecide.herokuapp.com"
ORIGIN: "https://nacesdecide.herokuapp.com/"
如果您删除尾部的斜杠,那么我敢打赌一切都会按预期进行验证。
@IAmKale 的回答解决了最初的问题。但是,请务必注意,您可能 运行 变成 server error: unexpected token < in JSON at position 0
。我还没有找到具体的解决方案,但确保使用 distinct username
进行注册修复了它。此外,似乎多次注册需要不同的设备——每次注册一台设备。
我尝试使用 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"
:
Looking at how the Duo WebAuthn library verifies this response,基本原点比较失败,因为 "https://nacesdecide.herokuapp.com/"
的 ORIGIN
不等同于响应的原点:
Response: "https://nacesdecide.herokuapp.com"
ORIGIN: "https://nacesdecide.herokuapp.com/"
如果您删除尾部的斜杠,那么我敢打赌一切都会按预期进行验证。
@IAmKale 的回答解决了最初的问题。但是,请务必注意,您可能 运行 变成 server error: unexpected token < in JSON at position 0
。我还没有找到具体的解决方案,但确保使用 distinct username
进行注册修复了它。此外,似乎多次注册需要不同的设备——每次注册一台设备。