diff --git a/package-lock.json b/package-lock.json index 2588e33..3bdd16f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@veramo/key-manager": "^5.6.0", "@vueuse/core": "^10.9.0", "@zxing/text-encoding": "^0.9.0", + "asn1-ber": "^1.2.2", "axios": "^1.6.8", "cbor-x": "^1.5.9", "class-transformer": "^0.5.1", @@ -10347,6 +10348,11 @@ "license": "MIT", "optional": true }, + "node_modules/asn1-ber": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/asn1-ber/-/asn1-ber-1.2.2.tgz", + "integrity": "sha512-CbNem/7hxrjSiOAOOTX4iZxu+0m3jiLqlsERQwwPM1IDR/22M8IPpA1VVndCLw5KtjRYyRODbvAEIfuTogNDng==" + }, "node_modules/asn1.js": { "version": "5.4.1", "license": "MIT", diff --git a/package.json b/package.json index 6b1b697..40a9dc9 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@veramo/key-manager": "^5.6.0", "@vueuse/core": "^10.9.0", "@zxing/text-encoding": "^0.9.0", + "asn1-ber": "^1.2.2", "axios": "^1.6.8", "cbor-x": "^1.5.9", "class-transformer": "^0.5.1", diff --git a/src/libs/didPeer.ts b/src/libs/didPeer.ts index b674ef2..7305f6b 100644 --- a/src/libs/didPeer.ts +++ b/src/libs/didPeer.ts @@ -1,11 +1,10 @@ +import asn1 from "asn1-ber"; 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 { DIDResolutionResult, Resolver } from "did-resolver"; import { bytesToMultibase } from "@veramo/utils"; -import { generateRandomBytes } from "@/libs/crypto"; - export interface JWK { kty: string; crv: string; @@ -102,7 +101,7 @@ export function createPeerDid(publicKeyCose: Uint8Array) { const methodSpecificId = bytesToMultibase( publicKeyCose, "base58btc", - "ed25519-pub", + "secp256k1-pub", ); return "did:peer:0" + methodSpecificId; } @@ -128,18 +127,53 @@ export async function createJwt( async function webAuthnES256KSigner(credentialID: ArrayBuffer) { return async (data: string | Uint8Array) => { // also has clientDataJSON - const { signature, authenticatorData } = await generateWebAuthnSignature( - data, - credentialID, - ); + const { signature } = await generateWebAuthnSignature(data, credentialID); + + // This converts from the browser ArrayBuffer to a Node.js Buffer, which is a requirement for the asn1 library. + const signatureBuffer = Buffer.from(signature); + console.log("signature inside signer", signature); + console.log("buffer signature inside signer", signatureBuffer); + // Decode the DER-encoded signature to extract R and S values + const reader = new asn1.BerReader(signatureBuffer); + console.log("after reader"); + reader.readSequence(); + console.log("after read sequence"); + const r = reader.readString(asn1.Ber.Integer, true); + console.log("after r"); + const s = reader.readString(asn1.Ber.Integer, true); + console.log("after r & s"); - // Combine the WebAuthn components into a single signature format as required by did-jwt - const combinedSignature = Buffer.concat([ - Buffer.from(authenticatorData), - Buffer.from(signature), - ]); + // Ensure R and S are 32 bytes each + const rBuffer = Buffer.from(r); + const sBuffer = Buffer.from(s); + console.log("after rBuffer & sBuffer", rBuffer, sBuffer); + const rWithoutPrefix = rBuffer.length > 32 ? rBuffer.slice(1) : rBuffer; + const sWithoutPrefix = sBuffer.length > 32 ? sBuffer.slice(1) : sBuffer; + const rPadded = + rWithoutPrefix.length < 32 + ? Buffer.concat([Buffer.alloc(32 - rWithoutPrefix.length), rBuffer]) + : rWithoutPrefix; + const sPadded = + rWithoutPrefix.length < 32 + ? Buffer.concat([Buffer.alloc(32 - sWithoutPrefix.length), sBuffer]) + : sWithoutPrefix; - return combinedSignature.toString("base64"); + // Concatenate R and S to form the 64-byte array (ECDSA signature format expected by JWT) + const combinedSignature = Buffer.concat([rPadded, sPadded]); + console.log( + "combinedSignature", + combinedSignature.length, + combinedSignature, + ); + + const combSig64 = combinedSignature.toString("base64"); + console.log("combSig64", combSig64); + const combSig64Url = combSig64 + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); + console.log("combSig64Url", combSig64Url); + return combSig64Url; }; } @@ -150,17 +184,25 @@ async function generateWebAuthnSignature( if (!(dataToSign instanceof Uint8Array)) { dataToSign = new TextEncoder().encode(dataToSign as string); } - const challenge = generateRandomBytes(32); const options = { - challenge: challenge, + challenge: dataToSign, allowCredentials: [{ id: credentialId, type: "public-key" }], userVerification: "preferred", }; const assertion = await navigator.credentials.get({ publicKey: options }); + console.log("assertion", assertion); const authenticatorAssertionResponse = assertion?.response; + console.log( + "clientDataJSON decoded", + JSON.parse( + new TextDecoder("utf-8").decode( + authenticatorAssertionResponse.clientDataJSON, + ), + ), + ); return { signature: authenticatorAssertionResponse.signature, clientDataJSON: authenticatorAssertionResponse.clientDataJSON, @@ -170,15 +212,55 @@ async function generateWebAuthnSignature( export async function verifyJwt( jwt: string, - issuerDid: string, - publicKey: JWK, + issuerDid: string, // eslint-disable-line @typescript-eslint/no-unused-vars + publicKey: JWK, // eslint-disable-line @typescript-eslint/no-unused-vars ) { const decoded = verifyJWT(jwt, { - didAuthenticator: { - authenticators: [{ publicKeyJwk: publicKey }], - issuer: issuerDid, - }, - resolver: getResolver(), + // didAuthenticator: { + // authenticators: [{ publicKeyJwk: publicKey }], + // issuer: issuerDid, + // }, + //resolver: new Resolver({ ...getResolver() }), + resolver: new Resolver({ peer: peerDidToDidDocument }), }); return decoded; } + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +async function peerDidToDidDocument(did: string): Promise { + if (!did.startsWith("did:peer:0z")) { + throw new Error( + "This only verifies a peer DID method 0 base58btc encoded.", + ); + } + // this is basically hard-coded based on the results from the @aviarytech/did-peer resolver + const id = did.split(":")[2]; + const multibase = id.slice(1); + const encnumbasis = multibase.slice(1); + const didDocument = { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/jws-2020/v1", + ], + assertionMethod: ["did:peer:" + id + "#" + encnumbasis], + authentication: ["did:peer:" + id + "#" + encnumbasis], + capabilityDelegation: ["did:peer:" + id + "#" + encnumbasis], + capabilityInvocation: ["did:peer:" + id + "#" + encnumbasis], + id: did, + keyAgreement: undefined, + service: undefined, + verificationMethod: [ + { + id: "did:peer:" + id + "#" + encnumbasis, + type: "EcdsaSecp256k1VerificationKey2019", + controller: did, + publicKeyMultibase: multibase, + }, + ], + }; + return { + didDocument, + didDocumentMetadata: {}, + didResolutionMetadata: { contentType: "application/did+ld+json" }, + }; +}