Browse Source

refactor, and make separate testing buttons for different approaches

pull/116/head
Trent Larson 3 months ago
parent
commit
141a7fd563
  1. 490
      src/libs/didPeer.ts
  2. 147
      src/views/TestView.vue

490
src/libs/didPeer.ts

@ -1,20 +1,19 @@
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 { createJWS, JWTPayload } from "did-jwt"; import { JWTPayload } 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 { bytesToMultibase } from "@veramo/utils";
import { import {
startAuthentication, startAuthentication,
startRegistration, WebAuthnAbortService, startRegistration,
} from "@simplewebauthn/browser"; } from "@simplewebauthn/browser";
import { import {
generateAuthenticationOptions, generateAuthenticationOptions,
generateRegistrationOptions, generateRegistrationOptions,
verifyAuthenticationResponse, verifyAuthenticationResponse,
verifyRegistrationResponse, verifyRegistrationResponse,
VerifyRegistrationResponseOpts,
} from "@simplewebauthn/server"; } from "@simplewebauthn/server";
import { VerifyAuthenticationResponseOpts } from "@simplewebauthn/server/esm/authentication/verifyAuthenticationResponse"; import { VerifyAuthenticationResponseOpts } from "@simplewebauthn/server/esm/authentication/verifyAuthenticationResponse";
import { import {
@ -23,7 +22,6 @@ import {
PublicKeyCredentialRequestOptionsJSON, PublicKeyCredentialRequestOptionsJSON,
} from "@simplewebauthn/types"; } from "@simplewebauthn/types";
import { generateRandomBytes } from "@/libs/crypto";
import { getWebCrypto, unwrapEC2Signature } from "@/libs/crypto/passkeyHelpers"; import { getWebCrypto, unwrapEC2Signature } from "@/libs/crypto/passkeyHelpers";
export interface JWK { export interface JWK {
@ -54,13 +52,6 @@ export async function registerCredential(userId: Uint8Array) {
// Don't prompt users for additional information about the authenticator // Don't prompt users for additional information about the authenticator
// (Recommended for smoother UX) // (Recommended for smoother UX)
attestationType: "none", attestationType: "none",
// Prevent users from re-registering existing authenticators
// excludeCredentials: userPasskeys.map(passkey => ({
// id: passkey.id,
// // Optional
// transports: passkey.transports,
// })),
// // See "Guiding use of authenticators via authenticatorSelection" below
authenticatorSelection: { authenticatorSelection: {
// Defaults // Defaults
residentKey: "preferred", residentKey: "preferred",
@ -69,24 +60,21 @@ export async function registerCredential(userId: Uint8Array) {
authenticatorAttachment: "platform", authenticatorAttachment: "platform",
}, },
}); });
// someday, instead of simplwebauthn, we'll go direct: navigator.credentials.create with PublicKeyCredentialCreationOptions
// with pubKeyCredParams: { type: "public-key", alg: -7 }
const attResp = await startRegistration(options); const attResp = await startRegistration(options);
console.log("attResp", attResp);
const verification = await verifyRegistrationResponse({ const verification = await verifyRegistrationResponse({
response: attResp, response: attResp,
expectedChallenge: options.challenge, expectedChallenge: options.challenge,
expectedOrigin: window.location.origin, expectedOrigin: window.location.origin,
expectedRPID: window.location.hostname, expectedRPID: window.location.hostname,
}); });
console.log("verification", verification);
const jwkObj = cborDecode( // references for parsing auth data and getting the public key
verification.registrationInfo?.credentialPublicKey as Uint8Array, // https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/helpers/parseAuthenticatorData.ts#L11
); // https://chatgpt.com/share/78a5c91d-099d-46dc-aa6d-fc0c916509fa
console.log("jwkObj from verification", jwkObj); // https://chatgpt.com/share/3c13f061-6031-45bc-a2d7-3347c1e7a2d7
console.log(
"[1]==2 => kty EC",
"[3]==-7 => alg ES256",
"[-1]==1 => crv P-256",
);
const { publicKeyJwk } = cborToKeys( const { publicKeyJwk } = cborToKeys(
verification.registrationInfo?.credentialPublicKey as Uint8Array, verification.registrationInfo?.credentialPublicKey as Uint8Array,
); );
@ -101,99 +89,13 @@ export async function registerCredential(userId: Uint8Array) {
}; };
} }
export async function registerCredential2(userId: Uint8Array) {
const challenge = generateRandomBytes(32);
const publicKeyOptions: PublicKeyCredentialCreationOptions = {
challenge: challenge,
rp: {
name: "Time Safari",
id: window.location.hostname,
},
user: {
id: userId,
name: "Current-User",
displayName: "Current User",
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7, // ES256 algorithm
},
],
authenticatorSelection: {
authenticatorAttachment: "platform",
userVerification: "preferred",
},
timeout: 60000,
attestation: "direct",
};
const credential = await navigator.credentials.create({
publicKey: publicKeyOptions,
});
console.log("credential", credential);
console.log(credential?.id, " is the new ID base64-url-encoded");
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: arrayToBase64Url(attestationResponse?.attestationObject),
clientDataJSON: arrayToBase64Url(attestationResponse?.clientDataJSON),
},
clientExtensionResults: {},
type: "public-key",
},
expectedChallenge: arrayToBase64Url(challenge),
expectedOrigin: window.location.origin,
};
console.log("verfInput", verfInput);
const verification = await verifyRegistrationResponse(verfInput);
console.log("verification", verification);
// Parse the attestation response to get the public key
const clientDataJSON = attestationResponse.clientDataJSON;
console.log("clientDataJSON raw", clientDataJSON);
console.log(
"clientDataJSON dec",
new TextDecoder("utf-8").decode(clientDataJSON),
);
const attestationObject = cborDecode(
new Uint8Array(attestationResponse.attestationObject),
);
console.log("attestationObject", attestationObject);
const { publicKeyJwk, publicKeyBuffer } = cborToKeys(
verification.registrationInfo?.credentialPublicKey as Uint8Array,
);
//const publicKeyBytes = extractPublicKeyCose(attestationObject.authData);
//const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData);
return {
authData: attestationObject.authData,
credId: credential?.id,
rawId: credential?.rawId,
publicKeyJwk,
publicKeyBytes: publicKeyBuffer,
};
}
// parse authData
// here's one: https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/helpers/parseAuthenticatorData.ts#L11
// from https://chatgpt.com/c/0ce72fda-bc5d-42ff-a748-6022f6e39fa0
// from https://chatgpt.com/share/78a5c91d-099d-46dc-aa6d-fc0c916509fa
export function createPeerDid(publicKeyBytes: Uint8Array) { export function createPeerDid(publicKeyBytes: Uint8Array) {
// https://github.com/decentralized-identity/veramo/blob/next/packages/did-provider-peer/src/peer-did-provider.ts#L67 // https://github.com/decentralized-identity/veramo/blob/next/packages/did-provider-peer/src/peer-did-provider.ts#L67
//const provider = new PeerDIDProvider({ defaultKms: LOCAL_KMS_NAME }); //const provider = new PeerDIDProvider({ defaultKms: LOCAL_KMS_NAME });
const methodSpecificId = bytesToMultibase( const methodSpecificId = bytesToMultibase(
publicKeyBytes, publicKeyBytes,
"base58btc", "base58btc",
"secp256k1-pub", "p256-pub",
); );
return "did:peer:0" + methodSpecificId; return "did:peer:0" + methodSpecificId;
} }
@ -205,11 +107,11 @@ export class PeerSetup {
public clientDataJsonDecoded?: object; public clientDataJsonDecoded?: object;
public clientDataJsonBase64Url?: Base64URLString; public clientDataJsonBase64Url?: Base64URLString;
public signature?: Base64URLString; public signature?: Base64URLString;
public publicKeyJwk?: JWK;
public async createJwt(fullPayload: object, credentialId: string) {
const header: JWTPayload = { typ: "JWT", alg: "ES256" };
public async createJwtSimplewebauthn(
fullPayload: object,
credentialId: string,
) {
this.challenge = new Uint8Array(Buffer.from(JSON.stringify(fullPayload))); this.challenge = new Uint8Array(Buffer.from(JSON.stringify(fullPayload)));
// const payloadHash: Uint8Array = sha256(this.challenge); // const payloadHash: Uint8Array = sha256(this.challenge);
const options: PublicKeyCredentialRequestOptionsJSON = const options: PublicKeyCredentialRequestOptionsJSON =
@ -236,17 +138,27 @@ export class PeerSetup {
// 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;
const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64"); // Our custom type of JWANT means the signature is based on a concatenation of the two Webauthn properties
const payloadBase64 = clientAuth.response.clientDataJSON; const header: JWTPayload = { typ: "JWANT", alg: "ES256" };
const headerBase64 = Buffer.from(JSON.stringify(header))
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const dataInJwt = {
AuthenticationData: this.authenticatorDataBase64Url,
ClientDataJSON: this.clientDataJsonBase64Url,
};
const dataInJwtString = JSON.stringify(dataInJwt);
const payloadBase64 = Buffer.from(dataInJwtString).toString("base64");
const signature = clientAuth.response.signature; const signature = clientAuth.response.signature;
return headerBase64 + "." + payloadBase64 + "." + signature; return headerBase64 + "." + payloadBase64 + "." + signature;
} }
public async createJwt2(fullPayload: object, credentialId: string) { public async createJwtNavigator(fullPayload: object, credentialId: string) {
const header: JWTPayload = { typ: "JWT", alg: "ES256" };
const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64");
const dataToSignString = JSON.stringify(fullPayload); const dataToSignString = JSON.stringify(fullPayload);
const dataToSignBuffer = Buffer.from(dataToSignString); const dataToSignBuffer = Buffer.from(dataToSignString);
@ -256,60 +168,32 @@ export class PeerSetup {
publicKey: { publicKey: {
challenge: this.challenge.buffer, challenge: this.challenge.buffer,
rpID: window.location.hostname, rpID: window.location.hostname,
//allowCredentials: [{ id: credentialId, type: "public-key" }],
userVerification: "preferred", userVerification: "preferred",
//extensions: fullPayload,
}, },
}; };
// console.log("lower authentication options", options);
// console.log("lower options in base64", {
// publicKey: {
// challenge: bufferToBase64URLString(options.publicKey.challenge),
// rpID: window.location.hostname,
// userVerification: "preferred",
// },
// });
const credential = await navigator.credentials.get(options); const credential = await navigator.credentials.get(options);
// console.log("lower credential get", credential); // console.log("nav credential get", credential);
// console.log("lower credential get in base64", {
// id: credential?.id, this.authenticatorDataBase64Url = bufferToBase64URLString(
// rawId: bufferToBase64URLString(credential?.rawId), credential?.response.authenticatorData,
// response: { );
// authenticatorData: bufferToBase64URLString(
// credential?.response.authenticatorData,
// ),
// clientDataJSON: bufferToBase64URLString(
// credential?.response.clientDataJSON,
// ),
// signature: bufferToBase64URLString(credential?.response.signature),
// },
// type: credential?.type,
// });
const authenticatorAssertionResponse = credential?.response;
this.authenticatorDataBase64Url =
authenticatorAssertionResponse.authenticatorData;
this.authenticatorData = Buffer.from( this.authenticatorData = Buffer.from(
this.authenticatorDataBase64Url as Base64URLString, this.authenticatorDataBase64Url as Base64URLString,
"base64", "base64",
).buffer; ).buffer;
// console.log("lower authenticator data", this.authenticatorData);
this.clientDataJsonBase64Url = this.clientDataJsonBase64Url = bufferToBase64URLString(
authenticatorAssertionResponse.clientDataJSON; credential?.response.clientDataJSON,
);
this.clientDataJsonDecoded = JSON.parse( this.clientDataJsonDecoded = JSON.parse(
new TextDecoder("utf-8").decode( new TextDecoder("utf-8").decode(credential?.response.clientDataJSON),
authenticatorAssertionResponse.clientDataJSON,
),
); );
// console.log("lower clientDataJSON decoded", this.clientDataJsonDecoded);
const origSignature = Buffer.from( // Our custom type of JWANT means the signature is based on a concatenation of the two Webauthn properties
authenticatorAssertionResponse.signature, const header: JWTPayload = { typ: "JWANT", alg: "ES256" };
).toString("base64"); const headerBase64 = Buffer.from(JSON.stringify(header))
this.signature = origSignature .toString("base64")
.replace(/\+/g, "-") .replace(/\+/g, "-")
.replace(/\//g, "_") .replace(/\//g, "_")
.replace(/=+$/, ""); .replace(/=+$/, "");
@ -319,122 +203,159 @@ export class PeerSetup {
ClientDataJSON: this.clientDataJsonBase64Url, ClientDataJSON: this.clientDataJsonBase64Url,
}; };
const dataInJwtString = JSON.stringify(dataInJwt); const dataInJwtString = JSON.stringify(dataInJwt);
const payloadBase64 = Buffer.from(dataInJwtString).toString("base64"); const payloadBase64 = Buffer.from(dataInJwtString)
const jwt = headerBase64 + "." + payloadBase64 + "." + this.signature; .toString("base64")
return jwt; .replace(/\+/g, "-")
} .replace(/\//g, "_")
.replace(/=+$/, "");
// Attempted with JWS, but it will not match because it signs different content (header + payload)
//const signer = await this.webAuthnES256KSigner(credentialId);
//const jwt = createJWS(fullPayload, signer, { typ: "JWT", alg: "ES256" });
async webAuthnES256KSigner(credentialID: string) {
return async (data: string | Uint8Array) => {
const signature = await this.generateWebAuthnSignature(
data,
credentialID,
);
// This converts from the browser ArrayBuffer to a Node.js Buffer, which is a requirement for the asn1 library.
const signatureBuffer = Buffer.from(signature);
console.log("lower signature inside signer", signature);
console.log("lower buffer signature inside signer", signatureBuffer);
console.log("lower base64 buffer signature inside signer", signatureBuffer.toString("base64"));
// Decode the DER-encoded signature to extract R and S values
const reader = new asn1.BerReader(signatureBuffer);
console.log("lower after reader");
reader.readSequence();
console.log("lower after read sequence");
const r = reader.readString(asn1.Ber.Integer, true);
console.log("lower after r");
const s = reader.readString(asn1.Ber.Integer, true);
console.log("lower after r & s");
// Ensure R and S are 32 bytes each
const rBuffer = Buffer.from(r);
const sBuffer = Buffer.from(s);
console.log("lower after rBuffer & sBuffer", rBuffer, sBuffer);
const rWithoutPrefix = rBuffer.length > 32 ? rBuffer.slice(1) : rBuffer;
const sWithoutPrefix = sBuffer.length > 32 ? sBuffer.slice(1) : sBuffer;
const rPadded =
rWithoutPrefix.length < 32
? Buffer.concat([Buffer.alloc(32 - rWithoutPrefix.length), rBuffer])
: rWithoutPrefix;
const sPadded =
rWithoutPrefix.length < 32
? Buffer.concat([Buffer.alloc(32 - sWithoutPrefix.length), sBuffer])
: sWithoutPrefix;
// Concatenate R and S to form the 64-byte array (ECDSA signature format expected by JWT)
const combinedSignature = Buffer.concat([rPadded, sPadded]);
console.log(
"lower combinedSignature",
combinedSignature.length,
combinedSignature,
);
const combSig64 = combinedSignature.toString("base64"); const origSignature = Buffer.from(credential?.response.signature)
console.log("lower combSig64", combSig64); .toString("base64")
const combSig64Url = combSig64 this.signature = origSignature
.replace(/\+/g, "-") .replace(/\+/g, "-")
.replace(/\//g, "_") .replace(/\//g, "_")
.replace(/=+$/, ""); .replace(/=+$/, "");
console.log("lower combSig64Url", combSig64Url);
return combSig64Url; const jwt = headerBase64 + "." + payloadBase64 + "." + this.signature;
}; return jwt;
} }
async generateWebAuthnSignature( // return a low-level signing function, similar to createJWS approach
dataToSign: string | Uint8Array, // from Signer interface // async webAuthnES256KSigner(credentialID: string) {
credentialId: string, // return async (data: string | Uint8Array) => {
) { // // get signature from WebAuthn
if (!(dataToSign instanceof Uint8Array)) { // const signature = await this.generateWebAuthnSignature(data);
console.log("lower dataToSign & typeof ", typeof dataToSign, dataToSign); //
dataToSign = new Uint8Array(base64URLStringToBuffer(dataToSign)); // // This converts from the browser ArrayBuffer to a Node.js Buffer, which is a requirement for the asn1 library.
// const signatureBuffer = Buffer.from(signature);
// console.log("lower signature inside signer", signature);
// console.log("lower buffer signature inside signer", signatureBuffer);
// console.log("lower base64 buffer signature inside signer", signatureBuffer.toString("base64"));
// // Decode the DER-encoded signature to extract R and S values
// const reader = new asn1.BerReader(signatureBuffer);
// console.log("lower after reader");
// reader.readSequence();
// console.log("lower after read sequence");
// const r = reader.readString(asn1.Ber.Integer, true);
// console.log("lower after r");
// const s = reader.readString(asn1.Ber.Integer, true);
// console.log("lower after r & s");
//
// // Ensure R and S are 32 bytes each
// const rBuffer = Buffer.from(r);
// const sBuffer = Buffer.from(s);
// console.log("lower after rBuffer & sBuffer", rBuffer, sBuffer);
// const rWithoutPrefix = rBuffer.length > 32 ? rBuffer.slice(1) : rBuffer;
// const sWithoutPrefix = sBuffer.length > 32 ? sBuffer.slice(1) : sBuffer;
// const rPadded =
// rWithoutPrefix.length < 32
// ? Buffer.concat([Buffer.alloc(32 - rWithoutPrefix.length), rBuffer])
// : rWithoutPrefix;
// const sPadded =
// rWithoutPrefix.length < 32
// ? Buffer.concat([Buffer.alloc(32 - sWithoutPrefix.length), sBuffer])
// : sWithoutPrefix;
//
// // Concatenate R and S to form the 64-byte array (ECDSA signature format expected by JWT)
// const combinedSignature = Buffer.concat([rPadded, sPadded]);
// console.log(
// "lower combinedSignature",
// combinedSignature.length,
// combinedSignature,
// );
//
// const combSig64 = combinedSignature.toString("base64");
// console.log("lower combSig64", combSig64);
// const combSig64Url = combSig64
// .replace(/\+/g, "-")
// .replace(/\//g, "_")
// .replace(/=+$/, "");
// console.log("lower combSig64Url", combSig64Url);
// return combSig64Url;
// };
// }
} }
console.log("lower credentialId", credentialId);
this.challenge = dataToSign; export async function verifyJwtSimplewebauthn(
const options = { jwt: string,
publicKey: { credId: Base64URLString,
challenge: this.challenge.buffer, rawId: Uint8Array,
rpID: window.location.hostname, authenticatorData: ArrayBuffer,
//allowCredentials: [{ id: credentialId, type: "public-key" }], authenticatorDataBase64Url: Base64URLString,
userVerification: "preferred", challenge: Uint8Array,
//extensions: fullPayload, clientDataJSON: object,
clientDataJsonBase64Url: Base64URLString,
publicKeyBytes: Uint8Array,
publicKeyJwk: JWK,
signature: Base64URLString,
) {
const authData = arrayToBase64Url(Buffer.from(authenticatorData));
const authOpts: VerifyAuthenticationResponseOpts = {
authenticator: {
credentialID: credId,
credentialPublicKey: publicKeyBytes,
counter: 0,
},
expectedChallenge: arrayToBase64Url(challenge),
expectedOrigin: window.location.origin,
expectedRPID: window.location.hostname,
response: {
authenticatorAttachment: "platform",
clientExtensionResults: {},
id: credId,
rawId: arrayToBase64Url(rawId),
response: {
authenticatorData: authData,
clientDataJSON: arrayToBase64Url(
Buffer.from(JSON.stringify(clientDataJSON)),
),
signature: signature,
},
type: "public-key",
}, },
}; };
const verification = await verifyAuthenticationResponse(authOpts);
return verification.verified;
}
// console.log("lower authentication options", options); // I'd love to use this but it doesn't verify.
const assertion = await navigator.credentials.get(options); // Pequires:
// console.log("lower credential get", assertion); // 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);
const authenticatorAssertionResponse = assertion?.response; // Hash the client data
const hash = sha256(clientDataFromBase);
this.authenticatorDataBase64Url = // Construct the preimage
authenticatorAssertionResponse.authenticatorData; const preimage = Buffer.concat([authDataFromBase, hash]);
this.authenticatorData = Buffer.from(
this.authenticatorDataBase64Url as Base64URLString,
"base64",
).buffer;
// console.log("lower authenticator data", this.authenticatorData);
this.clientDataJsonBase64Url = const isValid = p256.verify(
authenticatorAssertionResponse.clientDataJSON; finalSigBuffer,
this.clientDataJsonDecoded = JSON.parse( new Uint8Array(preimage),
new TextDecoder("utf-8").decode( publicKeyBytes,
authenticatorAssertionResponse.clientDataJSON,
),
); );
// console.log("lower clientDataJSON decoded", this.clientDataJsonDecoded); return isValid;
this.signature = Buffer.from(
authenticatorAssertionResponse.signature,
).toString("base64");
return this.signature;
}
} }
export async function verifyJwt( export async function verifyJwtWebCrypto(
jwt: string, jwt: string,
credId: Base64URLString, credId: Base64URLString,
rawId: Uint8Array, rawId: Uint8Array,
@ -447,35 +368,6 @@ export async function verifyJwt(
publicKeyJwk: JWK, publicKeyJwk: JWK,
signature: Base64URLString, signature: Base64URLString,
) { ) {
// const authData = arrayToBase64Url(Buffer.from(authenticatorData));
// const authOpts: VerifyAuthenticationResponseOpts = {
// authenticator: {
// credentialID: credId,
// credentialPublicKey: publicKeyBytes,
// counter: 0,
// },
// expectedChallenge: arrayToBase64Url(challenge),
// expectedOrigin: window.location.origin,
// expectedRPID: window.location.hostname,
// response: {
// authenticatorAttachment: "platform",
// clientExtensionResults: {},
// id: credId,
// rawId: arrayToBase64Url(rawId),
// response: {
// authenticatorData: authData,
// clientDataJSON: arrayToBase64Url(
// Buffer.from(JSON.stringify(clientDataJSON)),
// ),
// signature: signature,
// },
// type: "public-key",
// },
// };
// console.log("auth opts", authOpts);
// const verification = await verifyAuthenticationResponse(authOpts);
// console.log("auth verification", verification);
const authDataFromBase = Buffer.from(authenticatorDataBase64Url, "base64"); const authDataFromBase = Buffer.from(authenticatorDataBase64Url, "base64");
const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64"); const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64");
const sigBuffer = Buffer.from(signature, "base64"); const sigBuffer = Buffer.from(signature, "base64");
@ -487,18 +379,6 @@ export async function verifyJwt(
// Construct the preimage // Construct the preimage
const preimage = Buffer.concat([authDataFromBase, hash]); const preimage = Buffer.concat([authDataFromBase, hash]);
// console.log("finalSigBuffer", finalSigBuffer);
// console.log("preimage", preimage);
// console.log("publicKeyBytes", publicKeyBytes);
// This uses p256 from @noble/curves/p256, which I would prefer but it's returning false.
// const isValid = p256.verify(
// finalSigBuffer,
// new Uint8Array(preimage),
// publicKeyBytes,
// );
// console.log("isValid", isValid);
const WebCrypto = await getWebCrypto(); const WebCrypto = await getWebCrypto();
const verifyAlgorithm = { const verifyAlgorithm = {
name: "ECDSA", name: "ECDSA",
@ -515,17 +395,12 @@ export async function verifyJwt(
false, false,
["verify"], ["verify"],
); );
// console.log("verifyAlgorithm", verifyAlgorithm);
// console.log("publicKeyCryptoKey", publicKeyCryptoKey);
// console.log("finalSigBuffer", finalSigBuffer);
// console.log("preimage", preimage);
const verified = await WebCrypto.subtle.verify( const verified = await WebCrypto.subtle.verify(
verifyAlgorithm, verifyAlgorithm,
publicKeyCryptoKey, publicKeyCryptoKey,
finalSigBuffer, finalSigBuffer,
preimage, preimage,
); );
// console.log("verified", verified);
return verified; return verified;
} }
@ -628,7 +503,6 @@ function base64URLStringToBuffer(base64URLString) {
function cborToKeys(publicKeyBytes: Uint8Array) { function cborToKeys(publicKeyBytes: Uint8Array) {
const jwkObj = cborDecode(publicKeyBytes); const jwkObj = cborDecode(publicKeyBytes);
console.log("jwkObj from verification", jwkObj);
if ( if (
jwkObj[1] != 2 || // kty "EC" jwkObj[1] != 2 || // kty "EC"
jwkObj[3] != -7 || // alg "ES256" jwkObj[3] != -7 || // alg "ES256"
@ -663,7 +537,7 @@ async function pemToCryptoKey(pem: string) {
for (let i = 0; i < binaryDerString.length; i++) { for (let i = 0; i < binaryDerString.length; i++) {
binaryDer[i] = binaryDerString.charCodeAt(i); binaryDer[i] = binaryDerString.charCodeAt(i);
} }
console.log("binaryDer", binaryDer.buffer); // console.log("binaryDer", binaryDer.buffer);
return await window.crypto.subtle.importKey( return await window.crypto.subtle.importKey(
"spki", "spki",
binaryDer.buffer, binaryDer.buffer,

147
src/views/TestView.vue

@ -172,25 +172,52 @@
<div class="mt-8"> <div class="mt-8">
<h2 class="text-xl font-bold mb-4">Passkeys</h2> <h2 class="text-xl font-bold mb-4">Passkeys</h2>
<div>
Register
<button <button
@click="register()" @click="register()"
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2" class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
> >
Register Simplewebauthn
</button> </button>
</div>
<div>
Create
<button <button
@click="create()" @click="createJwtSimplewebauthn()"
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2" class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
> >
Create Simplewebauthn
</button> </button>
<button <button
@click="verify()" @click="createJwtNavigator()"
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2" class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
> >
Navigator
</button>
</div>
<div>
Verify Verify
<button
@click="verifySimplewebauthn()"
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
>
Simplewebauthn
</button>
<button
@click="verifyWebCrypto()"
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
>
WebCrypto
</button>
<button
@click="verifyP256()"
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
>
p256 - broken
</button> </button>
</div> </div>
</div>
</section> </section>
</template> </template>
@ -206,10 +233,10 @@ import {
createPeerDid, createPeerDid,
JWK, JWK,
PeerSetup, PeerSetup,
registerCredential, registerCredential, verifyJwtP256,
verifyJwt, verifyJwtSimplewebauthn,
verifyJwtWebCrypto,
} from "@/libs/didPeer"; } from "@/libs/didPeer";
import { Buffer } from "buffer";
import { JWTPayload } from "did-jwt"; import { JWTPayload } from "did-jwt";
const inputFileNameRef = ref<Blob>(); const inputFileNameRef = ref<Blob>();
@ -220,21 +247,12 @@ export default class Help extends Vue {
fileName?: string; fileName?: string;
// for passkeys // for passkeys
authenticatorData?: ArrayBuffer;
credId?: Base64URLString; credId?: Base64URLString;
jwt?: string; jwt?: string;
jwt2?: string;
payload = {
"@context": "https://schema.org",
type: "GiveAction",
description: "pizza",
};
peerSetup?: PeerSetup; peerSetup?: PeerSetup;
peerSetup2?: PeerSetup;
publicKeyJwk?: JWK; publicKeyJwk?: JWK;
publicKeyBytes?: Uint8Array; publicKeyBytes?: Uint8Array;
rawId?: Uint8Array; rawId?: Uint8Array;
savedCredentialId = "";
userId?: ArrayBuffer; userId?: ArrayBuffer;
async uploadFile(event: Event) { async uploadFile(event: Event) {
@ -269,8 +287,6 @@ export default class Help extends Vue {
public async register() { public async register() {
this.userId = generateRandomBytes(16).buffer; this.userId = generateRandomBytes(16).buffer;
const encodedUserId = Buffer.from(this.userId).toString("base64");
console.log("encodedUserId", encodedUserId);
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);
@ -278,55 +294,76 @@ export default class Help extends Vue {
this.publicKeyBytes = cred.publicKeyBytes; this.publicKeyBytes = cred.publicKeyBytes;
this.credId = cred.credId as string; this.credId = cred.credId as string;
this.rawId = cred.rawId as Uint8Array; this.rawId = cred.rawId as Uint8Array;
this.savedCredentialId = this.credId;
} }
public async create() { public async createJwtSimplewebauthn() {
console.log("Starting a create");
const did = createPeerDid(this.publicKeyBytes as Uint8Array); const did = createPeerDid(this.publicKeyBytes as Uint8Array);
// console.log("did", did); console.log("generated peer did", did);
const payload = {
"@context": "https://schema.org",
type: "GiveAction",
description: "pizza",
};
// from createJWT in did-jwt/src/JWT.ts // from createJWT in did-jwt/src/JWT.ts
const timestamps: Partial<JWTPayload> = { const timestamps: Partial<JWTPayload> = {
iat: Math.floor(Date.now() / 1000), iat: Math.floor(Date.now() / 1000),
exp: undefined, exp: undefined,
}; };
const fullPayload = { ...timestamps, ...this.payload, did }; const fullPayload = { ...timestamps, ...payload, did };
this.peerSetup = new PeerSetup(); this.peerSetup = new PeerSetup();
const rawJwt = await this.peerSetup.createJwt( this.jwt = await this.peerSetup.createJwtSimplewebauthn(
fullPayload, fullPayload,
this.credId as string, this.credId as string,
); );
//console.log("simple raw jwt", rawJwt);
this.jwt = rawJwt
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
console.log("simple jwt4url", this.jwt); console.log("simple jwt4url", this.jwt);
// console.log("simple peerSetup", this.peerSetup); }
public async createJwtNavigator() {
const did = createPeerDid(this.publicKeyBytes as Uint8Array);
console.log("generated peer did", did);
this.peerSetup2 = new PeerSetup(); const payload = {
const rawJwt2 = await this.peerSetup2.createJwt2( "@context": "https://schema.org",
type: "GiveAction",
description: "pizza",
};
// from createJWT in did-jwt/src/JWT.ts
const timestamps: Partial<JWTPayload> = {
iat: Math.floor(Date.now() / 1000),
exp: undefined,
};
const fullPayload = { ...timestamps, ...payload, did };
this.peerSetup = new PeerSetup();
this.jwt = await this.peerSetup.createJwtNavigator(
fullPayload, fullPayload,
this.credId as string, this.credId as string,
); );
// console.log("lower raw jwt2", rawJwt2); console.log("lower jwt4url", this.jwt);
this.jwt2 = rawJwt2
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
console.log("lower jwt4url2", this.jwt2);
// console.log("lower peerSetup2", this.peerSetup2);
} }
public async verify() { public async verifyP256() {
if (!this.jwt || !this.peerSetup) { const decoded = await verifyJwtP256(
alert("Create a JWT first."); this.jwt as string,
return; this.credId as Base64URLString,
this.rawId as Uint8Array,
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);
} }
const decoded = await verifyJwt(
this.jwt, public async verifySimplewebauthn() {
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.peerSetup.authenticatorData as ArrayBuffer, this.peerSetup.authenticatorData as ArrayBuffer,
@ -339,21 +376,23 @@ export default class Help extends Vue {
this.peerSetup.signature as Base64URLString, this.peerSetup.signature as Base64URLString,
); );
console.log("decoded", decoded); console.log("decoded", decoded);
}
const decoded2 = await verifyJwt( public async verifyWebCrypto() {
this.jwt2 as string, 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.peerSetup2.authenticatorData as ArrayBuffer, this.peerSetup.authenticatorData as ArrayBuffer,
this.peerSetup2.authenticatorDataBase64Url as Base64URLString, this.peerSetup.authenticatorDataBase64Url as Base64URLString,
this.peerSetup2.challenge as Uint8Array, this.peerSetup.challenge as Uint8Array,
this.peerSetup2.clientDataJsonDecoded, this.peerSetup.clientDataJsonDecoded,
this.peerSetup2.clientDataJsonBase64Url as Base64URLString, this.peerSetup.clientDataJsonBase64Url as Base64URLString,
this.publicKeyBytes as Uint8Array, this.publicKeyBytes as Uint8Array,
this.publicKeyJwk as JWK, this.publicKeyJwk as JWK,
this.peerSetup2.signature as Base64URLString, this.peerSetup.signature as Base64URLString,
); );
console.log("decoded2", decoded2); console.log("decoded", decoded);
} }
} }
</script> </script>

Loading…
Cancel
Save