Skip to content

Commit

Permalink
Fix bugs with WebAuthn preventing sign in and registration. (#22651) (#…
Browse files Browse the repository at this point in the history
…22721)

Partial Backport #22651

This PR fixes a longstanding bug within webauthn due to the backend
using URLEncodedBase64 but the javascript using decoding using plain
base64. This causes intermittent issues with users reporting decoding
errors.

Fix #22507

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
  • Loading branch information
3 people authored Feb 2, 2023
1 parent 9cde526 commit 2e12161
Showing 1 changed file with 22 additions and 15 deletions.
37 changes: 22 additions & 15 deletions web_src/js/features/user-auth-webauthn.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export function initUserAuthWebAuthn() {

$.getJSON(`${appSubUrl}/user/webauthn/assertion`, {})
.done((makeAssertionOptions) => {
makeAssertionOptions.publicKey.challenge = decode(makeAssertionOptions.publicKey.challenge);
makeAssertionOptions.publicKey.challenge = decodeURLEncodedBase64(makeAssertionOptions.publicKey.challenge);
for (let i = 0; i < makeAssertionOptions.publicKey.allowCredentials.length; i++) {
makeAssertionOptions.publicKey.allowCredentials[i].id = decode(makeAssertionOptions.publicKey.allowCredentials[i].id);
makeAssertionOptions.publicKey.allowCredentials[i].id = decodeURLEncodedBase64(makeAssertionOptions.publicKey.allowCredentials[i].id);
}
navigator.credentials.get({
publicKey: makeAssertionOptions.publicKey
Expand Down Expand Up @@ -56,14 +56,14 @@ function verifyAssertion(assertedCredential) {
type: 'POST',
data: JSON.stringify({
id: assertedCredential.id,
rawId: bufferEncode(rawId),
rawId: encodeURLEncodedBase64(rawId),
type: assertedCredential.type,
clientExtensionResults: assertedCredential.getClientExtensionResults(),
response: {
authenticatorData: bufferEncode(authData),
clientDataJSON: bufferEncode(clientDataJSON),
signature: bufferEncode(sig),
userHandle: bufferEncode(userHandle),
authenticatorData: encodeURLEncodedBase64(authData),
clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
signature: encodeURLEncodedBase64(sig),
userHandle: encodeURLEncodedBase64(userHandle),
},
}),
contentType: 'application/json; charset=utf-8',
Expand All @@ -85,14 +85,21 @@ function verifyAssertion(assertedCredential) {
});
}

// Encode an ArrayBuffer into a base64 string.
function bufferEncode(value) {
// Encode an ArrayBuffer into a URLEncoded base64 string.
function encodeURLEncodedBase64(value) {
return encode(value)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}

// Dccode a URLEncoded base64 to an ArrayBuffer string.
function decodeURLEncodedBase64(value) {
return decode(value
.replace(/_/g, '/')
.replace(/-/g, '+'));
}

function webauthnRegistered(newCredential) {
const attestationObject = new Uint8Array(newCredential.response.attestationObject);
const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
Expand All @@ -104,11 +111,11 @@ function webauthnRegistered(newCredential) {
headers: {'X-Csrf-Token': csrfToken},
data: JSON.stringify({
id: newCredential.id,
rawId: bufferEncode(rawId),
rawId: encodeURLEncodedBase64(rawId),
type: newCredential.type,
response: {
attestationObject: bufferEncode(attestationObject),
clientDataJSON: bufferEncode(clientDataJSON),
attestationObject: encodeURLEncodedBase64(attestationObject),
clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
},
}),
dataType: 'json',
Expand Down Expand Up @@ -184,11 +191,11 @@ function webAuthnRegisterRequest() {
}).done((makeCredentialOptions) => {
$('#nickname').closest('div.field').removeClass('error');

makeCredentialOptions.publicKey.challenge = decode(makeCredentialOptions.publicKey.challenge);
makeCredentialOptions.publicKey.user.id = decode(makeCredentialOptions.publicKey.user.id);
makeCredentialOptions.publicKey.challenge = decodeURLEncodedBase64(makeCredentialOptions.publicKey.challenge);
makeCredentialOptions.publicKey.user.id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.user.id);
if (makeCredentialOptions.publicKey.excludeCredentials) {
for (let i = 0; i < makeCredentialOptions.publicKey.excludeCredentials.length; i++) {
makeCredentialOptions.publicKey.excludeCredentials[i].id = decode(makeCredentialOptions.publicKey.excludeCredentials[i].id);
makeCredentialOptions.publicKey.excludeCredentials[i].id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.excludeCredentials[i].id);
}
}

Expand Down

0 comments on commit 2e12161

Please sign in to comment.