@passwordless-id/webauthn
Version:
A small wrapper around the webauthn protocol to make one's life easier.
93 lines (92 loc) • 3.85 kB
JavaScript
import * as utils from './utils.js';
import { authenticatorMetadata } from './authenticatorMetadata.js';
const utf8Decoder = new TextDecoder('utf-8');
export function parseClient(data) {
if (typeof data == 'string')
data = utils.parseBase64url(data);
return JSON.parse(utf8Decoder.decode(data));
}
export function parseAuthenticator(authData) {
if (typeof authData == 'string')
authData = utils.parseBase64url(authData);
//console.debug(authData)
let flags = new DataView(authData.slice(32, 33)).getUint8(0);
//console.debug(flags)
// https://w3c.github.io/webauthn/#sctn-authenticator-data
return {
rpIdHash: extractRpIdHash(authData),
flags: {
userPresent: !!(flags & 1),
//reserved1: !!(flags & 2),
userVerified: !!(flags & 4),
backupEligibility: !!(flags & 8),
backupState: !!(flags & 16),
//reserved2: !!(flags & 32),
attestedData: !!(flags & 64),
extensionsIncluded: !!(flags & 128)
},
signCount: new DataView(authData.slice(33, 37)).getUint32(0, false), // Big-Endian!
aaguid: extractAaguid(authData),
//credentialId: extractCredentialId()
};
}
function extractRpIdHash(authData) {
return utils.toBase64url(authData.slice(0, 32));
}
/**
* Returns the AAGUID in the format "00000000-0000-0000-0000-000000000000"
*/
function extractAaguid(authData) {
if (authData.byteLength < 53)
return "00000000-0000-0000-0000-000000000000";
const buffer = authData.slice(37, 53); // 16 byte
const hex = utils.bufferToHex(buffer);
const aaguid = `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20, 32)}`;
return aaguid; // example: "d41f5a69-b817-4144-a13c-9ebd6d9254d6"
}
export function getAlgoName(num) {
switch (num) {
case -7: return "ES256";
case -8: return "EdDSA";
case -257: return "RS256";
default: throw new Error(`Unknown algorithm code: ${num}`);
}
}
export function parseRegistration(registrationJson) {
const authenticator = parseAuthenticator(registrationJson.response.authenticatorData);
return toRegistrationInfo(registrationJson, authenticator);
}
export function toRegistrationInfo(registrationJson, authenticator) {
const aaguid = authenticator.aaguid;
return {
authenticator: {
aaguid,
counter: authenticator.signCount,
icon_light: 'https://webauthn.passwordless.id/authenticators/' + aaguid + '-light.png',
icon_dark: 'https://webauthn.passwordless.id/authenticators/' + aaguid + '-dark.png',
name: authenticatorMetadata[aaguid] ?? 'Unknown',
},
credential: {
id: registrationJson.id,
publicKey: registrationJson.response.publicKey,
algorithm: getAlgoName(registrationJson.response.publicKeyAlgorithm),
transports: registrationJson.response.transports
},
synced: authenticator.flags.backupEligibility,
user: registrationJson.user, // That's specific to this library
userVerified: authenticator.flags.userVerified,
};
}
export function toAuthenticationInfo(authenticationJson, authenticator) {
return {
credentialId: authenticationJson.id,
userId: authenticationJson.response.userHandle,
counter: authenticator.signCount,
userVerified: authenticator.flags.userVerified,
authenticatorAttachment: authenticationJson.authenticatorAttachment
};
}
export function parseAuthentication(authenticationJson) {
const authenticator = parseAuthenticator(authenticationJson.response.authenticatorData);
return toAuthenticationInfo(authenticationJson, authenticator);
}