Browse Source

remove duplicate data elements, including public key which is in peer DID

pull/116/head
Trent Larson 5 months ago
parent
commit
c661dee520
  1. 29
      src/db/tables/accounts.ts
  2. 135
      src/libs/didPeer.ts
  3. 37
      src/views/TestView.vue

29
src/db/tables/accounts.ts

@ -3,41 +3,46 @@
*/ */
export type Account = { export type Account = {
/** /**
* Auto-generated ID by Dexie. * Auto-generated ID by Dexie
*/ */
id?: number; id?: number;
/** /**
* The date the account was created. * The date the account was created
*/ */
dateCreated: string; 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; did: string;
/** /**
* Stringified JSON containing underlying key material. * Stringified JSON containing underlying key material, if generated from a mnemonic
* Based on the IIdentifier type from Veramo. * Based on the IIdentifier type from Veramo
* @see {@link https://github.com/uport-project/veramo/blob/next/packages/core-types/src/types/IIdentifier.ts} * @see {@link https://github.com/uport-project/veramo/blob/next/packages/core-types/src/types/IIdentifier.ts}
*/ */
identity: string; identity?: string;
/** /**
* The public key in hexadecimal format. * The mnemonic phrase for the account, if this is from a mnemonic
*/ */
publicKeyHex: string; mnemonic?: string;
/** /**
* The mnemonic passphrase for the account. * The Webauthn credential ID, if this is from a passkey
*/ */
mnemonic: string; passkeyCredId?: string;
/**
* The public key in hexadecimal format.
*/
publicKeyHex: string;
}; };
/** /**

135
src/libs/didPeer.ts

@ -1,10 +1,9 @@
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 { JWTPayload } from "did-jwt"; import { bytesToMultibase, JWTPayload, multibaseToBytes } from "did-jwt";
import { DIDResolutionResult } 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 { import {
startAuthentication, startAuthentication,
startRegistration, startRegistration,
@ -24,6 +23,7 @@ import {
import { getWebCrypto, unwrapEC2Signature } from "@/libs/crypto/passkeyHelpers"; import { getWebCrypto, unwrapEC2Signature } from "@/libs/crypto/passkeyHelpers";
const PEER_DID_PREFIX = "did:peer:0";
export interface JWK { export interface JWK {
kty: string; kty: string;
crv: string; crv: string;
@ -97,14 +97,16 @@ export function createPeerDid(publicKeyBytes: Uint8Array) {
"base58btc", "base58btc",
"p256-pub", "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 { export class PeerSetup {
public authenticatorData?: ArrayBuffer; public authenticatorData?: ArrayBuffer;
public authenticatorDataBase64Url?: Base64URLString;
public challenge?: Uint8Array; public challenge?: Uint8Array;
public clientDataJsonDecoded?: object;
public clientDataJsonBase64Url?: Base64URLString; public clientDataJsonBase64Url?: Base64URLString;
public signature?: Base64URLString; public signature?: Base64URLString;
@ -124,17 +126,12 @@ export class PeerSetup {
const clientAuth = await startAuthentication(options); const clientAuth = await startAuthentication(options);
// console.log("simple credential get", clientAuth); // console.log("simple credential get", clientAuth);
this.authenticatorDataBase64Url = clientAuth.response.authenticatorData; const authenticatorDataBase64Url = clientAuth.response.authenticatorData;
this.authenticatorData = Buffer.from( this.authenticatorData = Buffer.from(
clientAuth.response.authenticatorData, clientAuth.response.authenticatorData,
"base64", "base64",
).buffer; ).buffer;
this.clientDataJsonBase64Url = clientAuth.response.clientDataJSON; 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); // console.log("simple authenticatorData for signing", this.authenticatorData);
this.signature = clientAuth.response.signature; this.signature = clientAuth.response.signature;
@ -147,7 +144,7 @@ export class PeerSetup {
.replace(/=+$/, ""); .replace(/=+$/, "");
const dataInJwt = { const dataInJwt = {
AuthenticationData: this.authenticatorDataBase64Url, AuthenticationData: authenticatorDataBase64Url,
ClientDataJSON: this.clientDataJsonBase64Url, ClientDataJSON: this.clientDataJsonBase64Url,
}; };
const dataInJwtString = JSON.stringify(dataInJwt); const dataInJwtString = JSON.stringify(dataInJwt);
@ -175,20 +172,14 @@ export class PeerSetup {
const credential = await navigator.credentials.get(options); const credential = await navigator.credentials.get(options);
// console.log("nav credential get", credential); // console.log("nav credential get", credential);
this.authenticatorDataBase64Url = bufferToBase64URLString( this.authenticatorData = credential?.response.authenticatorData;
credential?.response.authenticatorData, const authenticatorDataBase64Url = bufferToBase64URLString(
this.authenticatorData,
); );
this.authenticatorData = Buffer.from(
this.authenticatorDataBase64Url as Base64URLString,
"base64",
).buffer;
this.clientDataJsonBase64Url = bufferToBase64URLString( this.clientDataJsonBase64Url = bufferToBase64URLString(
credential?.response.clientDataJSON, 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 // 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" }; const header: JWTPayload = { typ: "JWANT", alg: "ES256" };
@ -199,7 +190,7 @@ export class PeerSetup {
.replace(/=+$/, ""); .replace(/=+$/, "");
const dataInJwt = { const dataInJwt = {
AuthenticationData: this.authenticatorDataBase64Url, AuthenticationData: authenticatorDataBase64Url,
ClientDataJSON: this.clientDataJsonBase64Url, ClientDataJSON: this.clientDataJsonBase64Url,
}; };
const dataInJwtString = JSON.stringify(dataInJwt); const dataInJwtString = JSON.stringify(dataInJwt);
@ -276,20 +267,51 @@ export class PeerSetup {
// } // }
} }
// 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( export async function verifyJwtSimplewebauthn(
jwt: string,
credId: Base64URLString, credId: Base64URLString,
rawId: Uint8Array, rawId: Uint8Array,
did: string,
authenticatorData: ArrayBuffer, authenticatorData: ArrayBuffer,
authenticatorDataBase64Url: Base64URLString,
challenge: Uint8Array, challenge: Uint8Array,
clientDataJSON: object,
clientDataJsonBase64Url: Base64URLString, clientDataJsonBase64Url: Base64URLString,
publicKeyBytes: Uint8Array,
publicKeyJwk: JWK,
signature: Base64URLString, signature: Base64URLString,
) { ) {
const authData = arrayToBase64Url(Buffer.from(authenticatorData)); const authData = arrayToBase64Url(Buffer.from(authenticatorData));
const publicKeyBytes = peerDidToPublicKeyBytes(did);
const authOpts: VerifyAuthenticationResponseOpts = { const authOpts: VerifyAuthenticationResponseOpts = {
authenticator: { authenticator: {
credentialID: credId, credentialID: credId,
@ -306,9 +328,7 @@ export async function verifyJwtSimplewebauthn(
rawId: arrayToBase64Url(rawId), rawId: arrayToBase64Url(rawId),
response: { response: {
authenticatorData: authData, authenticatorData: authData,
clientDataJSON: arrayToBase64Url( clientDataJSON: clientDataJsonBase64Url,
Buffer.from(JSON.stringify(clientDataJSON)),
),
signature: signature, signature: signature,
}, },
type: "public-key", type: "public-key",
@ -318,57 +338,16 @@ export async function verifyJwtSimplewebauthn(
return verification.verified; 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( export async function verifyJwtWebCrypto(
jwt: string,
credId: Base64URLString, credId: Base64URLString,
rawId: Uint8Array, rawId: Uint8Array,
did: string,
authenticatorData: ArrayBuffer, authenticatorData: ArrayBuffer,
authenticatorDataBase64Url: Base64URLString,
challenge: Uint8Array, challenge: Uint8Array,
clientDataJSON: object,
clientDataJsonBase64Url: Base64URLString, clientDataJsonBase64Url: Base64URLString,
publicKeyBytes: Uint8Array,
publicKeyJwk: JWK,
signature: Base64URLString, signature: Base64URLString,
) { ) {
const authDataFromBase = Buffer.from(authenticatorDataBase64Url, "base64"); const authDataFromBase = Buffer.from(authenticatorData);
const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64"); const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64");
const sigBuffer = Buffer.from(signature, "base64"); const sigBuffer = Buffer.from(signature, "base64");
const finalSigBuffer = unwrapEC2Signature(sigBuffer); const finalSigBuffer = unwrapEC2Signature(sigBuffer);
@ -384,6 +363,8 @@ export async function verifyJwtWebCrypto(
name: "ECDSA", name: "ECDSA",
hash: { name: "SHA-256" }, hash: { name: "SHA-256" },
}; };
const publicKeyBytes = peerDidToPublicKeyBytes(did);
const publicKeyJwk = cborToKeys(publicKeyBytes).publicKeyJwk;
const keyAlgorithm = { const keyAlgorithm = {
name: "ECDSA", name: "ECDSA",
namedCurve: publicKeyJwk.crv, namedCurve: publicKeyJwk.crv,
@ -479,19 +460,19 @@ function base64urlEncode(buffer: ArrayBuffer) {
// from @simplewebauthn/browser // from @simplewebauthn/browser
function bufferToBase64URLString(buffer) { function bufferToBase64URLString(buffer) {
const bytes = new Uint8Array(buffer); const bytes = new Uint8Array(buffer);
let str = ''; let str = "";
for (const charCode of bytes) { for (const charCode of bytes) {
str += String.fromCharCode(charCode); str += String.fromCharCode(charCode);
} }
const base64String = btoa(str); const base64String = btoa(str);
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
} }
// from @simplewebauthn/browser // from @simplewebauthn/browser
function base64URLStringToBuffer(base64URLString) { 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 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 binary = atob(padded);
const buffer = new ArrayBuffer(binary.length); const buffer = new ArrayBuffer(binary.length);
const bytes = new Uint8Array(buffer); const bytes = new Uint8Array(buffer);

37
src/views/TestView.vue

@ -231,9 +231,9 @@ import { db } from "@/db/index";
import { generateRandomBytes } from "@/libs/crypto"; import { generateRandomBytes } from "@/libs/crypto";
import { import {
createPeerDid, createPeerDid,
JWK,
PeerSetup, PeerSetup,
registerCredential, verifyJwtP256, registerCredential,
verifyJwtP256,
verifyJwtSimplewebauthn, verifyJwtSimplewebauthn,
verifyJwtWebCrypto, verifyJwtWebCrypto,
} from "@/libs/didPeer"; } from "@/libs/didPeer";
@ -248,10 +248,9 @@ export default class Help extends Vue {
// for passkeys // for passkeys
credId?: Base64URLString; credId?: Base64URLString;
did?: string;
jwt?: string; jwt?: string;
peerSetup?: PeerSetup; peerSetup?: PeerSetup;
publicKeyJwk?: JWK;
publicKeyBytes?: Uint8Array;
rawId?: Uint8Array; rawId?: Uint8Array;
userId?: ArrayBuffer; userId?: ArrayBuffer;
@ -290,15 +289,14 @@ export default class Help extends Vue {
const cred = await registerCredential(this.userId as Uint8Array); const cred = await registerCredential(this.userId as Uint8Array);
console.log("public key", cred); console.log("public key", cred);
this.publicKeyJwk = cred.publicKeyJwk;
this.publicKeyBytes = cred.publicKeyBytes; this.publicKeyBytes = cred.publicKeyBytes;
this.did = createPeerDid(this.publicKeyBytes as Uint8Array);
this.credId = cred.credId as string; this.credId = cred.credId as string;
this.rawId = cred.rawId as Uint8Array; this.rawId = cred.rawId as Uint8Array;
} }
public async createJwtSimplewebauthn() { public async createJwtSimplewebauthn() {
const did = createPeerDid(this.publicKeyBytes as Uint8Array); console.log("generated peer did", this.did);
console.log("generated peer did", did);
const payload = { const payload = {
"@context": "https://schema.org", "@context": "https://schema.org",
@ -310,7 +308,7 @@ export default class Help extends Vue {
iat: Math.floor(Date.now() / 1000), iat: Math.floor(Date.now() / 1000),
exp: undefined, exp: undefined,
}; };
const fullPayload = { ...timestamps, ...payload, did }; const fullPayload = { ...timestamps, ...payload, iss: this.did };
this.peerSetup = new PeerSetup(); this.peerSetup = new PeerSetup();
this.jwt = await this.peerSetup.createJwtSimplewebauthn( this.jwt = await this.peerSetup.createJwtSimplewebauthn(
@ -321,8 +319,7 @@ export default class Help extends Vue {
} }
public async createJwtNavigator() { public async createJwtNavigator() {
const did = createPeerDid(this.publicKeyBytes as Uint8Array); console.log("generated peer did", this.did);
console.log("generated peer did", did);
const payload = { const payload = {
"@context": "https://schema.org", "@context": "https://schema.org",
@ -334,7 +331,7 @@ export default class Help extends Vue {
iat: Math.floor(Date.now() / 1000), iat: Math.floor(Date.now() / 1000),
exp: undefined, exp: undefined,
}; };
const fullPayload = { ...timestamps, ...payload, did }; const fullPayload = { ...timestamps, ...payload, iss: this.did };
this.peerSetup = new PeerSetup(); this.peerSetup = new PeerSetup();
this.jwt = await this.peerSetup.createJwtNavigator( this.jwt = await this.peerSetup.createJwtNavigator(
@ -346,16 +343,12 @@ export default class Help extends Vue {
public async verifyP256() { public async verifyP256() {
const decoded = await verifyJwtP256( const decoded = await verifyJwtP256(
this.jwt as string,
this.credId as Base64URLString, this.credId as Base64URLString,
this.rawId as Uint8Array, this.rawId as Uint8Array,
this.did as string,
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.clientDataJsonBase64Url as Base64URLString, this.peerSetup.clientDataJsonBase64Url as Base64URLString,
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);
@ -363,16 +356,12 @@ export default class Help extends Vue {
public async verifySimplewebauthn() { public async verifySimplewebauthn() {
const decoded = await verifyJwtSimplewebauthn( const decoded = await verifyJwtSimplewebauthn(
this.jwt as string,
this.credId as Base64URLString, this.credId as Base64URLString,
this.rawId as Uint8Array, this.rawId as Uint8Array,
this.did as string,
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.clientDataJsonBase64Url as Base64URLString, this.peerSetup.clientDataJsonBase64Url as Base64URLString,
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);
@ -380,16 +369,12 @@ export default class Help extends Vue {
public async verifyWebCrypto() { public async verifyWebCrypto() {
const decoded = await verifyJwtWebCrypto( const decoded = await verifyJwtWebCrypto(
this.jwt as string,
this.credId as Base64URLString, this.credId as Base64URLString,
this.rawId as Uint8Array, this.rawId as Uint8Array,
this.did as string,
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.clientDataJsonBase64Url as Base64URLString, this.peerSetup.clientDataJsonBase64Url as Base64URLString,
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