diff --git a/src/libs/crypto/passkeyHelpers.ts b/src/libs/crypto/passkeyHelpers.ts index d2e3481..5e3daab 100644 --- a/src/libs/crypto/passkeyHelpers.ts +++ b/src/libs/crypto/passkeyHelpers.ts @@ -55,8 +55,8 @@ export function isoUint8ArrayConcat(arrays: Uint8Array[]): Uint8Array { } // from https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.ts -let webCrypto: unknown = undefined; -export function getWebCrypto() { +let webCrypto: { subtle: SubtleCrypto } | undefined = undefined; +export function getWebCrypto(): Promise<{ subtle: SubtleCrypto }> { /** * Hello there! If you came here wondering why this method is asynchronous when use of * `globalThis.crypto` is not, it's to minimize a bunch of refactor related to making this @@ -67,22 +67,25 @@ export function getWebCrypto() { * TODO: If it's after February 2025 when you read this then consider whether it still makes sense * to keep this method asynchronous. */ - const toResolve = new Promise((resolve, reject) => { - if (webCrypto) { - return resolve(webCrypto); - } - /** - * Naively attempt to access Crypto as a global object, which popular ESM-centric run-times - * support (and Node v20+) - */ - const _globalThisCrypto = _getWebCryptoInternals.stubThisGlobalThisCrypto(); - if (_globalThisCrypto) { - webCrypto = _globalThisCrypto; - return resolve(webCrypto); - } - // We tried to access it both in Node and globally, so bail out - return reject(new MissingWebCrypto()); - }); + const toResolve: Promise<{ subtle: SubtleCrypto }> = new Promise( + (resolve, reject) => { + if (webCrypto) { + return resolve(webCrypto); + } + /** + * Naively attempt to access Crypto as a global object, which popular ESM-centric run-times + * support (and Node v20+) + */ + const _globalThisCrypto = + _getWebCryptoInternals.stubThisGlobalThisCrypto(); + if (_globalThisCrypto) { + webCrypto = _globalThisCrypto; + return resolve(webCrypto); + } + // We tried to access it both in Node and globally, so bail out + return reject(new MissingWebCrypto()); + }, + ); return toResolve; } export class MissingWebCrypto extends Error { @@ -96,7 +99,7 @@ export class MissingWebCrypto extends Error { export const _getWebCryptoInternals = { stubThisGlobalThisCrypto: () => globalThis.crypto, // Make it possible to reset the `webCrypto` at the top of the file - setCachedCrypto: (newCrypto: unknown) => { + setCachedCrypto: (newCrypto: { subtle: SubtleCrypto }) => { webCrypto = newCrypto; }, -}; \ No newline at end of file +}; diff --git a/src/libs/didPeer.ts b/src/libs/didPeer.ts index 3e259a6..93a5403 100644 --- a/src/libs/didPeer.ts +++ b/src/libs/didPeer.ts @@ -74,7 +74,7 @@ export async function registerCredential(passkeyName?: string) { const credIdBase64Url = verification.registrationInfo?.credentialID as string; if (attResp.rawId !== credIdBase64Url) { - console.log("Warning! The raw ID does not match the credential ID.") + console.log("Warning! The raw ID does not match the credential ID."); } const credIdHex = Buffer.from( base64URLStringToArrayBuffer(credIdBase64Url), @@ -237,8 +237,9 @@ export class PeerSetup { .replace(/\//g, "_") .replace(/=+$/, ""); - const origSignature = Buffer.from(credential?.response.signature) - .toString("base64") + const origSignature = Buffer.from(credential?.response.signature).toString( + "base64", + ); this.signature = origSignature .replace(/\+/g, "-") .replace(/\//g, "_") @@ -423,6 +424,7 @@ export async function verifyJwtWebCrypto( return verified; } +// eslint-disable-next-line @typescript-eslint/no-unused-vars async function peerDidToDidDocument(did: string): Promise { if (!did.startsWith("did:peer:0z")) { throw new Error( @@ -463,12 +465,15 @@ async function peerDidToDidDocument(did: string): Promise { } // convert COSE public key to PEM format +// eslint-disable-next-line @typescript-eslint/no-unused-vars function COSEtoPEM(cose: Buffer) { // const alg = cose.get(3); // Algorithm const x = cose[-2]; // x-coordinate const y = cose[-3]; // y-coordinate // Ensure the coordinates are in the correct format + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error because it complains about the type of x and y const pubKeyBuffer = Buffer.concat([Buffer.from([0x04]), x, y]); // Convert to PEM format @@ -479,6 +484,7 @@ ${pubKeyBuffer.toString("base64")} return pem; } +// eslint-disable-next-line @typescript-eslint/no-unused-vars function base64urlDecode(input: string) { input = input.replace(/-/g, "+").replace(/_/g, "/"); const pad = input.length % 4 === 0 ? "" : "====".slice(input.length % 4); @@ -490,13 +496,14 @@ function base64urlDecode(input: string) { return bytes.buffer; } +// eslint-disable-next-line @typescript-eslint/no-unused-vars function base64urlEncode(buffer: ArrayBuffer) { const str = String.fromCharCode(...new Uint8Array(buffer)); return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } // from @simplewebauthn/browser -function arrayBufferToBase64URLString(buffer) { +function arrayBufferToBase64URLString(buffer: ArrayBuffer) { const bytes = new Uint8Array(buffer); let str = ""; for (const charCode of bytes) { @@ -545,6 +552,7 @@ function cborToKeys(publicKeyBytes: Uint8Array) { return { publicKeyJwk, publicKeyBuffer }; } +// eslint-disable-next-line @typescript-eslint/no-unused-vars async function pemToCryptoKey(pem: string) { const binaryDerString = atob( pem diff --git a/src/views/ImportDerivedAccountView.vue b/src/views/ImportDerivedAccountView.vue index 09dbb51..b3b2157 100644 --- a/src/views/ImportDerivedAccountView.vue +++ b/src/views/ImportDerivedAccountView.vue @@ -17,7 +17,7 @@

- Will increment the maximum derivation path from the existing seed. + Will increment the maximum known derivation path from the existing seed.

@@ -75,7 +75,7 @@ import { deriveAddress, newIdentifier, nextDerivationPath, -} from "../libs/crypto"; +} from "@/libs/crypto"; import { accountsDB, db } from "@/db/index"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; diff --git a/src/views/TestView.vue b/src/views/TestView.vue index dc6a40c..aae1918 100644 --- a/src/views/TestView.vue +++ b/src/views/TestView.vue @@ -173,59 +173,65 @@

Passkeys

See console for results. -
+
+ See existing passkeys in Chrome at: chrome://settings/passkeys +
Active DID: {{ activeDid }} {{ credIdHex ? "has passkey ID" : "has no passkey ID" }} +
- Register + Register Passkey
+
- Create + Create JWT
+
- Verify + Verify New JWT
+
Verify New JWT -- requires creation first
@@ -335,7 +341,7 @@ export default class Help extends Vue { did: this.activeDid, passkeyCredIdHex: this.credIdHex, publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"), - });`` + }); } public async createJwtSimplewebauthn() { @@ -360,44 +366,46 @@ export default class Help extends Vue { public async verifyP256() { const decoded = await verifyJwtP256( - this.credIdHex as Base64URLString, + this.credIdHex as string, this.activeDid as string, - this.peerSetup.authenticatorData as ArrayBuffer, - this.peerSetup.challenge as Uint8Array, - this.peerSetup.clientDataJsonBase64Url as Base64URLString, - this.peerSetup.signature as Base64URLString, + this.peerSetup?.authenticatorData as ArrayBuffer, + this.peerSetup?.challenge as Uint8Array, + this.peerSetup?.clientDataJsonBase64Url as Base64URLString, + this.peerSetup?.signature as Base64URLString, ); console.log("decoded", decoded); } public async verifySimplewebauthn() { const decoded = await verifyJwtSimplewebauthn( - this.credIdHex as Base64URLString, + this.credIdHex as string, this.activeDid as string, - this.peerSetup.authenticatorData as ArrayBuffer, - this.peerSetup.challenge as Uint8Array, - this.peerSetup.clientDataJsonBase64Url as Base64URLString, - this.peerSetup.signature as Base64URLString, + this.peerSetup?.authenticatorData as ArrayBuffer, + this.peerSetup?.challenge as Uint8Array, + this.peerSetup?.clientDataJsonBase64Url as Base64URLString, + this.peerSetup?.signature as Base64URLString, ); console.log("decoded", decoded); } public async verifyWebCrypto() { const decoded = await verifyJwtWebCrypto( - this.credIdHex as Base64URLString, + this.credIdHex as string, this.activeDid as string, - this.peerSetup.authenticatorData as ArrayBuffer, - this.peerSetup.challenge as Uint8Array, - this.peerSetup.clientDataJsonBase64Url as Base64URLString, - this.peerSetup.signature as Base64URLString, + this.peerSetup?.authenticatorData as ArrayBuffer, + this.peerSetup?.challenge as Uint8Array, + this.peerSetup?.clientDataJsonBase64Url as Base64URLString, + this.peerSetup?.signature as Base64URLString, ); console.log("decoded", decoded); } + public async verifyMyJwt() { + const did = + "did:peer:0zKMFjvUgYrM1hXwDciYHiA9MxXtJPXnRLJvqoMNAKoDLX9pKMWLb3VDsgua1p2zW1xXRsjZSTNsfvMnNyMS7dB4k7NAhFwL3pXBrBXgyYJ9ri"; const jwt = "eyJ0eXAiOiJKV0FOVCIsImFsZyI6IkVTMjU2In0.eyJBdXRoZW50aWNhdGlvbkRhdGFCNjRVUkwiOiJTWllONVlnT2pHaDBOQmNQWkhaZ1c0X2tycm1paGpMSG1Wenp1b01kbDJNRkFBQUFBQSIsIkNsaWVudERhdGFKU09OQjY0VVJMIjoiZXlKMGVYQmxJam9pZDJWaVlYVjBhRzR1WjJWMElpd2lZMmhoYkd4bGJtZGxJam9pWlhsS01sbDVTVFpsZVVwcVkyMVdhMXBYTlRCaFYwWnpWVE5XYVdGdFZtcGtRMGsyWlhsS1FWa3lPWFZrUjFZMFpFTkpOa2x0YURCa1NFSjZUMms0ZG1NeVRtOWFWekZvVEcwNWVWcDVTWE5KYTBJd1pWaENiRWxxYjJsU01td3lXbFZHYW1SSGJIWmlhVWx6U1cxU2JHTXlUbmxoV0VJd1lWYzVkVWxxYjJsalIydzJaVzFGYVdaWU1ITkpiV3hvWkVOSk5rMVVZM2hQUkZVMFRtcHJOVTFEZDJsaFdFNTZTV3B2YVZwSGJHdFBia0pzV2xoSk5rMUljRXhVVlZweFpHeFdibGRZU2s1TlYyaFpaREJTYW1GV2JFbGhWVVUxVkZob1dXUkZjRkZYUnpWVFZFVndNbU5YT1U1VWEwWk1ZakJTVFZkRWJIZFRNREZZVkVkSmVsWnJVbnBhTTFab1RWaEJlV1ZzWTNobFJtaFRZekp3WVZVeFVrOWpNbG95VkZjMVQyVlZNVlJPTWxKRFRrZHpNMVJyUm05U2JtUk5UVE5DV1ZGdVNrTlhSMlExVjFWdk5XTnRhMmxtVVNJc0ltOXlhV2RwYmlJNkltaDBkSEE2THk5c2IyTmhiR2h2YzNRNk9EQTRNQ0lzSW1OeWIzTnpUM0pwWjJsdUlqcG1ZV3h6WlgwIiwiaWF0IjoxNzE4NTg2OTkyLCJpc3MiOiJkaWQ6cGVlcjowektNRmp2VWdZck0xaFh3RGNpWUhpQTlNeFh0SlBYblJMSnZxb01OQUtvRExYOXBLTVdMYjNWRHNndWExcDJ6VzF4WFJzalpTVE5zZnZNbk55TVM3ZEI0azdOQWhGd0wzcFhCckJYZ3lZSjlyaSJ9.MEUCIQDJyCTbMPIFnuBoW3FYnlgtDEIHZ2OrkCEvqVnHU7kJDQIgVxjBjfW1TwQfcSOYwK8Z7AdCWGJlyxtLEsrnPif7caE"; const pieces = jwt.split("."); - console.log("pieces", typeof pieces[1], pieces); const payload = JSON.parse(Buffer.from(pieces[1], "base64").toString()); const authData = Buffer.from(payload["AuthenticationDataB64URL"], "base64"); const clientJSON = Buffer.from( @@ -408,8 +416,8 @@ export default class Help extends Vue { const challenge = clientData.challenge; const signatureB64URL = pieces[2]; const decoded = await verifyJwtWebCrypto( - this.credIdHex as Base64URLString, - this.activeDid as string, + this.credIdHex as string, + did, authData, challenge, payload["ClientDataJSONB64URL"],