add prompt for a passkey and a start at creating a JWT out of it
This commit is contained in:
125
package-lock.json
generated
125
package-lock.json
generated
@@ -28,6 +28,7 @@
|
|||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@zxing/text-encoding": "^0.9.0",
|
"@zxing/text-encoding": "^0.9.0",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
|
"cbor-x": "^1.5.9",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"dexie": "^3.2.7",
|
"dexie": "^3.2.7",
|
||||||
"dexie-export-import": "^4.1.1",
|
"dexie-export-import": "^4.1.1",
|
||||||
@@ -2412,6 +2413,78 @@
|
|||||||
"node": ">=8.9"
|
"node": ">=8.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-darwin-arm64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-darwin-x64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-linux-arm": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-linux-arm64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-linux-x64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-win32-x64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@dicebear/adventurer": {
|
"node_modules/@dicebear/adventurer": {
|
||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -10936,6 +11009,35 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cbor-extract": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"node-gyp-build-optional-packages": "5.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"download-cbor-prebuilds": "bin/download-prebuilds.js"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@cbor-extract/cbor-extract-darwin-arm64": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-darwin-x64": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-linux-arm": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-linux-arm64": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-linux-x64": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-win32-x64": "2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cbor-x": {
|
||||||
|
"version": "1.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.5.9.tgz",
|
||||||
|
"integrity": "sha512-OEI5rEu3MeR0WWNUXuIGkxmbXVhABP+VtgAXzm48c9ulkrsvxshjjk94XSOGphyAKeNGLPfAxxzEtgQ6rEVpYQ==",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"cbor-extract": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
@@ -17826,6 +17928,29 @@
|
|||||||
"node-gyp-build-test": "build-test.js"
|
"node-gyp-build-test": "build-test.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-gyp-build-optional-packages": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-gyp-build-optional-packages": "bin.js",
|
||||||
|
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||||
|
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-int64": {
|
"node_modules/node-int64": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@zxing/text-encoding": "^0.9.0",
|
"@zxing/text-encoding": "^0.9.0",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
|
"cbor-x": "^1.5.9",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"dexie": "^3.2.7",
|
"dexie": "^3.2.7",
|
||||||
"dexie-export-import": "^4.1.1",
|
"dexie-export-import": "^4.1.1",
|
||||||
|
|||||||
130
src/libs/didPeer.ts
Normal file
130
src/libs/didPeer.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { decode as cborDecode } from "cbor-x";
|
||||||
|
import { createJWS, JWTPayload } from "did-jwt";
|
||||||
|
|
||||||
|
export async function registerCredential() {
|
||||||
|
const publicKeyOptions: PublicKeyCredentialCreationOptions = {
|
||||||
|
challenge: new Uint8Array(32), // Random challenge
|
||||||
|
rp: {
|
||||||
|
name: "Time Safari",
|
||||||
|
id: window.location.hostname,
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
id: new Uint8Array(16), // User ID
|
||||||
|
name: "user@example.com",
|
||||||
|
displayName: "Example User",
|
||||||
|
},
|
||||||
|
pubKeyCredParams: [
|
||||||
|
{
|
||||||
|
type: "public-key",
|
||||||
|
alg: -7, // ES256 algorithm
|
||||||
|
},
|
||||||
|
],
|
||||||
|
authenticatorSelection: {
|
||||||
|
authenticatorAttachment: "platform",
|
||||||
|
userVerification: "preferred",
|
||||||
|
},
|
||||||
|
timeout: 60000,
|
||||||
|
attestation: "direct",
|
||||||
|
};
|
||||||
|
|
||||||
|
const credential = await navigator.credentials.create({
|
||||||
|
publicKey: publicKeyOptions,
|
||||||
|
});
|
||||||
|
console.log("credential", credential);
|
||||||
|
const attestationResponse = credential?.response;
|
||||||
|
|
||||||
|
// Parse the attestation response to get the public key
|
||||||
|
const clientDataJSON = attestationResponse.clientDataJSON;
|
||||||
|
console.log("clientDataJSON", clientDataJSON);
|
||||||
|
const attestationObject = cborDecode(
|
||||||
|
new Uint8Array(attestationResponse.attestationObject),
|
||||||
|
);
|
||||||
|
|
||||||
|
const authData = new Uint8Array(attestationObject.authData);
|
||||||
|
const publicKey = extractPublicKey(authData);
|
||||||
|
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error just because it doesn't like the "any"
|
||||||
|
function extractPublicKey(authData) {
|
||||||
|
// 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
|
||||||
|
const publicKeyCose = authData.slice(authData.length - 77); // Example position
|
||||||
|
const publicKeyJwk = coseToJwk(publicKeyCose);
|
||||||
|
return publicKeyJwk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error just because it doesn't like the "any"
|
||||||
|
function coseToJwk(coseKey) {
|
||||||
|
// Convert COSE key format to JWK
|
||||||
|
// This is simplified and needs appropriate parsing and conversion logic
|
||||||
|
return {
|
||||||
|
kty: "EC",
|
||||||
|
crv: "P-256",
|
||||||
|
x: btoa(coseKey.slice(2, 34)),
|
||||||
|
y: btoa(coseKey.slice(34, 66)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
credentialId: ArrayBuffer,
|
||||||
|
) {
|
||||||
|
const signer = await webAuthnES256KSigner(credentialId);
|
||||||
|
|
||||||
|
// from createJWT in did-jwt/src/JWT.ts
|
||||||
|
const header: JWTPayload = { typ: "JWT", alg: "ES256K" };
|
||||||
|
const timestamps: Partial<JWTPayload> = {
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
|
exp: undefined,
|
||||||
|
};
|
||||||
|
const fullPayload = { ...timestamps, ...payload, iss: issuerDid };
|
||||||
|
|
||||||
|
return createJWS(fullPayload, signer, header);
|
||||||
|
}
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { accountsDB } from "@/db/index";
|
import { accountsDB } from "@/db/index";
|
||||||
|
import { createJwt, registerCredential } from "@/libs/didPeer";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {},
|
components: {},
|
||||||
@@ -76,8 +77,15 @@ export default class StartView extends Vue {
|
|||||||
this.numAccounts = await accountsDB.accounts.count();
|
this.numAccounts = await accountsDB.accounts.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onClickYes() {
|
public async onClickYes() {
|
||||||
this.$router.push({ name: "new-identifier" });
|
const cred = await registerCredential();
|
||||||
|
console.log("cred", cred);
|
||||||
|
|
||||||
|
const credArrayBuffer = new TextEncoder().encode("some-random-id").buffer;
|
||||||
|
|
||||||
|
const jwt = await createJwt({ a: 1 }, "issuerDid", credArrayBuffer);
|
||||||
|
console.log("jwt", jwt);
|
||||||
|
//this.$router.push({ name: "new-identifier" });
|
||||||
}
|
}
|
||||||
|
|
||||||
public onClickNo() {
|
public onClickNo() {
|
||||||
|
|||||||
Reference in New Issue
Block a user