120 lines
3.0 KiB
TypeScript
120 lines
3.0 KiB
TypeScript
import { decode } from 'cbor2';
|
|
import { page } from '$app/state';
|
|
|
|
interface CreateCredential extends Credential {
|
|
response: AuthenticatorAttestationResponse;
|
|
}
|
|
|
|
interface AttestationObject {
|
|
authData: Uint8Array;
|
|
fmt: string;
|
|
}
|
|
|
|
interface DecodedPublicKeyObject {
|
|
[key: number]: number | Uint8Array;
|
|
}
|
|
|
|
export async function createPasskey(username: string, userid: number, challenge: string) {
|
|
const challengeBuffer = Uint8Array.from(challenge, (c) => c.charCodeAt(0));
|
|
const idBuffer = Uint8Array.from(userid.toString(), (c) => c.charCodeAt(0));
|
|
|
|
const credential = (await navigator.credentials.create({
|
|
publicKey: {
|
|
challenge: challengeBuffer,
|
|
rp: { id: page.url.hostname, name: 'TrevStack' },
|
|
user: {
|
|
id: idBuffer,
|
|
name: username,
|
|
displayName: username
|
|
},
|
|
pubKeyCredParams: [
|
|
{
|
|
type: 'public-key',
|
|
alg: -7
|
|
},
|
|
{
|
|
type: 'public-key',
|
|
alg: -257
|
|
}
|
|
],
|
|
timeout: 60000,
|
|
attestation: 'none'
|
|
}
|
|
})) as CreateCredential | null;
|
|
|
|
if (!credential) {
|
|
throw new Error('Could not create passkey');
|
|
}
|
|
|
|
console.log(credential.id);
|
|
//console.log(credential.type);
|
|
|
|
const utf8Decoder = new TextDecoder('utf-8');
|
|
const decodedClientData = utf8Decoder.decode(credential.response.clientDataJSON);
|
|
const clientDataObj = JSON.parse(decodedClientData);
|
|
|
|
console.log(clientDataObj);
|
|
|
|
const attestationObject = new Uint8Array(credential.response.attestationObject);
|
|
const decodedAttestationObject = decode(attestationObject) as AttestationObject;
|
|
|
|
const { authData } = decodedAttestationObject;
|
|
|
|
// get the length of the credential ID
|
|
const dataView = new DataView(new ArrayBuffer(2));
|
|
const idLenBytes = authData.slice(53, 55);
|
|
idLenBytes.forEach((value, index) => dataView.setUint8(index, value));
|
|
const credentialIdLength = dataView.getUint16(0);
|
|
|
|
// get the credential ID
|
|
// const credentialId = authData.slice(55, 55 + credentialIdLength);
|
|
|
|
// get the public key object
|
|
const publicKeyBytes = authData.slice(55 + credentialIdLength);
|
|
|
|
console.log(publicKeyBytes);
|
|
|
|
// the publicKeyBytes are encoded again as CBOR
|
|
const publicKeyObject = new Uint8Array(publicKeyBytes.buffer);
|
|
const decodedPublicKeyObject = decode(publicKeyObject) as DecodedPublicKeyObject;
|
|
|
|
console.log(decodedPublicKeyObject);
|
|
|
|
return {
|
|
id: credential.id,
|
|
publicKey: publicKeyBytes,
|
|
algorithm: decodedPublicKeyObject[3]
|
|
};
|
|
}
|
|
|
|
interface GetCredential extends Credential {
|
|
response: AuthenticatorAssertionResponse;
|
|
}
|
|
|
|
export async function getPasskey(passkeyids: string[], challenge: string) {
|
|
const challengeBuffer = Uint8Array.from(challenge, (c) => c.charCodeAt(0));
|
|
|
|
const credential = (await navigator.credentials.get({
|
|
publicKey: {
|
|
challenge: challengeBuffer,
|
|
allowCredentials: passkeyids.map((passkeyid) => {
|
|
return {
|
|
id: Uint8Array.from(passkeyid, (c) => c.charCodeAt(0)),
|
|
type: 'public-key'
|
|
};
|
|
}),
|
|
timeout: 60000
|
|
}
|
|
})) as GetCredential | null;
|
|
|
|
if (!credential) {
|
|
throw new Error('Could not get passkey');
|
|
}
|
|
|
|
const signature = credential.response.signature;
|
|
|
|
return {
|
|
signature
|
|
};
|
|
}
|