retrieve the correct passkey just created (doesn't validate JWT)

This commit is contained in:
2024-05-24 20:16:34 -06:00
parent 7dde4d4d30
commit 94fb76cfdc
6 changed files with 253 additions and 66 deletions

View File

@@ -64,6 +64,10 @@ export const deriveAddress = (
return [address, privateHex, publicHex, derivationPath];
};
export const generateRandomBytes = (numBytes: number): Uint8Array => {
return getRandomBytesSync(numBytes);
};
/**
*
*

View File

@@ -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,49 +89,6 @@ function coseToJwk(coseKey) {
};
}
async function generateWebAuthnSignature(
dataToSign: string | Uint8Array,
credentialID: ArrayBuffer,
) {
if (!(dataToSign instanceof Uint8Array)) {
dataToSign = new TextEncoder().encode(dataToSign as string);
}
const challenge = dataToSign;
const options = {
challenge: challenge,
allowCredentials: [{ id: credentialID, type: "public-key" }],
userVerification: "preferred",
};
const assertion = await navigator.credentials.get({ publicKey: options });
const authenticatorAssertionResponse = assertion?.response;
return {
signature: authenticatorAssertionResponse.signature,
clientDataJSON: authenticatorAssertionResponse.clientDataJSON,
authenticatorData: authenticatorAssertionResponse.authenticatorData,
};
}
async function webAuthnES256KSigner(credentialID: ArrayBuffer) {
return async (data: string | Uint8Array) => {
// also has clientDataJSON
const { signature, authenticatorData } = await generateWebAuthnSignature(
data,
credentialID,
);
// Combine the WebAuthn components into a single signature format as required by did-jwt
const combinedSignature = Buffer.concat([
Buffer.from(authenticatorData),
Buffer.from(signature),
]);
return combinedSignature.toString("base64");
};
}
export async function createJwt(
payload: object,
issuerDid: string,
@@ -128,3 +106,56 @@ export async function createJwt(
return createJWS(fullPayload, signer, header);
}
async function webAuthnES256KSigner(credentialID: ArrayBuffer) {
return async (data: string | Uint8Array) => {
// also has clientDataJSON
const { signature, authenticatorData } = await generateWebAuthnSignature(
data,
credentialID,
);
// Combine the WebAuthn components into a single signature format as required by did-jwt
const combinedSignature = Buffer.concat([
Buffer.from(authenticatorData),
Buffer.from(signature),
]);
return combinedSignature.toString("base64");
};
}
async function generateWebAuthnSignature(
dataToSign: string | Uint8Array,
credentialId: ArrayBuffer,
) {
if (!(dataToSign instanceof Uint8Array)) {
dataToSign = new TextEncoder().encode(dataToSign as string);
}
const challenge = generateRandomBytes(32);
const options = {
challenge: challenge,
allowCredentials: [{ id: credentialId, type: "public-key" }],
userVerification: "preferred",
};
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;
}

View File

@@ -154,7 +154,7 @@ function setupGlobalErrorHandler(app: VueApp) {
info: string,
) => {
console.error(
"Global Error Handler. Info:",
"Ouch! Global Error Handler. Info:",
info,
"Error:",
err,

View File

@@ -63,14 +63,18 @@
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { accountsDB } from "@/db/index";
import { createJwt, registerCredential } from "@/libs/didPeer";
import { generateRandomBytes } from "@/libs/crypto";
import { createJwt, JWK, registerCredential, verifyJwt } from "@/libs/didPeer";
@Component({
components: {},
})
export default class StartView extends Vue {
numAccounts = 0;
publicKey?: JWK;
userId?: Uint8Array;
async mounted() {
await accountsDB.open();
@@ -78,22 +82,35 @@ export default class StartView extends Vue {
}
public async onClickYes() {
const cred = await registerCredential();
console.log("cred", cred);
this.userId = generateRandomBytes(16);
const encodedUserId = new TextDecoder("utf-8").decode(this.userId);
console.log("encodedUserId", encodedUserId);
const credArrayBuffer = new TextEncoder().encode("some-random-id").buffer;
const jwt = await createJwt({ a: 1 }, "issuerDid", credArrayBuffer);
console.log("jwt", jwt);
const cred = await registerCredential(this.userId, generateRandomBytes(32));
console.log("public key", cred);
this.publicKey = cred.publicKey;
this.userId = cred.rawId as Uint8Array;
//this.$router.push({ name: "new-identifier" });
}
public onClickNo() {
this.$router.push({ name: "import-account" });
public async onClickNo() {
const credArrBuff = this.userId;
const jwt = await createJwt({ a: 1 }, "issuerDid", credArrBuff);
console.log("jwt", jwt);
const jwt4url = jwt
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
console.log("jwt4url", jwt4url);
const decoded = await verifyJwt(jwt, this.publicKey as JWK);
console.log("decoded", decoded);
//this.$router.push({ name: "import-account" });
}
public onClickDerive() {
this.$router.push({ name: "import-derive" });
//this.$router.push({ name: "import-derive" });
}
}
</script>