UNPKG

ox

Version:

Ethereum Standard Library

248 lines 9.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CredentialRequestFailedError = exports.CredentialCreationFailedError = exports.createChallenge = void 0; exports.createCredential = createCredential; exports.getAuthenticatorData = getAuthenticatorData; exports.getClientDataJSON = getClientDataJSON; exports.getCredentialCreationOptions = getCredentialCreationOptions; exports.getCredentialRequestOptions = getCredentialRequestOptions; exports.getSignPayload = getSignPayload; exports.sign = sign; exports.verify = verify; const Base64 = require("./Base64.js"); const Bytes = require("./Bytes.js"); const Errors = require("./Errors.js"); const Hash = require("./Hash.js"); const Hex = require("./Hex.js"); const internal = require("./internal/webauthn.js"); const P256 = require("./P256.js"); exports.createChallenge = Uint8Array.from([ 105, 171, 180, 181, 160, 222, 75, 198, 42, 42, 32, 31, 141, 37, 186, 233, ]); async function createCredential(options) { const { createFn = window.navigator.credentials.create.bind(window.navigator.credentials), ...rest } = options; const creationOptions = getCredentialCreationOptions(rest); try { const credential = (await createFn(creationOptions)); if (!credential) throw new CredentialCreationFailedError(); const response = credential.response; const publicKey = await internal.parseCredentialPublicKey(response); return { id: credential.id, publicKey, raw: credential, }; } catch (error) { throw new CredentialCreationFailedError({ cause: error, }); } } function getAuthenticatorData(options = {}) { const { flag = 5, rpId = window.location.hostname, signCount = 0 } = options; const rpIdHash = Hash.sha256(Hex.fromString(rpId)); const flag_bytes = Hex.fromNumber(flag, { size: 1 }); const signCount_bytes = Hex.fromNumber(signCount, { size: 4 }); return Hex.concat(rpIdHash, flag_bytes, signCount_bytes); } function getClientDataJSON(options) { const { challenge, crossOrigin = false, extraClientData, origin = window.location.origin, } = options; return JSON.stringify({ type: 'webauthn.get', challenge: Base64.fromHex(challenge, { url: true, pad: false }), origin, crossOrigin, ...extraClientData, }); } function getCredentialCreationOptions(options) { const { attestation = 'none', authenticatorSelection = { residentKey: 'preferred', requireResidentKey: false, userVerification: 'required', }, challenge = exports.createChallenge, excludeCredentialIds, extensions, name: name_, rp = { id: window.location.hostname, name: window.document.title, }, user, } = options; const name = (user?.name ?? name_); return { publicKey: { attestation, authenticatorSelection, challenge: typeof challenge === 'string' ? Bytes.fromHex(challenge) : challenge, ...(excludeCredentialIds ? { excludeCredentials: excludeCredentialIds?.map((id) => ({ id: Base64.toBytes(id), type: 'public-key', })), } : {}), pubKeyCredParams: [ { type: 'public-key', alg: -7, }, ], ...(extensions && { extensions }), rp, user: { id: user?.id ?? Hash.keccak256(Bytes.fromString(name), { as: 'Bytes' }), name, displayName: user?.displayName ?? name, }, }, }; } function getCredentialRequestOptions(options) { const { credentialId, challenge, extensions, rpId = window.location.hostname, userVerification = 'required', } = options; return { publicKey: { ...(credentialId ? { allowCredentials: Array.isArray(credentialId) ? credentialId.map((id) => ({ id: Base64.toBytes(id), type: 'public-key', })) : [ { id: Base64.toBytes(credentialId), type: 'public-key', }, ], } : {}), challenge: Bytes.fromHex(challenge), ...(extensions && { extensions }), rpId, userVerification, }, }; } function getSignPayload(options) { const { challenge, crossOrigin, extraClientData, flag, origin, rpId, signCount, userVerification = 'required', } = options; const authenticatorData = getAuthenticatorData({ flag, rpId, signCount, }); const clientDataJSON = getClientDataJSON({ challenge, crossOrigin, extraClientData, origin, }); const clientDataJSONHash = Hash.sha256(Hex.fromString(clientDataJSON)); const challengeIndex = clientDataJSON.indexOf('"challenge"'); const typeIndex = clientDataJSON.indexOf('"type"'); const metadata = { authenticatorData, clientDataJSON, challengeIndex, typeIndex, userVerificationRequired: userVerification === 'required', }; const payload = Hex.concat(authenticatorData, clientDataJSONHash); return { metadata, payload }; } async function sign(options) { const { getFn = window.navigator.credentials.get.bind(window.navigator.credentials), ...rest } = options; const requestOptions = getCredentialRequestOptions(rest); try { const credential = (await getFn(requestOptions)); if (!credential) throw new CredentialRequestFailedError(); const response = credential.response; const clientDataJSON = String.fromCharCode(...new Uint8Array(response.clientDataJSON)); const challengeIndex = clientDataJSON.indexOf('"challenge"'); const typeIndex = clientDataJSON.indexOf('"type"'); const signature = internal.parseAsn1Signature(new Uint8Array(response.signature)); return { metadata: { authenticatorData: Hex.fromBytes(new Uint8Array(response.authenticatorData)), clientDataJSON, challengeIndex, typeIndex, userVerificationRequired: requestOptions.publicKey.userVerification === 'required', }, signature, raw: credential, }; } catch (error) { throw new CredentialRequestFailedError({ cause: error, }); } } function verify(options) { const { challenge, hash = true, metadata, publicKey, signature } = options; const { authenticatorData, challengeIndex, clientDataJSON, typeIndex, userVerificationRequired, } = metadata; const authenticatorDataBytes = Bytes.fromHex(authenticatorData); if (authenticatorDataBytes.length < 37) return false; const flag = authenticatorDataBytes[32]; if ((flag & 0x01) !== 0x01) return false; if (userVerificationRequired && (flag & 0x04) !== 0x04) return false; if ((flag & 0x08) !== 0x08 && (flag & 0x10) === 0x10) return false; if (typeIndex !== undefined) { const type = '"type":"webauthn.get"'; if (type !== clientDataJSON.slice(Number(typeIndex), type.length + 1)) return false; } const challengeMatch = challengeIndex !== undefined ? clientDataJSON .slice(Number(challengeIndex)) .match(/^"challenge":"(.*?)"/) : clientDataJSON.match(/"challenge":"(.*?)"/); if (!challengeMatch) return false; const [_, challenge_extracted] = challengeMatch; if (Hex.fromBytes(Base64.toBytes(challenge_extracted)) !== challenge) return false; const clientDataJSONHash = Hash.sha256(Bytes.fromString(clientDataJSON), { as: 'Bytes', }); const payload = Bytes.concat(authenticatorDataBytes, clientDataJSONHash); return P256.verify({ hash, payload, publicKey, signature, }); } class CredentialCreationFailedError extends Errors.BaseError { constructor({ cause } = {}) { super('Failed to create credential.', { cause, }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'WebAuthnP256.CredentialCreationFailedError' }); } } exports.CredentialCreationFailedError = CredentialCreationFailedError; class CredentialRequestFailedError extends Errors.BaseError { constructor({ cause } = {}) { super('Failed to request credential.', { cause, }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'WebAuthnP256.CredentialRequestFailedError' }); } } exports.CredentialRequestFailedError = CredentialRequestFailedError; //# sourceMappingURL=WebAuthnP256.js.map