retrieve the correct passkey just created (doesn't validate JWT)
This commit is contained in:
@@ -64,6 +64,10 @@ export const deriveAddress = (
|
||||
return [address, privateHex, publicHex, derivationPath];
|
||||
};
|
||||
|
||||
export const generateRandomBytes = (numBytes: number): Uint8Array => {
|
||||
return getRandomBytesSync(numBytes);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ function setupGlobalErrorHandler(app: VueApp) {
|
||||
info: string,
|
||||
) => {
|
||||
console.error(
|
||||
"Global Error Handler. Info:",
|
||||
"Ouch! Global Error Handler. Info:",
|
||||
info,
|
||||
"Error:",
|
||||
err,
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user