fully split the signature creation from the verification
This commit is contained in:
38
package-lock.json
generated
38
package-lock.json
generated
@@ -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"
|
||||
|
||||
@@ -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(/=+$/, "");
|
||||
}
|
||||
|
||||
export async function registerCredential() {
|
||||
function arrayToBase64Url(anything: Uint8Array) {
|
||||
return toBase64Url(Buffer.from(anything).toString("base64"));
|
||||
}
|
||||
|
||||
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);
|
||||
// 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 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));
|
||||
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",
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user