From 66aeb7ed29665875c60ba0e8543576b23626ac62 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Fri, 24 May 2024 20:16:34 -0600 Subject: [PATCH] retrieve the correct passkey just created (doesn't validate JWT) --- package-lock.json | 134 +++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/libs/crypto/index.ts | 4 ++ src/libs/didPeer.ts | 115 +++++++++++++++++++++------------ src/main.ts | 2 +- src/views/StartView.vue | 37 ++++++++--- 6 files changed, 240 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index 58f38d7..2588e33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@veramo/data-store": "^5.6.0", "@veramo/did-manager": "^5.6.0", "@veramo/did-provider-ethr": "^5.6.0", + "@veramo/did-provider-peer": "^6.0.0", "@veramo/did-resolver": "^5.6.0", "@veramo/key-manager": "^5.6.0", "@vueuse/core": "^10.9.0", @@ -123,6 +124,14 @@ "node": ">=6.0.0" } }, + "node_modules/@aviarytech/did-peer": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@aviarytech/did-peer/-/did-peer-0.0.22.tgz", + "integrity": "sha512-BdA7L9wpYNLf1c3d0yB92aoj1AUWE10p408VZ4IJXfavb/oNxALZRRRJTcvMdrd5P2XXQsP5+x4bXfO24iRURg==", + "dependencies": { + "buffer": "^6.0.3" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", @@ -9441,6 +9450,131 @@ "ethr-did": "^3.0.5" } }, + "node_modules/@veramo/did-provider-peer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@veramo/did-provider-peer/-/did-provider-peer-6.0.0.tgz", + "integrity": "sha512-U58pm/KaVtnQ15xLdJxtGDG2QhsPZgFxR0qE6X1S+p/zbI33K12nnUSvts6xrPUzoLXAETjPa1V1MA8SnXrJIg==", + "dependencies": { + "@aviarytech/did-peer": "^0.0.22", + "@veramo/core-types": "^6.0.0", + "@veramo/did-manager": "^6.0.0", + "@veramo/utils": "^6.0.0", + "debug": "^4.3.3", + "did-resolver": "^4.1.0" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/@noble/ciphers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/@veramo/core-types": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@veramo/core-types/-/core-types-6.0.0.tgz", + "integrity": "sha512-Gdjo45veUT2DXhPmhD2hy1X9jem7IuZNjUvPIC5+qRVBT1yvWsJjOL7D+YWhbeA6LHIbNpO1a9GR2iaSL/FebA==", + "dependencies": { + "credential-status": "^3.0.0", + "debug": "^4.3.3", + "did-jwt-vc": "^4.0.0", + "did-resolver": "^4.1.0" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/@veramo/did-discovery": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@veramo/did-discovery/-/did-discovery-6.0.0.tgz", + "integrity": "sha512-S/r0MWAVQZSe9SGb7XfWDhdsLfsSqkzMIweOiLTjZjHndkkDJ+qX3s5gblSNcTTvOvZRsXDI3fa4IbsjhDc1PQ==", + "dependencies": { + "@veramo/core-types": "^6.0.0", + "debug": "^4.3.3" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/@veramo/did-manager": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@veramo/did-manager/-/did-manager-6.0.0.tgz", + "integrity": "sha512-sjF0d0ZE+h7Jw7WwGtyRhjT5MhKBwGLx2AsR2Ao0qqyloVAxVS9jDcl2DP1ZdCKC+7BR4X30E1ja99N7q3INDg==", + "dependencies": { + "@veramo/core-types": "^6.0.0", + "@veramo/did-discovery": "^6.0.0" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/@veramo/utils": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@veramo/utils/-/utils-6.0.0.tgz", + "integrity": "sha512-IvX0QqjLfamfD6s/qUVtfWFtdb1Idd5Dvfm7RzMKSvT+D5cstFQw3v/sySKoEY8du/ruy8ZUe7v9VpCAYlngPQ==", + "dependencies": { + "@ipld/dag-pb": "^4.0.5", + "@noble/curves": "^1.1.0", + "@veramo/core-types": "^6.0.0", + "credential-status": "^3.0.0", + "cross-fetch": "^4.0.0", + "debug": "^4.3.3", + "did-jwt": "^8.0.0", + "did-jwt-vc": "^4.0.0", + "did-resolver": "^4.1.0", + "ethers": "^6.11.1", + "ipfs-unixfs": "^11.1.0", + "multiformats": "^13.0.0", + "uint8arrays": "^4.0.6" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/credential-status": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/credential-status/-/credential-status-3.0.4.tgz", + "integrity": "sha512-6xHMXhdIZjhyTzkXx49hR4CjHNEqFJmWEL29CNulGA0XDozHsZXVF34BZyHGz9vWD/Du05pVEDhpZL0jdLGIRw==", + "dependencies": { + "did-jwt": "^8.0.0", + "did-resolver": "^4.1.0" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/did-jwt": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/did-jwt/-/did-jwt-8.0.4.tgz", + "integrity": "sha512-KPtG7H+8GgKGMiDqFvOdNy5BBN3hpA+8xV7VygEnpst5oPIqjvcH3rTtnPF55a8bOxIzE2PudKGIXIQhekv7WA==", + "dependencies": { + "@noble/ciphers": "^0.5.0", + "@noble/curves": "^1.0.0", + "@noble/hashes": "^1.3.0", + "@scure/base": "^1.1.3", + "canonicalize": "^2.0.0", + "did-resolver": "^4.1.0", + "multibase": "^4.0.6", + "multiformats": "^9.6.2", + "uint8arrays": "3.1.1" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/did-jwt-vc": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/did-jwt-vc/-/did-jwt-vc-4.0.4.tgz", + "integrity": "sha512-O/VSW+pux25+bERGQ1Z+LNv8z8gIy++yL8yjspxNZHCLjdmCelZz3hjuIpHlixhBCFP2YyTOQqGg8QeIMRwN9Q==", + "dependencies": { + "did-jwt": "^8.0.0", + "did-resolver": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/did-jwt/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" + }, + "node_modules/@veramo/did-provider-peer/node_modules/did-jwt/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/@veramo/did-provider-peer/node_modules/multiformats": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.1.0.tgz", + "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==" + }, "node_modules/@veramo/did-resolver": { "version": "5.6.0", "license": "Apache-2.0", diff --git a/package.json b/package.json index 227de5b..2c3ef3e 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@veramo/data-store": "^5.6.0", "@veramo/did-manager": "^5.6.0", "@veramo/did-provider-ethr": "^5.6.0", + "@veramo/did-provider-peer": "^6.0.0", "@veramo/did-resolver": "^5.6.0", "@veramo/key-manager": "^5.6.0", "@vueuse/core": "^10.9.0", diff --git a/src/libs/crypto/index.ts b/src/libs/crypto/index.ts index f8ea248..7afdf2c 100644 --- a/src/libs/crypto/index.ts +++ b/src/libs/crypto/index.ts @@ -64,6 +64,10 @@ export const deriveAddress = ( return [address, privateHex, publicHex, derivationPath]; }; +export const generateRandomBytes = (numBytes: number): Uint8Array => { + return getRandomBytesSync(numBytes); +}; + /** * * diff --git a/src/libs/didPeer.ts b/src/libs/didPeer.ts index 71c054f..faabb73 100644 --- a/src/libs/didPeer.ts +++ b/src/libs/didPeer.ts @@ -1,17 +1,35 @@ +import { Buffer } from "buffer/"; import { decode as cborDecode } from "cbor-x"; -import { createJWS, JWTPayload } from "did-jwt"; +import { createJWS, JWTPayload, verifyJWT } from "did-jwt"; +import { getResolver } from "@veramo/did-provider-peer"; -export async function registerCredential() { +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: new Uint8Array(32), // Random challenge + challenge: challenge, rp: { name: "Time Safari", id: window.location.hostname, }, user: { - id: new Uint8Array(16), // User ID - name: "user@example.com", - displayName: "Example User", + id: userId, + name: "current-user", + displayName: "Current User", }, pubKeyCredParams: [ { @@ -35,19 +53,23 @@ export async function registerCredential() { // Parse the attestation response to get the public key const clientDataJSON = attestationResponse.clientDataJSON; - console.log("clientDataJSON", 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 publicKey; + return { rawId: credential?.rawId, publicKey }; } -// @ts-expect-error just because it doesn't like the "any" -function extractPublicKey(authData) { +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 @@ -56,8 +78,7 @@ function extractPublicKey(authData) { return publicKeyJwk; } -// @ts-expect-error just because it doesn't like the "any" -function coseToJwk(coseKey) { +function coseToJwk(coseKey: Uint8Array) { // Convert COSE key format to JWK // This is simplified and needs appropriate parsing and conversion logic return { @@ -68,29 +89,22 @@ function coseToJwk(coseKey) { }; } -async function generateWebAuthnSignature( - dataToSign: string | Uint8Array, - credentialID: ArrayBuffer, +export async function createJwt( + payload: object, + issuerDid: string, + credentialId: ArrayBuffer, ) { - if (!(dataToSign instanceof Uint8Array)) { - dataToSign = new TextEncoder().encode(dataToSign as string); - } - const challenge = dataToSign; + const signer = await webAuthnES256KSigner(credentialId); - const options = { - challenge: challenge, - allowCredentials: [{ id: credentialID, type: "public-key" }], - userVerification: "preferred", + // from createJWT in did-jwt/src/JWT.ts + const header: JWTPayload = { typ: "JWT", alg: "ES256K" }; + const timestamps: Partial = { + iat: Math.floor(Date.now() / 1000), + exp: undefined, }; + const fullPayload = { ...timestamps, ...payload, iss: issuerDid }; - const assertion = await navigator.credentials.get({ publicKey: options }); - - const authenticatorAssertionResponse = assertion?.response; - return { - signature: authenticatorAssertionResponse.signature, - clientDataJSON: authenticatorAssertionResponse.clientDataJSON, - authenticatorData: authenticatorAssertionResponse.authenticatorData, - }; + return createJWS(fullPayload, signer, header); } async function webAuthnES256KSigner(credentialID: ArrayBuffer) { @@ -111,20 +125,37 @@ async function webAuthnES256KSigner(credentialID: ArrayBuffer) { }; } -export async function createJwt( - payload: object, - issuerDid: string, +async function generateWebAuthnSignature( + dataToSign: string | Uint8Array, credentialId: ArrayBuffer, ) { - const signer = await webAuthnES256KSigner(credentialId); + if (!(dataToSign instanceof Uint8Array)) { + dataToSign = new TextEncoder().encode(dataToSign as string); + } + const challenge = generateRandomBytes(32); - // from createJWT in did-jwt/src/JWT.ts - const header: JWTPayload = { typ: "JWT", alg: "ES256K" }; - const timestamps: Partial = { - iat: Math.floor(Date.now() / 1000), - exp: undefined, + const options = { + challenge: challenge, + allowCredentials: [{ id: credentialId, type: "public-key" }], + userVerification: "preferred", }; - const fullPayload = { ...timestamps, ...payload, iss: issuerDid }; - return createJWS(fullPayload, signer, header); + 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; } diff --git a/src/main.ts b/src/main.ts index bd183f3..edf1f0a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -154,7 +154,7 @@ function setupGlobalErrorHandler(app: VueApp) { info: string, ) => { console.error( - "Global Error Handler. Info:", + "Ouch! Global Error Handler. Info:", info, "Error:", err, diff --git a/src/views/StartView.vue b/src/views/StartView.vue index 7da2040..93057ec 100644 --- a/src/views/StartView.vue +++ b/src/views/StartView.vue @@ -63,14 +63,18 @@