diff --git a/src/public/scripts/main.ts b/src/public/scripts/main.ts index eb6e8c3..ab4e7b6 100644 --- a/src/public/scripts/main.ts +++ b/src/public/scripts/main.ts @@ -44,7 +44,6 @@ import { import { IconButton } from '@material/mwc-icon-button'; import { StoredCredential } from './common'; -const BASE64_SLICE_LENGTH = 40; const aaguids = await fetch('/webauthn/aaguids').then(res => res.json()); const app = initializeApp({ @@ -279,8 +278,8 @@ async function parseRegistrationCredential( cred: RegistrationCredential ): Promise { const credJSON = { - id: cred.id.slice(0, BASE64_SLICE_LENGTH)+'...', - rawId: cred.id.slice(0, BASE64_SLICE_LENGTH)+'...', + id: cred.id, + rawId: cred.id, type: cred.type, response: { clientDataJSON: {}, @@ -298,7 +297,7 @@ async function parseRegistrationCredential( credJSON.response.clientDataJSON = JSON.parse(decoder.decode(cred.response.clientDataJSON)); const attestationObject = cbor.decodeAllSync(cred.response.attestationObject)[0] - attestationObject.authData = await parseAuthenticatorData(attestationObject.authData); + attestationObject.authData = await parseAuthData(attestationObject.authData); credJSON.response.attestationObject = attestationObject; if (cred.response.getTransports) { @@ -316,8 +315,8 @@ async function parseAuthenticationCredential( const userHandle = cred.response.userHandle ? base64url.encode(cred.response.userHandle) : undefined; const credJSON = { - id: cred.id.slice(0, BASE64_SLICE_LENGTH)+'...', - rawId: cred.id.slice(0, BASE64_SLICE_LENGTH)+'...', + id: cred.id, + rawId: cred.id, type: cred.type, response: { clientDataJSON: {}, @@ -330,14 +329,14 @@ async function parseAuthenticationCredential( const decoder = new TextDecoder('utf-8'); credJSON.response.clientDataJSON = JSON.parse(decoder.decode(cred.response.clientDataJSON)); - credJSON.response.authenticatorData = await parseAuthenticatorData(cred.response.authenticatorData); + credJSON.response.authenticatorData = await parseAuthenticatorData(new Uint8Array(cred.response.authenticatorData)); credJSON.clientExtensionResults = parseClientExtensionResults(cred); return credJSON; } -async function parseAuthenticatorData( +async function parseAuthData( buffer: any ): Promise { const authData = { @@ -358,7 +357,7 @@ async function parseAuthenticatorData( const rpIdHash = buffer.slice(0, 32); buffer = buffer.slice(32); - authData.rpIdHash = rpIdHash.toString('hex'); + authData.rpIdHash = [...rpIdHash].map(x => x.toString(16).padStart(2, '0')).join(''); const flags = (buffer.slice(0, 1))[0]; buffer = buffer.slice(1); @@ -382,7 +381,7 @@ async function parseAuthenticatorData( const credIDLenBuf = buffer.slice(0, 2); buffer = buffer.slice(2); const credIDLen = credIDLenBuf.readUInt16BE(0) - authData.credentialID = (base64url.encode(buffer.slice(0, credIDLen))).slice(0, BASE64_SLICE_LENGTH)+'...'; + authData.credentialID = base64url.encode(buffer.slice(0, credIDLen)); buffer = buffer.slice(credIDLen); const firstDecoded = cbor.decodeFirstSync(buffer.slice(0)); @@ -392,6 +391,39 @@ async function parseAuthenticatorData( return authData; } +async function parseAuthenticatorData( + buffer: any +): Promise { + const authData = { + rpIdHash: '', + flags: { + up: false, + uv: false, + be: false, + bs: false, + at: false, + ed: false, + }, + }; + + const rpIdHash = buffer.slice(0, 32); + buffer = buffer.slice(32); + authData.rpIdHash = [...rpIdHash].map(x => x.toString(16).padStart(2, '0')).join(''); + + const flags = (buffer.slice(0, 1))[0]; + buffer = buffer.slice(1); + authData.flags = { + up: !!(flags & (1 << 0)), + uv: !!(flags & (1 << 2)), + be: !!(flags & (1 << 3)), + bs: !!(flags & (1 << 4)), + at: !!(flags & (1 << 6)), + ed: !!(flags & (1 << 7)), + }; + + return authData; +} + function parseClientExtensionResults( credential: RegistrationCredential | AuthenticationCredential ): AuthenticationExtensionsClientOutputs { @@ -635,7 +667,7 @@ const authenticate = async (opts: WebAuthnAuthenticationObject): Promise => const parsedCredential = await parseAuthenticationCredential(credential); - console.log('[AuthenticationResponseJSON]', encodedCredential); + console.log('[AuthenticationResponseJSON]', parsedCredential); // Verify and store the credential. await _fetch('/webauthn/authResponse', encodedCredential); @@ -774,8 +806,8 @@ const onRegisterPlatformAuthenticator = async (): Promise => { opts.authenticatorSelection = opts.authenticatorSelection || {}; opts.authenticatorSelection.authenticatorAttachment = 'platform'; try { - await registerCredential(opts); - showSnackbar('A credential successfully registered!'); + const parsedCredential = await registerCredential(opts); + showSnackbar('A credential successfully registered!', parsedCredential); listCredentials(); } catch (e: any) { console.error(e); diff --git a/src/public/scripts/util.ts b/src/public/scripts/util.ts index 273e599..9868eda 100644 --- a/src/public/scripts/util.ts +++ b/src/public/scripts/util.ts @@ -19,18 +19,25 @@ import { LinearProgress } from '@material/mwc-linear-progress'; import { html, render } from 'lit'; const $: any = document.querySelector.bind(document); +const BASE64_SLICE_LENGTH = 40; const snackbar = $('#snackbar') as Snackbar; function showPayload( - payload: JSON + payload: any ): void { + payload.id = payload.id.slice(0, BASE64_SLICE_LENGTH)+'...'; + payload.rawId = payload.rawId.slice(0, BASE64_SLICE_LENGTH)+'...'; + if (payload.response?.authData) { + payload.response.authData = payload.response.authData.slice(0, BASE64_SLICE_LENGTH)+'...'; + } $('#json-viewer').data = { payload }; $('#json-viewer').expandAll(); $('#payload-viewer').show(); }; function showSnackbar(message: string, payload?: any): void { + $('#snack-button')?.remove(); snackbar.labelText = message; if (payload) { const button = document.createElement('mwc-button'); @@ -41,8 +48,6 @@ function showSnackbar(message: string, payload?: any): void { showPayload(payload); }); snackbar.appendChild(button); - } else { - $('#snack-button')?.remove(); } snackbar.show(); }; diff --git a/src/public/styles/style.scss b/src/public/styles/style.scss index c4728ae..8329940 100644 --- a/src/public/styles/style.scss +++ b/src/public/styles/style.scss @@ -24,6 +24,7 @@ * { --mdc-theme-primary: rgb(255,64,129); + --mdc-dialog-max-width: 1000px; } html, body, main { @@ -141,7 +142,6 @@ footer { } mwc-dialog { - min-width: 300px; mwc-textfield { margin: 8px 0; } @@ -150,4 +150,5 @@ mwc-dialog { json-viewer { font-family: monospace; font-size: 12px; + overflow: scroll; }