remove duplicate data elements, including public key which is in peer DID
This commit is contained in:
@@ -3,41 +3,46 @@
|
||||
*/
|
||||
export type Account = {
|
||||
/**
|
||||
* Auto-generated ID by Dexie.
|
||||
* Auto-generated ID by Dexie
|
||||
*/
|
||||
id?: number;
|
||||
|
||||
/**
|
||||
* The date the account was created.
|
||||
* The date the account was created
|
||||
*/
|
||||
dateCreated: string;
|
||||
|
||||
/**
|
||||
* The derivation path for the account.
|
||||
* The derivation path for the account, if this is from a mnemonic
|
||||
*/
|
||||
derivationPath: string;
|
||||
derivationPath?: string;
|
||||
|
||||
/**
|
||||
* Decentralized Identifier (DID) for the account.
|
||||
* Decentralized Identifier (DID) for the account
|
||||
*/
|
||||
did: string;
|
||||
|
||||
/**
|
||||
* Stringified JSON containing underlying key material.
|
||||
* Based on the IIdentifier type from Veramo.
|
||||
* Stringified JSON containing underlying key material, if generated from a mnemonic
|
||||
* Based on the IIdentifier type from Veramo
|
||||
* @see {@link https://github.com/uport-project/veramo/blob/next/packages/core-types/src/types/IIdentifier.ts}
|
||||
*/
|
||||
identity: string;
|
||||
identity?: string;
|
||||
|
||||
/**
|
||||
* The mnemonic phrase for the account, if this is from a mnemonic
|
||||
*/
|
||||
mnemonic?: string;
|
||||
|
||||
/**
|
||||
* The Webauthn credential ID, if this is from a passkey
|
||||
*/
|
||||
passkeyCredId?: string;
|
||||
|
||||
/**
|
||||
* The public key in hexadecimal format.
|
||||
*/
|
||||
publicKeyHex: string;
|
||||
|
||||
/**
|
||||
* The mnemonic passphrase for the account.
|
||||
*/
|
||||
mnemonic: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import asn1 from "asn1-ber";
|
||||
import { Buffer } from "buffer/";
|
||||
import { decode as cborDecode } from "cbor-x";
|
||||
import { JWTPayload } from "did-jwt";
|
||||
import { bytesToMultibase, JWTPayload, multibaseToBytes } from "did-jwt";
|
||||
import { DIDResolutionResult } from "did-resolver";
|
||||
import { sha256 } from "ethereum-cryptography/sha256.js";
|
||||
import { bytesToMultibase } from "@veramo/utils";
|
||||
import {
|
||||
startAuthentication,
|
||||
startRegistration,
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
|
||||
import { getWebCrypto, unwrapEC2Signature } from "@/libs/crypto/passkeyHelpers";
|
||||
|
||||
const PEER_DID_PREFIX = "did:peer:0";
|
||||
export interface JWK {
|
||||
kty: string;
|
||||
crv: string;
|
||||
@@ -97,14 +97,16 @@ export function createPeerDid(publicKeyBytes: Uint8Array) {
|
||||
"base58btc",
|
||||
"p256-pub",
|
||||
);
|
||||
return "did:peer:0" + methodSpecificId;
|
||||
return PEER_DID_PREFIX + methodSpecificId;
|
||||
}
|
||||
|
||||
function peerDidToPublicKeyBytes(did: string) {
|
||||
return multibaseToBytes(did.substring(PEER_DID_PREFIX.length));
|
||||
}
|
||||
|
||||
export class PeerSetup {
|
||||
public authenticatorData?: ArrayBuffer;
|
||||
public authenticatorDataBase64Url?: Base64URLString;
|
||||
public challenge?: Uint8Array;
|
||||
public clientDataJsonDecoded?: object;
|
||||
public clientDataJsonBase64Url?: Base64URLString;
|
||||
public signature?: Base64URLString;
|
||||
|
||||
@@ -124,17 +126,12 @@ export class PeerSetup {
|
||||
const clientAuth = await startAuthentication(options);
|
||||
// console.log("simple credential get", clientAuth);
|
||||
|
||||
this.authenticatorDataBase64Url = clientAuth.response.authenticatorData;
|
||||
const authenticatorDataBase64Url = clientAuth.response.authenticatorData;
|
||||
this.authenticatorData = Buffer.from(
|
||||
clientAuth.response.authenticatorData,
|
||||
"base64",
|
||||
).buffer;
|
||||
this.clientDataJsonBase64Url = clientAuth.response.clientDataJSON;
|
||||
this.clientDataJsonDecoded = JSON.parse(
|
||||
Buffer.from(clientAuth.response.clientDataJSON, "base64").toString(
|
||||
"utf-8",
|
||||
),
|
||||
);
|
||||
// console.log("simple authenticatorData for signing", this.authenticatorData);
|
||||
this.signature = clientAuth.response.signature;
|
||||
|
||||
@@ -147,7 +144,7 @@ export class PeerSetup {
|
||||
.replace(/=+$/, "");
|
||||
|
||||
const dataInJwt = {
|
||||
AuthenticationData: this.authenticatorDataBase64Url,
|
||||
AuthenticationData: authenticatorDataBase64Url,
|
||||
ClientDataJSON: this.clientDataJsonBase64Url,
|
||||
};
|
||||
const dataInJwtString = JSON.stringify(dataInJwt);
|
||||
@@ -175,20 +172,14 @@ export class PeerSetup {
|
||||
const credential = await navigator.credentials.get(options);
|
||||
// console.log("nav credential get", credential);
|
||||
|
||||
this.authenticatorDataBase64Url = bufferToBase64URLString(
|
||||
credential?.response.authenticatorData,
|
||||
this.authenticatorData = credential?.response.authenticatorData;
|
||||
const authenticatorDataBase64Url = bufferToBase64URLString(
|
||||
this.authenticatorData,
|
||||
);
|
||||
this.authenticatorData = Buffer.from(
|
||||
this.authenticatorDataBase64Url as Base64URLString,
|
||||
"base64",
|
||||
).buffer;
|
||||
|
||||
this.clientDataJsonBase64Url = bufferToBase64URLString(
|
||||
credential?.response.clientDataJSON,
|
||||
);
|
||||
this.clientDataJsonDecoded = JSON.parse(
|
||||
new TextDecoder("utf-8").decode(credential?.response.clientDataJSON),
|
||||
);
|
||||
|
||||
// Our custom type of JWANT means the signature is based on a concatenation of the two Webauthn properties
|
||||
const header: JWTPayload = { typ: "JWANT", alg: "ES256" };
|
||||
@@ -199,7 +190,7 @@ export class PeerSetup {
|
||||
.replace(/=+$/, "");
|
||||
|
||||
const dataInJwt = {
|
||||
AuthenticationData: this.authenticatorDataBase64Url,
|
||||
AuthenticationData: authenticatorDataBase64Url,
|
||||
ClientDataJSON: this.clientDataJsonBase64Url,
|
||||
};
|
||||
const dataInJwtString = JSON.stringify(dataInJwt);
|
||||
@@ -276,20 +267,51 @@ export class PeerSetup {
|
||||
// }
|
||||
}
|
||||
|
||||
export async function verifyJwtSimplewebauthn(
|
||||
jwt: string,
|
||||
// I'd love to use this but it doesn't verify.
|
||||
// Pequires:
|
||||
// npm install @noble/curves
|
||||
// ... and this import:
|
||||
// import { p256 } from "@noble/curves/p256";
|
||||
export async function verifyJwtP256(
|
||||
credId: Base64URLString,
|
||||
rawId: Uint8Array,
|
||||
did: string,
|
||||
authenticatorData: ArrayBuffer,
|
||||
challenge: Uint8Array,
|
||||
clientDataJsonBase64Url: Base64URLString,
|
||||
signature: Base64URLString,
|
||||
) {
|
||||
const authDataFromBase = Buffer.from(authenticatorData);
|
||||
const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64");
|
||||
const sigBuffer = Buffer.from(signature, "base64");
|
||||
const finalSigBuffer = unwrapEC2Signature(sigBuffer);
|
||||
const publicKeyBytes = peerDidToPublicKeyBytes(did);
|
||||
|
||||
// Hash the client data
|
||||
const hash = sha256(clientDataFromBase);
|
||||
|
||||
// Construct the preimage
|
||||
const preimage = Buffer.concat([authDataFromBase, hash]);
|
||||
|
||||
const isValid = p256.verify(
|
||||
finalSigBuffer,
|
||||
new Uint8Array(preimage),
|
||||
publicKeyBytes,
|
||||
);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
export async function verifyJwtSimplewebauthn(
|
||||
credId: Base64URLString,
|
||||
rawId: Uint8Array,
|
||||
did: string,
|
||||
authenticatorData: ArrayBuffer,
|
||||
authenticatorDataBase64Url: Base64URLString,
|
||||
challenge: Uint8Array,
|
||||
clientDataJSON: object,
|
||||
clientDataJsonBase64Url: Base64URLString,
|
||||
publicKeyBytes: Uint8Array,
|
||||
publicKeyJwk: JWK,
|
||||
signature: Base64URLString,
|
||||
) {
|
||||
const authData = arrayToBase64Url(Buffer.from(authenticatorData));
|
||||
const publicKeyBytes = peerDidToPublicKeyBytes(did);
|
||||
const authOpts: VerifyAuthenticationResponseOpts = {
|
||||
authenticator: {
|
||||
credentialID: credId,
|
||||
@@ -306,9 +328,7 @@ export async function verifyJwtSimplewebauthn(
|
||||
rawId: arrayToBase64Url(rawId),
|
||||
response: {
|
||||
authenticatorData: authData,
|
||||
clientDataJSON: arrayToBase64Url(
|
||||
Buffer.from(JSON.stringify(clientDataJSON)),
|
||||
),
|
||||
clientDataJSON: clientDataJsonBase64Url,
|
||||
signature: signature,
|
||||
},
|
||||
type: "public-key",
|
||||
@@ -318,57 +338,16 @@ export async function verifyJwtSimplewebauthn(
|
||||
return verification.verified;
|
||||
}
|
||||
|
||||
// I'd love to use this but it doesn't verify.
|
||||
// Pequires:
|
||||
// npm install @noble/curves
|
||||
// ... and this import:
|
||||
// import { p256 } from "@noble/curves/p256";
|
||||
export async function verifyJwtP256(
|
||||
jwt: string,
|
||||
credId: Base64URLString,
|
||||
rawId: Uint8Array,
|
||||
authenticatorData: ArrayBuffer,
|
||||
authenticatorDataBase64Url: Base64URLString,
|
||||
challenge: Uint8Array,
|
||||
clientDataJSON: object,
|
||||
clientDataJsonBase64Url: Base64URLString,
|
||||
publicKeyBytes: Uint8Array,
|
||||
publicKeyJwk: JWK,
|
||||
signature: Base64URLString,
|
||||
) {
|
||||
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]);
|
||||
|
||||
const isValid = p256.verify(
|
||||
finalSigBuffer,
|
||||
new Uint8Array(preimage),
|
||||
publicKeyBytes,
|
||||
);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
export async function verifyJwtWebCrypto(
|
||||
jwt: string,
|
||||
credId: Base64URLString,
|
||||
rawId: Uint8Array,
|
||||
did: string,
|
||||
authenticatorData: ArrayBuffer,
|
||||
authenticatorDataBase64Url: Base64URLString,
|
||||
challenge: Uint8Array,
|
||||
clientDataJSON: object,
|
||||
clientDataJsonBase64Url: Base64URLString,
|
||||
publicKeyBytes: Uint8Array,
|
||||
publicKeyJwk: JWK,
|
||||
signature: Base64URLString,
|
||||
) {
|
||||
const authDataFromBase = Buffer.from(authenticatorDataBase64Url, "base64");
|
||||
const authDataFromBase = Buffer.from(authenticatorData);
|
||||
const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64");
|
||||
const sigBuffer = Buffer.from(signature, "base64");
|
||||
const finalSigBuffer = unwrapEC2Signature(sigBuffer);
|
||||
@@ -384,6 +363,8 @@ export async function verifyJwtWebCrypto(
|
||||
name: "ECDSA",
|
||||
hash: { name: "SHA-256" },
|
||||
};
|
||||
const publicKeyBytes = peerDidToPublicKeyBytes(did);
|
||||
const publicKeyJwk = cborToKeys(publicKeyBytes).publicKeyJwk;
|
||||
const keyAlgorithm = {
|
||||
name: "ECDSA",
|
||||
namedCurve: publicKeyJwk.crv,
|
||||
@@ -479,19 +460,19 @@ function base64urlEncode(buffer: ArrayBuffer) {
|
||||
// from @simplewebauthn/browser
|
||||
function bufferToBase64URLString(buffer) {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
let str = '';
|
||||
let str = "";
|
||||
for (const charCode of bytes) {
|
||||
str += String.fromCharCode(charCode);
|
||||
}
|
||||
const base64String = btoa(str);
|
||||
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||
return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||
}
|
||||
|
||||
// from @simplewebauthn/browser
|
||||
function base64URLStringToBuffer(base64URLString) {
|
||||
const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const base64 = base64URLString.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const padLength = (4 - (base64.length % 4)) % 4;
|
||||
const padded = base64.padEnd(base64.length + padLength, '=');
|
||||
const padded = base64.padEnd(base64.length + padLength, "=");
|
||||
const binary = atob(padded);
|
||||
const buffer = new ArrayBuffer(binary.length);
|
||||
const bytes = new Uint8Array(buffer);
|
||||
|
||||
@@ -231,9 +231,9 @@ import { db } from "@/db/index";
|
||||
import { generateRandomBytes } from "@/libs/crypto";
|
||||
import {
|
||||
createPeerDid,
|
||||
JWK,
|
||||
PeerSetup,
|
||||
registerCredential, verifyJwtP256,
|
||||
registerCredential,
|
||||
verifyJwtP256,
|
||||
verifyJwtSimplewebauthn,
|
||||
verifyJwtWebCrypto,
|
||||
} from "@/libs/didPeer";
|
||||
@@ -248,10 +248,9 @@ export default class Help extends Vue {
|
||||
|
||||
// for passkeys
|
||||
credId?: Base64URLString;
|
||||
did?: string;
|
||||
jwt?: string;
|
||||
peerSetup?: PeerSetup;
|
||||
publicKeyJwk?: JWK;
|
||||
publicKeyBytes?: Uint8Array;
|
||||
rawId?: Uint8Array;
|
||||
userId?: ArrayBuffer;
|
||||
|
||||
@@ -290,15 +289,14 @@ export default class Help extends Vue {
|
||||
|
||||
const cred = await registerCredential(this.userId as Uint8Array);
|
||||
console.log("public key", cred);
|
||||
this.publicKeyJwk = cred.publicKeyJwk;
|
||||
this.publicKeyBytes = cred.publicKeyBytes;
|
||||
this.did = createPeerDid(this.publicKeyBytes as Uint8Array);
|
||||
this.credId = cred.credId as string;
|
||||
this.rawId = cred.rawId as Uint8Array;
|
||||
}
|
||||
|
||||
public async createJwtSimplewebauthn() {
|
||||
const did = createPeerDid(this.publicKeyBytes as Uint8Array);
|
||||
console.log("generated peer did", did);
|
||||
console.log("generated peer did", this.did);
|
||||
|
||||
const payload = {
|
||||
"@context": "https://schema.org",
|
||||
@@ -310,7 +308,7 @@ export default class Help extends Vue {
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: undefined,
|
||||
};
|
||||
const fullPayload = { ...timestamps, ...payload, did };
|
||||
const fullPayload = { ...timestamps, ...payload, iss: this.did };
|
||||
|
||||
this.peerSetup = new PeerSetup();
|
||||
this.jwt = await this.peerSetup.createJwtSimplewebauthn(
|
||||
@@ -321,8 +319,7 @@ export default class Help extends Vue {
|
||||
}
|
||||
|
||||
public async createJwtNavigator() {
|
||||
const did = createPeerDid(this.publicKeyBytes as Uint8Array);
|
||||
console.log("generated peer did", did);
|
||||
console.log("generated peer did", this.did);
|
||||
|
||||
const payload = {
|
||||
"@context": "https://schema.org",
|
||||
@@ -334,7 +331,7 @@ export default class Help extends Vue {
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: undefined,
|
||||
};
|
||||
const fullPayload = { ...timestamps, ...payload, did };
|
||||
const fullPayload = { ...timestamps, ...payload, iss: this.did };
|
||||
|
||||
this.peerSetup = new PeerSetup();
|
||||
this.jwt = await this.peerSetup.createJwtNavigator(
|
||||
@@ -346,16 +343,12 @@ export default class Help extends Vue {
|
||||
|
||||
public async verifyP256() {
|
||||
const decoded = await verifyJwtP256(
|
||||
this.jwt as string,
|
||||
this.credId as Base64URLString,
|
||||
this.rawId as Uint8Array,
|
||||
this.did as string,
|
||||
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);
|
||||
@@ -363,16 +356,12 @@ export default class Help extends Vue {
|
||||
|
||||
public async verifySimplewebauthn() {
|
||||
const decoded = await verifyJwtSimplewebauthn(
|
||||
this.jwt as string,
|
||||
this.credId as Base64URLString,
|
||||
this.rawId as Uint8Array,
|
||||
this.did as string,
|
||||
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);
|
||||
@@ -380,16 +369,12 @@ export default class Help extends Vue {
|
||||
|
||||
public async verifyWebCrypto() {
|
||||
const decoded = await verifyJwtWebCrypto(
|
||||
this.jwt as string,
|
||||
this.credId as Base64URLString,
|
||||
this.rawId as Uint8Array,
|
||||
this.did as string,
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user