Browse Source

fully split the signature creation from the verification

pull/116/head
Trent Larson 3 months ago
parent
commit
237c37f29d
  1. 38
      package-lock.json
  2. 161
      src/libs/didPeer.ts
  3. 16
      src/views/TestView.vue

38
package-lock.json

@ -6018,6 +6018,17 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/curves/node_modules/@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/ed25519": {
"version": "1.7.3",
"funding": [
@ -6030,8 +6041,9 @@
"optional": true
},
"node_modules/@noble/hashes": {
"version": "1.3.2",
"license": "MIT",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"engines": {
"node": ">= 16"
},
@ -8197,6 +8209,17 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip39/node_modules/@noble/hashes": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
"integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@segment/loosely-validate-event": {
"version": "2.0.0",
"optional": true,
@ -12718,6 +12741,17 @@
"node": ">=14.0.0"
}
},
"node_modules/ethers/node_modules/@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethers/node_modules/@types/node": {
"version": "18.15.13",
"license": "MIT"

161
src/libs/didPeer.ts

@ -3,6 +3,7 @@ import { Buffer } from "buffer/";
import { decode as cborDecode } from "cbor-x";
import { createJWS, JWTPayload, verifyJWT } from "did-jwt";
import { DIDResolutionResult, Resolver } from "did-resolver";
import { sha256 } from "ethereum-cryptography/sha256.js";
import { bytesToMultibase } from "@veramo/utils";
import {
startAuthentication,
@ -22,6 +23,8 @@ import {
PublicKeyCredentialRequestOptionsJSON,
} from "@simplewebauthn/types";
import { generateRandomBytes } from "@/libs/crypto";
export interface JWK {
kty: string;
crv: string;
@ -33,15 +36,15 @@ export interface PublicKeyCredential {
jwt: JWK;
}
function toBase64Url(anything: Uint8Array) {
return Buffer.from(anything)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
function toBase64Url(anythingB64: string) {
return anythingB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function arrayToBase64Url(anything: Uint8Array) {
return toBase64Url(Buffer.from(anything).toString("base64"));
}
export async function registerCredential() {
export async function registerCredential(userId: Uint8Array) {
const options: PublicKeyCredentialCreationOptionsJSON =
await generateRegistrationOptions({
rpName: "Time Safari",
@ -85,10 +88,8 @@ export async function registerCredential() {
};
}
export async function registerCredential2(
userId: Uint8Array,
challenge: Uint8Array,
) {
export async function registerCredential2(userId: Uint8Array) {
const challenge = generateRandomBytes(32);
const publicKeyOptions: PublicKeyCredentialCreationOptions = {
challenge: challenge,
rp: {
@ -120,20 +121,20 @@ export async function registerCredential2(
console.log("credential", credential);
console.log(credential?.id, " is the new ID base64-url-encoded");
console.log(toBase64Url(credential?.rawId), " is the base64 rawId");
console.log(arrayToBase64Url(credential?.rawId), " is the base64 rawId");
const attestationResponse = credential?.response;
const verfInput: VerifyRegistrationResponseOpts = {
response: {
id: credential?.id as string,
rawId: credential?.id as string, //Buffer.from(credential?.rawId).toString("base64"),
response: {
attestationObject: toBase64Url(attestationResponse?.attestationObject),
clientDataJSON: toBase64Url(attestationResponse?.clientDataJSON),
attestationObject: arrayToBase64Url(attestationResponse?.attestationObject),
clientDataJSON: arrayToBase64Url(attestationResponse?.clientDataJSON),
},
clientExtensionResults: {},
type: "public-key",
},
expectedChallenge: toBase64Url(challenge),
expectedChallenge: arrayToBase64Url(challenge),
expectedOrigin: window.location.origin,
};
console.log("verfInput", verfInput);
@ -169,8 +170,8 @@ export async function registerCredential2(
alg: "ES256",
crv: "P-256",
kty: "EC",
x: toBase64Url(jwkObj[-2]),
y: toBase64Url(jwkObj[-3]),
x: arrayToBase64Url(jwkObj[-2]),
y: arrayToBase64Url(jwkObj[-3]),
};
const publicKeyBytes = Buffer.concat([
Buffer.from(jwkObj[-2]),
@ -207,12 +208,65 @@ export function createPeerDid(publicKeyBytes: Uint8Array) {
export class PeerSetup {
public authenticatorData?: ArrayBuffer;
public challenge?: Uint8Array;
public clientDataJsonDecoded?: object;
public signature?: Base64URLString;
public async createJwt(
payload: object,
issuerDid: string,
credentialId: string,
) {
const header: JWTPayload = { typ: "JWT", alg: "ES256" };
// from createJWT in did-jwt/src/JWT.ts
const timestamps: Partial<JWTPayload> = {
iat: Math.floor(Date.now() / 1000),
exp: undefined,
};
const fullPayload = { ...timestamps, ...payload, iss: issuerDid };
const payloadHash: Uint8Array = sha256(
Buffer.from(JSON.stringify(fullPayload)),
);
const options: PublicKeyCredentialRequestOptionsJSON =
await generateAuthenticationOptions({
challenge: payloadHash,
rpID: window.location.hostname,
// Require users to use a previously-registered authenticator
// allowCredentials: userPasskeys.map(passkey => ({
// id: passkey.id,
// transports: passkey.transports,
// })),
});
console.log("custom authentication options", options);
const clientAuth = await startAuthentication(options);
console.log("custom clientAuth", clientAuth);
this.authenticatorData = Buffer.from(
clientAuth.response.authenticatorData,
"base64",
).buffer;
this.challenge = payloadHash;
this.clientDataJsonDecoded = JSON.parse(
Buffer.from(clientAuth.response.clientDataJSON, "base64").toString(
"utf-8",
),
);
this.signature = clientAuth.response.signature;
const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64");
const payloadBase64 = clientAuth.response.clientDataJSON;
const signature = clientAuth.response.signature;
return headerBase64 + "." + payloadBase64 + "." + signature;
}
public async createJwt2(
payload: object,
issuerDid: string,
credentialId: string,
) {
const signer = await this.webAuthnES256KSigner(credentialId);
@ -319,58 +373,63 @@ export async function verifyJwt(
credId: Base64URLString,
rawId: Uint8Array,
authenticatorData: ArrayBuffer,
challenge: Uint8Array,
clientDataJSON: object,
publicKey: Uint8Array,
signature: Base64URLString,
) {
const options: PublicKeyCredentialRequestOptionsJSON =
await generateAuthenticationOptions({
rpID: window.location.hostname,
// Require users to use a previously-registered authenticator
// allowCredentials: userPasskeys.map(passkey => ({
// id: passkey.id,
// transports: passkey.transports,
// })),
});
console.log("authentication options", options);
const clientAuth = await startAuthentication(options);
console.log("clientAuth", clientAuth);
const verfOpts: VerifyAuthenticationResponseOpts = {
authenticator: {
credentialID: credId,
credentialPublicKey: publicKey,
counter: 0,
},
expectedChallenge: options.challenge,
expectedOrigin: window.location.origin,
expectedRPID: window.location.hostname,
response: clientAuth,
};
console.log("verfOpts", verfOpts);
const verificationFromClient = await verifyAuthenticationResponse(verfOpts);
console.log("client auth verification", verificationFromClient);
const authData = toBase64Url(Buffer.from(authenticatorData));
// Here's a combined auth & verify process, based on some of the inputs.
//
// const options: PublicKeyCredentialRequestOptionsJSON =
// await generateAuthenticationOptions({
// rpID: window.location.hostname,
// // Require users to use a previously-registered authenticator
// // allowCredentials: userPasskeys.map(passkey => ({
// // id: passkey.id,
// // transports: passkey.transports,
// // })),
// });
// console.log("authentication options", options);
//
// const clientAuth = await startAuthentication(options);
// console.log("clientAuth", clientAuth);
//
// const verfOpts: VerifyAuthenticationResponseOpts = {
// authenticator: {
// credentialID: credId,
// credentialPublicKey: publicKey,
// counter: 0,
// },
// expectedChallenge: options.challenge,
// expectedOrigin: window.location.origin,
// expectedRPID: window.location.hostname,
// response: clientAuth,
// };
// console.log("verfOpts", verfOpts);
// const verificationFromClient = await verifyAuthenticationResponse(verfOpts);
// console.log("client auth verification", verificationFromClient);
const authData = arrayToBase64Url(Buffer.from(authenticatorData));
const authOpts: VerifyAuthenticationResponseOpts = {
authenticator: {
credentialID: credId,
credentialPublicKey: publicKey,
counter: 0,
},
expectedChallenge: options.challenge,
expectedChallenge: arrayToBase64Url(challenge),
expectedOrigin: window.location.origin,
expectedRPID: window.location.hostname,
response: {
authenticatorAttachment: "platform",
clientExtensionResults: {},
id: credId,
rawId: toBase64Url(rawId),
rawId: arrayToBase64Url(rawId),
response: {
authenticatorData: authData,
clientDataJSON: clientAuth.response.clientDataJSON,
signature: clientAuth.response.signature,
clientDataJSON: arrayToBase64Url(
Buffer.from(JSON.stringify(clientDataJSON)),
),
signature: signature,
},
type: "public-key",
},

16
src/views/TestView.vue

@ -222,6 +222,7 @@ export default class Help extends Vue {
authenticatorData?: ArrayBuffer;
credId?: Base64URLString;
jwt?: string;
payload = { a: 1 };
peerSetup?: PeerSetup;
publicKeyJwk?: JWK;
publicKeyBytes?: Uint8Array;
@ -264,10 +265,8 @@ export default class Help extends Vue {
const encodedUserId = Buffer.from(this.userId).toString("base64");
console.log("encodedUserId", encodedUserId);
const challenge = generateRandomBytes(32);
const cred = await registerCredential(
this.userId as Uint8Array,
challenge as Uint8Array,
this.userId as Uint8Array
);
console.log("public key", cred);
this.publicKeyJwk = cred.publicKeyJwk;
@ -280,9 +279,12 @@ export default class Help extends Vue {
public async create() {
const did = createPeerDid(this.publicKeyBytes as Uint8Array);
console.log("did", did);
const payload = { a: 1 };
this.peerSetup = new PeerSetup();
const rawJwt = await this.peerSetup.createJwt(payload, did, this.credId as string);
const rawJwt = await this.peerSetup.createJwt(
this.payload,
did,
this.credId as string,
);
console.log("raw jwt", rawJwt);
this.jwt = rawJwt
.replace(/\+/g, "-")
@ -296,15 +298,15 @@ export default class Help extends Vue {
alert("Create a JWT first.");
return;
}
const signature = this.jwt.split(".")[2];
const decoded = await verifyJwt(
this.jwt,
this.credId as Base64URLString,
this.rawId as Uint8Array,
this.peerSetup.authenticatorData as ArrayBuffer,
this.peerSetup.challenge as Uint8Array,
this.peerSetup.clientDataJsonDecoded,
this.publicKeyBytes as Uint8Array,
signature,
this.peerSetup.signature as Base64URLString,
);
console.log("decoded", decoded);
}

Loading…
Cancel
Save