"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,
            ...(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;
    const type = '"type":"webauthn.get"';
    if (type !== clientDataJSON.slice(Number(typeIndex), type.length + 1))
        return false;
    const match = clientDataJSON
        .slice(Number(challengeIndex))
        .match(/^"challenge":"(.*?)"/);
    if (!match)
        return false;
    const [_, challenge_extracted] = match;
    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