import * as Base64 from './Base64.js' import * as Bytes from './Bytes.js' import * as Errors from './Errors.js' import * as Hash from './Hash.js' import * as Hex from './Hex.js' import * as P256 from './P256.js' import type * as PublicKey from './PublicKey.js' import type * as Signature from './Signature.js' import type { Compute, OneOf } from './internal/types.js' import * as internal from './internal/webauthn.js' /** A WebAuthn-flavored P256 credential. */ export type P256Credential = { id: string publicKey: PublicKey.PublicKey raw: internal.PublicKeyCredential } /** Metadata for a WebAuthn P256 signature. */ export type SignMetadata = Compute<{ authenticatorData: Hex.Hex challengeIndex: number clientDataJSON: string typeIndex: number userVerificationRequired: boolean }> export const createChallenge = Uint8Array.from([ 105, 171, 180, 181, 160, 222, 75, 198, 42, 42, 32, 31, 141, 37, 186, 233, ]) /** * Creates a new WebAuthn P256 Credential, which can be stored and later used for signing. * * @example * ```ts twoslash * import { WebAuthnP256 } from 'ox' * * const credential = await WebAuthnP256.createCredential({ name: 'Example' }) // [!code focus] * // @log: { * // @log: id: 'oZ48...', * // @log: publicKey: { x: 51421...5123n, y: 12345...6789n }, * // @log: raw: PublicKeyCredential {}, * // @log: } * * const { metadata, signature } = await WebAuthnP256.sign({ * credentialId: credential.id, * challenge: '0xdeadbeef', * }) * ``` * * @param options - Credential creation options. * @returns A WebAuthn P256 credential. */ export async function createCredential( options: createCredential.Options, ): Promise { const { createFn = window.navigator.credentials.create.bind( window.navigator.credentials, ), ...rest } = options const creationOptions = getCredentialCreationOptions(rest) try { const credential = (await createFn( creationOptions, )) as internal.PublicKeyCredential if (!credential) throw new CredentialCreationFailedError() const response = credential.response as AuthenticatorAttestationResponse const publicKey = await internal.parseCredentialPublicKey(response) return { id: credential.id, publicKey, raw: credential, } } catch (error) { throw new CredentialCreationFailedError({ cause: error as Error, }) } } export declare namespace createCredential { type Options = getCredentialCreationOptions.Options & { /** * Credential creation function. Useful for environments that do not support * the WebAuthn API natively (i.e. React Native or testing environments). * * @default window.navigator.credentials.create */ createFn?: | (( options?: internal.CredentialCreationOptions | undefined, ) => Promise) | undefined } type ErrorType = | getCredentialCreationOptions.ErrorType | internal.parseCredentialPublicKey.ErrorType | Errors.GlobalErrorType } /** * Gets the authenticator data which contains information about the * processing of an authenticator request (ie. from `WebAuthnP256.sign`). * * :::warning * * This function is mainly for testing purposes or for manually constructing * autenticator data. In most cases you will not need this function. * `authenticatorData` is typically returned as part of the * {@link ox#WebAuthnP256.(sign:function)} response (ie. an authenticator response). * * ::: * * @example * ```ts twoslash * import { WebAuthnP256 } from 'ox' * * const authenticatorData = WebAuthnP256.getAuthenticatorData({ * rpId: 'example.com', * signCount: 420, * }) * // @log: "0xa379a6f6eeafb9a55e378c118034e2751e682fab9f2d30ab13d2125586ce194705000001a4" * ``` * * @param options - Options to construct the authenticator data. * @returns The authenticator data. */ export function getAuthenticatorData( options: getAuthenticatorData.Options = {}, ): Hex.Hex { 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) } export declare namespace getAuthenticatorData { type Options = { /** A bitfield that indicates various attributes that were asserted by the authenticator. [Read more](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Authenticator_data#flags) */ flag?: number | undefined /** The [Relying Party ID](https://w3c.github.io/webauthn/#relying-party-identifier) that the credential is scoped to. */ rpId?: internal.PublicKeyCredentialRequestOptions['rpId'] | undefined /** A signature counter, if supported by the authenticator (set to 0 otherwise). */ signCount?: number | undefined } type ErrorType = Errors.GlobalErrorType } /** * Constructs the Client Data in stringified JSON format which represents client data that * was passed to `credentials.get()` in {@link ox#WebAuthnP256.(sign:function)}. * * :::warning * * This function is mainly for testing purposes or for manually constructing * client data. In most cases you will not need this function. * `clientDataJSON` is typically returned as part of the * {@link ox#WebAuthnP256.(sign:function)} response (ie. an authenticator response). * * ::: * * @example * ```ts twoslash * import { WebAuthnP256 } from 'ox' * * const clientDataJSON = WebAuthnP256.getClientDataJSON({ * challenge: '0xdeadbeef', * origin: 'https://example.com', * }) * // @log: "{"type":"webauthn.get","challenge":"3q2-7w","origin":"https://example.com","crossOrigin":false}" * ``` * * @param options - Options to construct the client data. * @returns The client data. */ export function getClientDataJSON(options: getClientDataJSON.Options): string { 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, }) } export declare namespace getClientDataJSON { type Options = { /** The challenge to sign. */ challenge: Hex.Hex /** If set to `true`, it means that the calling context is an `