Browse Source

finally get something other that simplewebauthn working

pull/116/head
Trent Larson 6 months ago
parent
commit
8cf8b9f837
  1. 61
      package-lock.json
  2. 6
      package.json
  3. 102
      src/libs/crypto/passkeyHelpers.ts
  4. 251
      src/libs/didPeer.ts
  5. 8
      src/views/TestView.vue

61
package-lock.json

@ -14,12 +14,12 @@
"@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6", "@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", "@pvermeer/dexie-encrypted-addon": "^3.0.0",
"@simplewebauthn/browser": "^10.0.0", "@simplewebauthn/browser": "^10.0.0",
"@simplewebauthn/server": "^10.0.0", "@simplewebauthn/server": "^10.0.0",
"@tweenjs/tween.js": "^21.1.1", "@tweenjs/tween.js": "^21.1.1",
"@types/js-yaml": "^4.0.9",
"@types/luxon": "^3.4.2",
"@veramo/core": "^5.6.0", "@veramo/core": "^5.6.0",
"@veramo/credential-w3c": "^5.6.0", "@veramo/credential-w3c": "^5.6.0",
"@veramo/data-store": "^5.6.0", "@veramo/data-store": "^5.6.0",
@ -69,7 +69,9 @@
"web-did-resolver": "^2.0.27" "web-did-resolver": "^2.0.27"
}, },
"devDependencies": { "devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@types/luxon": "^3.4.2",
"@types/ramda": "^0.29.11", "@types/ramda": "^0.29.11",
"@types/three": "^0.155.1", "@types/three": "^0.155.1",
"@types/ua-parser-js": "^0.7.39", "@types/ua-parser-js": "^0.7.39",
@ -6009,21 +6011,11 @@
} }
}, },
"node_modules/@noble/curves": { "node_modules/@noble/curves": {
"version": "1.2.0", "version": "1.4.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz",
"integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==",
"dependencies": { "dependencies": {
"@noble/hashes": "1.3.2" "@noble/hashes": "1.4.0"
},
"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"
}, },
"funding": { "funding": {
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
@ -6106,7 +6098,8 @@
}, },
"node_modules/@peculiar/asn1-ecc": { "node_modules/@peculiar/asn1-ecc": {
"version": "2.3.8", "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": { "dependencies": {
"@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-schema": "^2.3.8",
"@peculiar/asn1-x509": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8",
@ -6126,7 +6119,8 @@
}, },
"node_modules/@peculiar/asn1-schema": { "node_modules/@peculiar/asn1-schema": {
"version": "2.3.8", "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": { "dependencies": {
"asn1js": "^3.0.5", "asn1js": "^3.0.5",
"pvtsutils": "^1.3.5", "pvtsutils": "^1.3.5",
@ -8752,6 +8746,7 @@
}, },
"node_modules/@types/js-yaml": { "node_modules/@types/js-yaml": {
"version": "4.0.9", "version": "4.0.9",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
@ -8768,6 +8763,7 @@
}, },
"node_modules/@types/luxon": { "node_modules/@types/luxon": {
"version": "3.4.2", "version": "3.4.2",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
@ -10601,11 +10597,12 @@
} }
}, },
"node_modules/braces": { "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, "devOptional": true,
"license": "MIT",
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -12741,6 +12738,17 @@
"node": ">=14.0.0" "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": { "node_modules/ethers/node_modules/@noble/hashes": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
@ -13455,9 +13463,10 @@
} }
}, },
"node_modules/fill-range": { "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, "devOptional": true,
"license": "MIT",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -14614,8 +14623,9 @@
}, },
"node_modules/is-number": { "node_modules/is-number": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"devOptional": true, "devOptional": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@ -20676,8 +20686,9 @@
}, },
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "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, "devOptional": true,
"license": "MIT",
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },

6
package.json

@ -16,12 +16,12 @@
"@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6", "@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", "@pvermeer/dexie-encrypted-addon": "^3.0.0",
"@simplewebauthn/browser": "^10.0.0", "@simplewebauthn/browser": "^10.0.0",
"@simplewebauthn/server": "^10.0.0", "@simplewebauthn/server": "^10.0.0",
"@tweenjs/tween.js": "^21.1.1", "@tweenjs/tween.js": "^21.1.1",
"@types/js-yaml": "^4.0.9",
"@types/luxon": "^3.4.2",
"@veramo/core": "^5.6.0", "@veramo/core": "^5.6.0",
"@veramo/credential-w3c": "^5.6.0", "@veramo/credential-w3c": "^5.6.0",
"@veramo/data-store": "^5.6.0", "@veramo/data-store": "^5.6.0",
@ -71,7 +71,9 @@
"web-did-resolver": "^2.0.27" "web-did-resolver": "^2.0.27"
}, },
"devDependencies": { "devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@types/luxon": "^3.4.2",
"@types/ramda": "^0.29.11", "@types/ramda": "^0.29.11",
"@types/three": "^0.155.1", "@types/three": "^0.155.1",
"@types/ua-parser-js": "^0.7.39", "@types/ua-parser-js": "^0.7.39",

102
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;
},
};

251
src/libs/didPeer.ts

@ -1,8 +1,8 @@
import asn1 from "asn1-ber"; import asn1 from "asn1-ber";
import { Buffer } from "buffer/"; import { Buffer } from "buffer/";
import { decode as cborDecode } from "cbor-x"; import { decode as cborDecode } from "cbor-x";
import { createJWS, JWTPayload, verifyJWT } from "did-jwt"; import { createJWS, JWTPayload } from "did-jwt";
import { DIDResolutionResult, Resolver } from "did-resolver"; import { DIDResolutionResult } from "did-resolver";
import { sha256 } from "ethereum-cryptography/sha256.js"; import { sha256 } from "ethereum-cryptography/sha256.js";
import { bytesToMultibase } from "@veramo/utils"; import { bytesToMultibase } from "@veramo/utils";
import { import {
@ -24,6 +24,7 @@ import {
} from "@simplewebauthn/types"; } from "@simplewebauthn/types";
import { generateRandomBytes } from "@/libs/crypto"; import { generateRandomBytes } from "@/libs/crypto";
import { getWebCrypto, unwrapEC2Signature } from "@/libs/crypto/passkeyHelpers";
export interface JWK { export interface JWK {
kty: string; kty: string;
@ -77,12 +78,24 @@ export async function registerCredential(userId: Uint8Array) {
expectedRPID: window.location.hostname, expectedRPID: window.location.hostname,
}); });
console.log("verification", verification); 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 { return {
authData: verification.registrationInfo?.attestationObject, authData: verification.registrationInfo?.attestationObject,
credId: verification.registrationInfo?.credentialID as string, credId: verification.registrationInfo?.credentialID as string,
rawId: new Uint8Array(new Buffer(attResp.rawId, "base64")), rawId: new Uint8Array(new Buffer(attResp.rawId, "base64")),
publicKeyJwk: undefined, publicKeyJwk: publicKeyJwk,
publicKeyBytes: verification.registrationInfo publicKeyBytes: verification.registrationInfo
?.credentialPublicKey as Uint8Array, ?.credentialPublicKey as Uint8Array,
}; };
@ -153,30 +166,9 @@ export async function registerCredential2(userId: Uint8Array) {
); );
console.log("attestationObject", attestationObject); console.log("attestationObject", attestationObject);
const jwkObj = cborDecode( const { publicKeyJwk, publicKeyBuffer } = cborToKeys(
verification.registrationInfo?.credentialPublicKey as Uint8Array, 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 publicKeyBytes = extractPublicKeyCose(attestationObject.authData);
//const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData); //const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData);
@ -186,7 +178,7 @@ export async function registerCredential2(userId: Uint8Array) {
credId: credential?.id, credId: credential?.id,
rawId: credential?.rawId, rawId: credential?.rawId,
publicKeyJwk, publicKeyJwk,
publicKeyBytes, publicKeyBytes: publicKeyBuffer,
}; };
} }
@ -208,9 +200,12 @@ export function createPeerDid(publicKeyBytes: Uint8Array) {
export class PeerSetup { export class PeerSetup {
public authenticatorData?: ArrayBuffer; public authenticatorData?: ArrayBuffer;
public authenticatorDataBase64Url?: Base64URLString;
public challenge?: Uint8Array; public challenge?: Uint8Array;
public clientDataJsonDecoded?: object; public clientDataJsonDecoded?: object;
public clientDataJsonBase64Url?: Base64URLString;
public signature?: Base64URLString; public signature?: Base64URLString;
public publicKeyJwk?: JWK;
public async createJwt( public async createJwt(
payload: object, payload: object,
@ -244,16 +239,23 @@ export class PeerSetup {
const clientAuth = await startAuthentication(options); const clientAuth = await startAuthentication(options);
console.log("custom clientAuth", clientAuth); console.log("custom clientAuth", clientAuth);
this.authenticatorDataBase64Url = clientAuth.response.authenticatorData;
this.authenticatorData = Buffer.from( this.authenticatorData = Buffer.from(
clientAuth.response.authenticatorData, clientAuth.response.authenticatorData,
"base64", "base64",
).buffer; ).buffer;
this.challenge = payloadHash; this.challenge = payloadHash;
this.clientDataJsonBase64Url = clientAuth.response.clientDataJSON;
this.clientDataJsonDecoded = JSON.parse( this.clientDataJsonDecoded = JSON.parse(
Buffer.from(clientAuth.response.clientDataJSON, "base64").toString( Buffer.from(clientAuth.response.clientDataJSON, "base64").toString(
"utf-8", "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; this.signature = clientAuth.response.signature;
const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64"); const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64");
@ -373,9 +375,12 @@ export async function verifyJwt(
credId: Base64URLString, credId: Base64URLString,
rawId: Uint8Array, rawId: Uint8Array,
authenticatorData: ArrayBuffer, authenticatorData: ArrayBuffer,
authenticatorDataBase64Url: Base64URLString,
challenge: Uint8Array, challenge: Uint8Array,
clientDataJSON: object, clientDataJSON: object,
publicKey: Uint8Array, clientDataJsonBase64Url: Base64URLString,
publicKeyBytes: Uint8Array,
publicKeyJwk: JWK,
signature: Base64URLString, signature: Base64URLString,
) { ) {
// Here's a combined auth & verify process, based on some of the inputs. // Here's a combined auth & verify process, based on some of the inputs.
@ -397,7 +402,7 @@ export async function verifyJwt(
// const verfOpts: VerifyAuthenticationResponseOpts = { // const verfOpts: VerifyAuthenticationResponseOpts = {
// authenticator: { // authenticator: {
// credentialID: credId, // credentialID: credId,
// credentialPublicKey: publicKey, // credentialPublicKey: publicKeyBytes,
// counter: 0, // counter: 0,
// }, // },
// expectedChallenge: options.challenge, // expectedChallenge: options.challenge,
@ -413,7 +418,7 @@ export async function verifyJwt(
const authOpts: VerifyAuthenticationResponseOpts = { const authOpts: VerifyAuthenticationResponseOpts = {
authenticator: { authenticator: {
credentialID: credId, credentialID: credId,
credentialPublicKey: publicKey, credentialPublicKey: publicKeyBytes,
counter: 0, counter: 0,
}, },
expectedChallenge: arrayToBase64Url(challenge), expectedChallenge: arrayToBase64Url(challenge),
@ -438,10 +443,107 @@ export async function verifyJwt(
const verification = await verifyAuthenticationResponse(authOpts); const verification = await verifyAuthenticationResponse(authOpts);
console.log("auth verification", verification); console.log("auth verification", verification);
const decoded = verifyJWT(jwt, { // It doesn't work to use the did-jwt verifyJWT with did-resolver Resolver
resolver: new Resolver({ peer: peerDidToDidDocument }), // const decoded = verifyJWT(jwt, {
}); // resolver: new Resolver({ peer: peerDidToDidDocument }),
return decoded; // });
// 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<DIDResolutionResult> { async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
@ -481,3 +583,86 @@ async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
didResolutionMetadata: { contentType: "application/did+ld+json" }, 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"],
);
}

8
src/views/TestView.vue

@ -265,9 +265,7 @@ export default class Help extends Vue {
const encodedUserId = Buffer.from(this.userId).toString("base64"); const encodedUserId = Buffer.from(this.userId).toString("base64");
console.log("encodedUserId", encodedUserId); console.log("encodedUserId", encodedUserId);
const cred = await registerCredential( const cred = await registerCredential(this.userId as Uint8Array);
this.userId as Uint8Array
);
console.log("public key", cred); console.log("public key", cred);
this.publicKeyJwk = cred.publicKeyJwk; this.publicKeyJwk = cred.publicKeyJwk;
this.publicKeyBytes = cred.publicKeyBytes; this.publicKeyBytes = cred.publicKeyBytes;
@ -277,6 +275,7 @@ export default class Help extends Vue {
} }
public async create() { public async create() {
console.log("Starting a create");
const did = createPeerDid(this.publicKeyBytes as Uint8Array); const did = createPeerDid(this.publicKeyBytes as Uint8Array);
console.log("did", did); console.log("did", did);
this.peerSetup = new PeerSetup(); this.peerSetup = new PeerSetup();
@ -303,9 +302,12 @@ export default class Help extends Vue {
this.credId as Base64URLString, this.credId as Base64URLString,
this.rawId as Uint8Array, this.rawId as Uint8Array,
this.peerSetup.authenticatorData as ArrayBuffer, this.peerSetup.authenticatorData as ArrayBuffer,
this.peerSetup.authenticatorDataBase64Url as Base64URLString,
this.peerSetup.challenge as Uint8Array, this.peerSetup.challenge as Uint8Array,
this.peerSetup.clientDataJsonDecoded, this.peerSetup.clientDataJsonDecoded,
this.peerSetup.clientDataJsonBase64Url as Base64URLString,
this.publicKeyBytes as Uint8Array, this.publicKeyBytes as Uint8Array,
this.publicKeyJwk as JWK,
this.peerSetup.signature as Base64URLString, this.peerSetup.signature as Base64URLString,
); );
console.log("decoded", decoded); console.log("decoded", decoded);

Loading…
Cancel
Save