Browse Source

fully split the signature creation from the verification

pull/116/head
Trent Larson 5 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/" "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": { "node_modules/@noble/ed25519": {
"version": "1.7.3", "version": "1.7.3",
"funding": [ "funding": [
@ -6030,8 +6041,9 @@
"optional": true "optional": true
}, },
"node_modules/@noble/hashes": { "node_modules/@noble/hashes": {
"version": "1.3.2", "version": "1.4.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
}, },
@ -8197,6 +8209,17 @@
"url": "https://paulmillr.com/funding/" "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": { "node_modules/@segment/loosely-validate-event": {
"version": "2.0.0", "version": "2.0.0",
"optional": true, "optional": true,
@ -12718,6 +12741,17 @@
"node": ">=14.0.0" "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": { "node_modules/ethers/node_modules/@types/node": {
"version": "18.15.13", "version": "18.15.13",
"license": "MIT" "license": "MIT"

161
src/libs/didPeer.ts

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

16
src/views/TestView.vue

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

Loading…
Cancel
Save