From 8cf8b9f837279c317216e2269fa603997b30a6ea Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Wed, 12 Jun 2024 20:26:26 -0600 Subject: [PATCH] finally get something other that simplewebauthn working --- package-lock.json | 61 +++++--- package.json | 6 +- src/libs/crypto/passkeyHelpers.ts | 102 ++++++++++++ src/libs/didPeer.ts | 251 ++++++++++++++++++++++++++---- src/views/TestView.vue | 8 +- 5 files changed, 365 insertions(+), 63 deletions(-) create mode 100644 src/libs/crypto/passkeyHelpers.ts diff --git a/package-lock.json b/package-lock.json index d06eb6b..80d8e59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,12 @@ "@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/vue-fontawesome": "^3.0.6", + "@peculiar/asn1-ecc": "^2.3.8", + "@peculiar/asn1-schema": "^2.3.8", "@pvermeer/dexie-encrypted-addon": "^3.0.0", "@simplewebauthn/browser": "^10.0.0", "@simplewebauthn/server": "^10.0.0", "@tweenjs/tween.js": "^21.1.1", - "@types/js-yaml": "^4.0.9", - "@types/luxon": "^3.4.2", "@veramo/core": "^5.6.0", "@veramo/credential-w3c": "^5.6.0", "@veramo/data-store": "^5.6.0", @@ -69,7 +69,9 @@ "web-did-resolver": "^2.0.27" }, "devDependencies": { + "@types/js-yaml": "^4.0.9", "@types/leaflet": "^1.9.8", + "@types/luxon": "^3.4.2", "@types/ramda": "^0.29.11", "@types/three": "^0.155.1", "@types/ua-parser-js": "^0.7.39", @@ -6009,21 +6011,11 @@ } }, "node_modules/@noble/curves": { - "version": "1.2.0", - "license": "MIT", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "engines": { - "node": ">= 16" + "@noble/hashes": "1.4.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -6106,7 +6098,8 @@ }, "node_modules/@peculiar/asn1-ecc": { "version": "2.3.8", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.8.tgz", + "integrity": "sha512-Ah/Q15y3A/CtxbPibiLM/LKcMbnLTdUdLHUgdpB5f60sSvGkXzxJCu5ezGTFHogZXWNX3KSmYqilCrfdmBc6pQ==", "dependencies": { "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", @@ -6126,7 +6119,8 @@ }, "node_modules/@peculiar/asn1-schema": { "version": "2.3.8", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz", + "integrity": "sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==", "dependencies": { "asn1js": "^3.0.5", "pvtsutils": "^1.3.5", @@ -8752,6 +8746,7 @@ }, "node_modules/@types/js-yaml": { "version": "4.0.9", + "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -8768,6 +8763,7 @@ }, "node_modules/@types/luxon": { "version": "3.4.2", + "dev": true, "license": "MIT" }, "node_modules/@types/node": { @@ -10601,11 +10597,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "devOptional": true, - "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -12741,6 +12738,17 @@ "node": ">=14.0.0" } }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/ethers/node_modules/@noble/hashes": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", @@ -13455,9 +13463,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "devOptional": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -14614,8 +14623,9 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "devOptional": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -20676,8 +20686,9 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "devOptional": true, - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, diff --git a/package.json b/package.json index 7f206ac..3a91ea7 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,12 @@ "@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/vue-fontawesome": "^3.0.6", + "@peculiar/asn1-ecc": "^2.3.8", + "@peculiar/asn1-schema": "^2.3.8", "@pvermeer/dexie-encrypted-addon": "^3.0.0", "@simplewebauthn/browser": "^10.0.0", "@simplewebauthn/server": "^10.0.0", "@tweenjs/tween.js": "^21.1.1", - "@types/js-yaml": "^4.0.9", - "@types/luxon": "^3.4.2", "@veramo/core": "^5.6.0", "@veramo/credential-w3c": "^5.6.0", "@veramo/data-store": "^5.6.0", @@ -71,7 +71,9 @@ "web-did-resolver": "^2.0.27" }, "devDependencies": { + "@types/js-yaml": "^4.0.9", "@types/leaflet": "^1.9.8", + "@types/luxon": "^3.4.2", "@types/ramda": "^0.29.11", "@types/three": "^0.155.1", "@types/ua-parser-js": "^0.7.39", diff --git a/src/libs/crypto/passkeyHelpers.ts b/src/libs/crypto/passkeyHelpers.ts new file mode 100644 index 0000000..d2e3481 --- /dev/null +++ b/src/libs/crypto/passkeyHelpers.ts @@ -0,0 +1,102 @@ +// from https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/helpers/iso/isoCrypto/unwrapEC2Signature.ts +import { AsnParser } from "@peculiar/asn1-schema"; +import { ECDSASigValue } from "@peculiar/asn1-ecc"; + +/** + * In WebAuthn, EC2 signatures are wrapped in ASN.1 structure so we need to peel r and s apart. + * + * See https://www.w3.org/TR/webauthn-2/#sctn-signature-attestation-types + */ +export function unwrapEC2Signature(signature: Uint8Array): Uint8Array { + const parsedSignature = AsnParser.parse(signature, ECDSASigValue); + let rBytes = new Uint8Array(parsedSignature.r); + let sBytes = new Uint8Array(parsedSignature.s); + + if (shouldRemoveLeadingZero(rBytes)) { + rBytes = rBytes.slice(1); + } + + if (shouldRemoveLeadingZero(sBytes)) { + sBytes = sBytes.slice(1); + } + + const finalSignature = isoUint8ArrayConcat([rBytes, sBytes]); + + return finalSignature; +} + +/** + * Determine if the DER-specific `00` byte at the start of an ECDSA signature byte sequence + * should be removed based on the following logic: + * + * "If the leading byte is 0x0, and the the high order bit on the second byte is not set to 0, + * then remove the leading 0x0 byte" + */ +function shouldRemoveLeadingZero(bytes: Uint8Array): boolean { + return bytes[0] === 0x0 && (bytes[1] & (1 << 7)) !== 0; +} + +// from https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/helpers/iso/isoUint8Array.ts#L49 +/** + * Combine multiple Uint8Arrays into a single Uint8Array + */ +export function isoUint8ArrayConcat(arrays: Uint8Array[]): Uint8Array { + let pointer = 0; + const totalLength = arrays.reduce((prev, curr) => prev + curr.length, 0); + + const toReturn = new Uint8Array(totalLength); + + arrays.forEach((arr) => { + toReturn.set(arr, pointer); + pointer += arr.length; + }); + + return toReturn; +} + +// from https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.ts +let webCrypto: unknown = undefined; +export function getWebCrypto() { + /** + * 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 + * synchronous. For example, `generateRegistrationOptions()` and `generateAuthenticationOptions()` + * become synchronous if we make this synchronous (since nothing else in that method is async) + * which represents a breaking API change in this library's core API. + * + * 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()); + }); + return toResolve; +} +export class MissingWebCrypto extends Error { + constructor() { + const message = "An instance of the Crypto API could not be located"; + super(message); + this.name = "MissingWebCrypto"; + } +} +// Make it possible to stub return values during testing +export const _getWebCryptoInternals = { + stubThisGlobalThisCrypto: () => globalThis.crypto, + // Make it possible to reset the `webCrypto` at the top of the file + setCachedCrypto: (newCrypto: unknown) => { + webCrypto = newCrypto; + }, +}; \ No newline at end of file diff --git a/src/libs/didPeer.ts b/src/libs/didPeer.ts index 6ac1137..3f4ee27 100644 --- a/src/libs/didPeer.ts +++ b/src/libs/didPeer.ts @@ -1,8 +1,8 @@ import asn1 from "asn1-ber"; import { Buffer } from "buffer/"; import { decode as cborDecode } from "cbor-x"; -import { createJWS, JWTPayload, verifyJWT } from "did-jwt"; -import { DIDResolutionResult, Resolver } from "did-resolver"; +import { createJWS, JWTPayload } from "did-jwt"; +import { DIDResolutionResult } from "did-resolver"; import { sha256 } from "ethereum-cryptography/sha256.js"; import { bytesToMultibase } from "@veramo/utils"; import { @@ -24,6 +24,7 @@ import { } from "@simplewebauthn/types"; import { generateRandomBytes } from "@/libs/crypto"; +import { getWebCrypto, unwrapEC2Signature } from "@/libs/crypto/passkeyHelpers"; export interface JWK { kty: string; @@ -77,12 +78,24 @@ export async function registerCredential(userId: Uint8Array) { expectedRPID: window.location.hostname, }); console.log("verification", verification); + const jwkObj = cborDecode( + verification.registrationInfo?.credentialPublicKey as Uint8Array, + ); + console.log("jwkObj from verification", jwkObj); + console.log( + "[1]==2 => kty EC", + "[3]==-7 => alg ES256", + "[-1]==1 => crv P-256", + ); + const { publicKeyJwk } = cborToKeys( + verification.registrationInfo?.credentialPublicKey as Uint8Array, + ); return { authData: verification.registrationInfo?.attestationObject, credId: verification.registrationInfo?.credentialID as string, rawId: new Uint8Array(new Buffer(attResp.rawId, "base64")), - publicKeyJwk: undefined, + publicKeyJwk: publicKeyJwk, publicKeyBytes: verification.registrationInfo ?.credentialPublicKey as Uint8Array, }; @@ -153,30 +166,9 @@ export async function registerCredential2(userId: Uint8Array) { ); console.log("attestationObject", attestationObject); - const jwkObj = cborDecode( + const { publicKeyJwk, publicKeyBuffer } = cborToKeys( verification.registrationInfo?.credentialPublicKey as Uint8Array, ); - console.log("jwkObj from verification", jwkObj); - if ( - jwkObj[1] != 2 || // kty "EC" - jwkObj[3] != -7 || // alg "ES256" - jwkObj[-1] != 1 || // crv "P-256" - jwkObj[-2].length != 32 || // x - jwkObj[-3].length != 32 // y - ) { - throw new Error("Unable to extract key."); - } - const publicKeyJwk = { - alg: "ES256", - crv: "P-256", - kty: "EC", - x: arrayToBase64Url(jwkObj[-2]), - y: arrayToBase64Url(jwkObj[-3]), - }; - const publicKeyBytes = Buffer.concat([ - Buffer.from(jwkObj[-2]), - Buffer.from(jwkObj[-3]), - ]); //const publicKeyBytes = extractPublicKeyCose(attestationObject.authData); //const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData); @@ -186,7 +178,7 @@ export async function registerCredential2(userId: Uint8Array) { credId: credential?.id, rawId: credential?.rawId, publicKeyJwk, - publicKeyBytes, + publicKeyBytes: publicKeyBuffer, }; } @@ -208,9 +200,12 @@ export function createPeerDid(publicKeyBytes: Uint8Array) { export class PeerSetup { public authenticatorData?: ArrayBuffer; + public authenticatorDataBase64Url?: Base64URLString; public challenge?: Uint8Array; public clientDataJsonDecoded?: object; + public clientDataJsonBase64Url?: Base64URLString; public signature?: Base64URLString; + public publicKeyJwk?: JWK; public async createJwt( payload: object, @@ -244,16 +239,23 @@ export class PeerSetup { const clientAuth = await startAuthentication(options); console.log("custom clientAuth", clientAuth); + this.authenticatorDataBase64Url = clientAuth.response.authenticatorData; this.authenticatorData = Buffer.from( clientAuth.response.authenticatorData, "base64", ).buffer; this.challenge = payloadHash; + this.clientDataJsonBase64Url = clientAuth.response.clientDataJSON; this.clientDataJsonDecoded = JSON.parse( Buffer.from(clientAuth.response.clientDataJSON, "base64").toString( "utf-8", ), ); + console.log("authenticatorData for signing", this.authenticatorData); + console.log( + "clientDataJSON for signing", + Buffer.from(clientAuth.response.clientDataJSON, "base64"), + ); this.signature = clientAuth.response.signature; const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64"); @@ -373,9 +375,12 @@ export async function verifyJwt( credId: Base64URLString, rawId: Uint8Array, authenticatorData: ArrayBuffer, + authenticatorDataBase64Url: Base64URLString, challenge: Uint8Array, clientDataJSON: object, - publicKey: Uint8Array, + clientDataJsonBase64Url: Base64URLString, + publicKeyBytes: Uint8Array, + publicKeyJwk: JWK, signature: Base64URLString, ) { // Here's a combined auth & verify process, based on some of the inputs. @@ -397,7 +402,7 @@ export async function verifyJwt( // const verfOpts: VerifyAuthenticationResponseOpts = { // authenticator: { // credentialID: credId, - // credentialPublicKey: publicKey, + // credentialPublicKey: publicKeyBytes, // counter: 0, // }, // expectedChallenge: options.challenge, @@ -413,7 +418,7 @@ export async function verifyJwt( const authOpts: VerifyAuthenticationResponseOpts = { authenticator: { credentialID: credId, - credentialPublicKey: publicKey, + credentialPublicKey: publicKeyBytes, counter: 0, }, expectedChallenge: arrayToBase64Url(challenge), @@ -438,10 +443,107 @@ export async function verifyJwt( const verification = await verifyAuthenticationResponse(authOpts); console.log("auth verification", verification); - const decoded = verifyJWT(jwt, { - resolver: new Resolver({ peer: peerDidToDidDocument }), - }); - return decoded; + // It doesn't work to use the did-jwt verifyJWT with did-resolver Resolver + // const decoded = verifyJWT(jwt, { + // resolver: new Resolver({ peer: peerDidToDidDocument }), + // }); + // return decoded; + + // const [headerB64, concatenatedPayloadB64, signatureB64] = jwt.split("."); + // + // const header = JSON.parse(atob(headerB64)); + // const jsonPayload = JSON.parse(atob(concatenatedPayloadB64)); + // const [jsonPayloadB64, otherDataHashB64] = concatenatedPayloadB64.split("."); + // + // const otherDataHash = base64urlDecode(otherDataHashB64); + // const signature = base64urlDecode(signatureB64); + // + // const dataToVerify = new TextEncoder().encode(`${headerB64}.${concatenatedPayloadB64}`); + // const dataHash = await sha256(dataToVerify); + // + // const authenticatorData = base64urlDecode(jsonPayload.authenticatorData); + // const clientDataJSON = jsonPayload.clientDataJSON; + + ///////// + + // const clientBuffer = clientDataJsonBase64Url + // .replace(/-/g, "+") + // .replace(/_/g, "/"); + // const clientData = new Uint8Array(Buffer.from(clientBuffer, "base64")); + // const clientDataHash = sha256(clientData); + // + // const verifyData = new Uint8Array([ + // ...new Uint8Array(authenticatorData), + // ...new Uint8Array(clientDataHash), + // ]); + // console.log("verifyData", verifyData); + // const sigBase64Raw = signature.replace(/-/g, "+").replace(/_/g, "/"); + // + // const sigHex = new Uint8Array(Buffer.from(sigBase64Raw, "base64")); //Buffer.from(sigBase64Raw, "base64").toString("hex"); + // const msgHex = verifyData; //new Buffer(verifyData).toString("hex"); + // const pubHex = publicKeyBytes; //new Buffer(publicKeyBytes).toString("hex"); + // console.log("sig msg pub", sigHex, msgHex, pubHex); + // const isValid = p256.verify(sigHex, msgHex, pubHex); + + ///////// + + const authDataFromBase = Buffer.from(authenticatorDataBase64Url, "base64"); + const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64"); + const sigBuffer = Buffer.from(signature, "base64"); + const finalSigBuffer = unwrapEC2Signature(sigBuffer); + + // Hash the client data + const hash = sha256(clientDataFromBase); + + // Construct the preimage + const preimage = Buffer.concat([authDataFromBase, hash]); + + console.log("finalSigBuffer", finalSigBuffer); + console.log("preimage", preimage); + console.log("publicKeyBytes", publicKeyBytes); + + // This uses p256 from @noble/curves/p256, which I would prefer but it's returning false. + // const isValid = p256.verify( + // finalSigBuffer, + // new Uint8Array(preimage), + // publicKeyBytes, + // ); + // console.log("isValid", isValid); + + ///////// + + const WebCrypto = await getWebCrypto(); + const verifyAlgorithm = { + name: "ECDSA", + hash: { name: "SHA-256" }, + }; + const keyAlgorithm = { + name: "ECDSA", + namedCurve: publicKeyJwk.crv, + }; + // const publicKeyCryptoKey = await importKey({ + // publicKeyJwk, + // algorithm: keyAlgorithm, + // }); + const publicKeyCryptoKey = await WebCrypto.subtle.importKey( + "jwk", + publicKeyJwk, + keyAlgorithm, + false, + ["verify"], + ); + console.log("verifyAlgorithm", verifyAlgorithm); + console.log("publicKeyCryptoKey", publicKeyCryptoKey); + console.log("finalSigBuffer", finalSigBuffer); + console.log("preimage", preimage); + const verified = await WebCrypto.subtle.verify( + verifyAlgorithm, + publicKeyCryptoKey, + finalSigBuffer, + preimage, + ); + console.log("verified", verified); + return verified; } async function peerDidToDidDocument(did: string): Promise { @@ -481,3 +583,86 @@ async function peerDidToDidDocument(did: string): Promise { didResolutionMetadata: { contentType: "application/did+ld+json" }, }; } + +// convert COSE public key to PEM format +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 + const pubKeyBuffer = Buffer.concat([Buffer.from([0x04]), x, y]); + + // Convert to PEM format + const pem = `-----BEGIN PUBLIC KEY----- +${pubKeyBuffer.toString("base64")} +-----END PUBLIC KEY-----`; + + return pem; +} + +function base64urlDecode(input: string) { + input = input.replace(/-/g, "+").replace(/_/g, "/"); + const pad = input.length % 4 === 0 ? "" : "====".slice(input.length % 4); + const str = atob(input + pad); + const bytes = new Uint8Array(str.length); + for (let i = 0; i < str.length; i++) { + bytes[i] = str.charCodeAt(i); + } + return bytes.buffer; +} + +function base64urlEncode(buffer: ArrayBuffer) { + const str = String.fromCharCode(...new Uint8Array(buffer)); + return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); +} + +function cborToKeys(publicKeyBytes: Uint8Array) { + const jwkObj = cborDecode(publicKeyBytes); + console.log("jwkObj from verification", jwkObj); + if ( + jwkObj[1] != 2 || // kty "EC" + jwkObj[3] != -7 || // alg "ES256" + jwkObj[-1] != 1 || // crv "P-256" + jwkObj[-2].length != 32 || // x + jwkObj[-3].length != 32 // y + ) { + throw new Error("Unable to extract key."); + } + const publicKeyJwk = { + alg: "ES256", + crv: "P-256", + kty: "EC", + x: arrayToBase64Url(jwkObj[-2]), + y: arrayToBase64Url(jwkObj[-3]), + }; + const publicKeyBuffer = Buffer.concat([ + Buffer.from(jwkObj[-2]), + Buffer.from(jwkObj[-3]), + ]); + return { publicKeyJwk, publicKeyBuffer }; +} + +async function pemToCryptoKey(pem: string) { + const binaryDerString = atob( + pem + .split("\n") + .filter((x) => !x.includes("-----")) + .join(""), + ); + const binaryDer = new Uint8Array(binaryDerString.length); + for (let i = 0; i < binaryDerString.length; i++) { + binaryDer[i] = binaryDerString.charCodeAt(i); + } + console.log("binaryDer", binaryDer.buffer); + return await window.crypto.subtle.importKey( + "spki", + binaryDer.buffer, + { + name: "RSASSA-PKCS1-v1_5", + hash: "SHA-256", + }, + true, + ["verify"], + ); +} diff --git a/src/views/TestView.vue b/src/views/TestView.vue index a5cae98..82f51bd 100644 --- a/src/views/TestView.vue +++ b/src/views/TestView.vue @@ -265,9 +265,7 @@ export default class Help extends Vue { const encodedUserId = Buffer.from(this.userId).toString("base64"); console.log("encodedUserId", encodedUserId); - const cred = await registerCredential( - this.userId as Uint8Array - ); + const cred = await registerCredential(this.userId as Uint8Array); console.log("public key", cred); this.publicKeyJwk = cred.publicKeyJwk; this.publicKeyBytes = cred.publicKeyBytes; @@ -277,6 +275,7 @@ export default class Help extends Vue { } public async create() { + console.log("Starting a create"); const did = createPeerDid(this.publicKeyBytes as Uint8Array); console.log("did", did); this.peerSetup = new PeerSetup(); @@ -303,9 +302,12 @@ export default class Help extends Vue { this.credId as Base64URLString, this.rawId as Uint8Array, this.peerSetup.authenticatorData as ArrayBuffer, + this.peerSetup.authenticatorDataBase64Url as Base64URLString, this.peerSetup.challenge as Uint8Array, this.peerSetup.clientDataJsonDecoded, + this.peerSetup.clientDataJsonBase64Url as Base64URLString, this.publicKeyBytes as Uint8Array, + this.publicKeyJwk as JWK, this.peerSetup.signature as Base64URLString, ); console.log("decoded", decoded);