Files
crowd-funder-for-time-pwa/src/libs/didPeer.ts

162 lines
4.5 KiB
TypeScript

import { Buffer } from "buffer/";
import { decode as cborDecode } from "cbor-x";
import { createJWS, JWTPayload, verifyJWT } from "did-jwt";
import { getResolver } from "@veramo/did-provider-peer";
import { generateRandomBytes } from "@/libs/crypto";
export interface JWK {
kty: string;
crv: string;
x: string;
y: string;
}
export interface PublicKeyCredential {
rawId: Uint8Array;
jwt: JWK;
}
export async function registerCredential(
userId: Uint8Array,
challenge: Uint8Array,
) {
const publicKeyOptions: PublicKeyCredentialCreationOptions = {
challenge: challenge,
rp: {
name: "Time Safari",
id: window.location.hostname,
},
user: {
id: userId,
name: "current-user",
displayName: "Current User",
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7, // ES256 algorithm
},
],
authenticatorSelection: {
authenticatorAttachment: "platform",
userVerification: "preferred",
},
timeout: 60000,
attestation: "direct",
};
const credential = await navigator.credentials.create({
publicKey: publicKeyOptions,
});
console.log("credential", credential);
const attestationResponse = credential?.response;
// Parse the attestation response to get the public key
const clientDataJSON = attestationResponse.clientDataJSON;
console.log("clientDataJSON raw", clientDataJSON);
console.log(
"clientDataJSON dec",
new TextDecoder("utf-8").decode(clientDataJSON),
);
const attestationObject = cborDecode(
new Uint8Array(attestationResponse.attestationObject),
);
console.log("attestationObject", attestationObject);
const authData = new Uint8Array(attestationObject.authData);
const publicKey = extractPublicKey(authData);
return { rawId: credential?.rawId, publicKey };
}
function extractPublicKey(authData: Uint8Array) {
// Extract the public key from authData using appropriate parsing
// This involves extracting the COSE key format and converting it to JWK
// For simplicity, we'll assume the public key is at a certain position in authData
const publicKeyCose = authData.slice(authData.length - 77); // Example position
const publicKeyJwk = coseToJwk(publicKeyCose);
return publicKeyJwk;
}
function coseToJwk(coseKey: Uint8Array) {
// Convert COSE key format to JWK
// This is simplified and needs appropriate parsing and conversion logic
return {
kty: "EC",
crv: "P-256",
x: btoa(coseKey.slice(2, 34)),
y: btoa(coseKey.slice(34, 66)),
};
}
export async function createJwt(
payload: object,
issuerDid: string,
credentialId: ArrayBuffer,
) {
const signer = await webAuthnES256KSigner(credentialId);
// from createJWT in did-jwt/src/JWT.ts
const header: JWTPayload = { typ: "JWT", alg: "ES256K" };
const timestamps: Partial<JWTPayload> = {
iat: Math.floor(Date.now() / 1000),
exp: undefined,
};
const fullPayload = { ...timestamps, ...payload, iss: issuerDid };
return createJWS(fullPayload, signer, header);
}
async function webAuthnES256KSigner(credentialID: ArrayBuffer) {
return async (data: string | Uint8Array) => {
// also has clientDataJSON
const { signature, authenticatorData } = await generateWebAuthnSignature(
data,
credentialID,
);
// Combine the WebAuthn components into a single signature format as required by did-jwt
const combinedSignature = Buffer.concat([
Buffer.from(authenticatorData),
Buffer.from(signature),
]);
return combinedSignature.toString("base64");
};
}
async function generateWebAuthnSignature(
dataToSign: string | Uint8Array,
credentialId: ArrayBuffer,
) {
if (!(dataToSign instanceof Uint8Array)) {
dataToSign = new TextEncoder().encode(dataToSign as string);
}
const challenge = generateRandomBytes(32);
const options = {
challenge: challenge,
allowCredentials: [{ id: credentialId, type: "public-key" }],
userVerification: "preferred",
};
const assertion = await navigator.credentials.get({ publicKey: options });
const authenticatorAssertionResponse = assertion?.response;
return {
signature: authenticatorAssertionResponse.signature,
clientDataJSON: authenticatorAssertionResponse.clientDataJSON,
authenticatorData: authenticatorAssertionResponse.authenticatorData,
};
}
export async function verifyJwt(jwt: string, publicKey: JWK) {
const decoded = verifyJWT(jwt, {
didAuthenticator: {
authenticators: [{ publicKeyJwk: publicKey }],
},
resolver: getResolver(),
});
return decoded;
}