Browse Source

refactor, and make separate testing buttons for different approaches

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

502
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 =
@ -233,83 +135,65 @@ export class PeerSetup {
"utf-8", "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;
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);
//console.log("lower credentialId", credentialId); // console.log("lower credentialId", credentialId);
this.challenge = new Uint8Array(dataToSignBuffer); this.challenge = new Uint8Array(dataToSignBuffer);
const options = { const options = {
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)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const origSignature = Buffer.from(credential?.response.signature)
.toString("base64")
this.signature = origSignature
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const jwt = headerBase64 + "." + payloadBase64 + "." + this.signature; const jwt = headerBase64 + "." + payloadBase64 + "." + this.signature;
return jwt; return jwt;
} }
// Attempted with JWS, but it will not match because it signs different content (header + payload) // return a low-level signing function, similar to createJWS approach
//const signer = await this.webAuthnES256KSigner(credentialId); // async webAuthnES256KSigner(credentialID: string) {
//const jwt = createJWS(fullPayload, signer, { typ: "JWT", alg: "ES256" }); // return async (data: string | Uint8Array) => {
async webAuthnES256KSigner(credentialID: string) { // // get signature from WebAuthn
return async (data: string | Uint8Array) => { // const signature = await this.generateWebAuthnSignature(data);
const signature = await this.generateWebAuthnSignature( //
data, // // This converts from the browser ArrayBuffer to a Node.js Buffer, which is a requirement for the asn1 library.
credentialID, // const signatureBuffer = Buffer.from(signature);
); // console.log("lower signature inside signer", signature);
// console.log("lower buffer signature inside signer", signatureBuffer);
// This converts from the browser ArrayBuffer to a Node.js Buffer, which is a requirement for the asn1 library. // console.log("lower base64 buffer signature inside signer", signatureBuffer.toString("base64"));
const signatureBuffer = Buffer.from(signature); // // Decode the DER-encoded signature to extract R and S values
console.log("lower signature inside signer", signature); // const reader = new asn1.BerReader(signatureBuffer);
console.log("lower buffer signature inside signer", signatureBuffer); // console.log("lower after reader");
console.log("lower base64 buffer signature inside signer", signatureBuffer.toString("base64")); // reader.readSequence();
// Decode the DER-encoded signature to extract R and S values // console.log("lower after read sequence");
const reader = new asn1.BerReader(signatureBuffer); // const r = reader.readString(asn1.Ber.Integer, true);
console.log("lower after reader"); // console.log("lower after r");
reader.readSequence(); // const s = reader.readString(asn1.Ber.Integer, true);
console.log("lower after read sequence"); // console.log("lower after r & s");
const r = reader.readString(asn1.Ber.Integer, true); //
console.log("lower after r"); // // Ensure R and S are 32 bytes each
const s = reader.readString(asn1.Ber.Integer, true); // const rBuffer = Buffer.from(r);
console.log("lower after r & s"); // const sBuffer = Buffer.from(s);
// console.log("lower after rBuffer & sBuffer", rBuffer, sBuffer);
// Ensure R and S are 32 bytes each // const rWithoutPrefix = rBuffer.length > 32 ? rBuffer.slice(1) : rBuffer;
const rBuffer = Buffer.from(r); // const sWithoutPrefix = sBuffer.length > 32 ? sBuffer.slice(1) : sBuffer;
const sBuffer = Buffer.from(s); // const rPadded =
console.log("lower after rBuffer & sBuffer", rBuffer, sBuffer); // rWithoutPrefix.length < 32
const rWithoutPrefix = rBuffer.length > 32 ? rBuffer.slice(1) : rBuffer; // ? Buffer.concat([Buffer.alloc(32 - rWithoutPrefix.length), rBuffer])
const sWithoutPrefix = sBuffer.length > 32 ? sBuffer.slice(1) : sBuffer; // : rWithoutPrefix;
const rPadded = // const sPadded =
rWithoutPrefix.length < 32 // rWithoutPrefix.length < 32
? Buffer.concat([Buffer.alloc(32 - rWithoutPrefix.length), rBuffer]) // ? Buffer.concat([Buffer.alloc(32 - sWithoutPrefix.length), sBuffer])
: rWithoutPrefix; // : sWithoutPrefix;
const sPadded = //
rWithoutPrefix.length < 32 // // Concatenate R and S to form the 64-byte array (ECDSA signature format expected by JWT)
? Buffer.concat([Buffer.alloc(32 - sWithoutPrefix.length), sBuffer]) // const combinedSignature = Buffer.concat([rPadded, sPadded]);
: sWithoutPrefix; // console.log(
// "lower combinedSignature",
// Concatenate R and S to form the 64-byte array (ECDSA signature format expected by JWT) // combinedSignature.length,
const combinedSignature = Buffer.concat([rPadded, sPadded]); // combinedSignature,
console.log( // );
"lower combinedSignature", //
combinedSignature.length, // const combSig64 = combinedSignature.toString("base64");
combinedSignature, // console.log("lower combSig64", combSig64);
); // const combSig64Url = combSig64
// .replace(/\+/g, "-")
const combSig64 = combinedSignature.toString("base64"); // .replace(/\//g, "_")
console.log("lower combSig64", combSig64); // .replace(/=+$/, "");
const combSig64Url = combSig64 // console.log("lower combSig64Url", combSig64Url);
.replace(/\+/g, "-") // return combSig64Url;
.replace(/\//g, "_") // };
.replace(/=+$/, ""); // }
console.log("lower combSig64Url", combSig64Url); }
return combSig64Url;
};
}
async generateWebAuthnSignature( export async function verifyJwtSimplewebauthn(
dataToSign: string | Uint8Array, // from Signer interface jwt: string,
credentialId: string, credId: Base64URLString,
) { rawId: Uint8Array,
if (!(dataToSign instanceof Uint8Array)) { authenticatorData: ArrayBuffer,
console.log("lower dataToSign & typeof ", typeof dataToSign, dataToSign); authenticatorDataBase64Url: Base64URLString,
dataToSign = new Uint8Array(base64URLStringToBuffer(dataToSign)); challenge: Uint8Array,
} clientDataJSON: object,
console.log("lower credentialId", credentialId); clientDataJsonBase64Url: Base64URLString,
this.challenge = dataToSign; publicKeyBytes: Uint8Array,
const options = { publicKeyJwk: JWK,
publicKey: { signature: Base64URLString,
challenge: this.challenge.buffer, ) {
rpID: window.location.hostname, const authData = arrayToBase64Url(Buffer.from(authenticatorData));
//allowCredentials: [{ id: credentialId, type: "public-key" }], const authOpts: VerifyAuthenticationResponseOpts = {
userVerification: "preferred", authenticator: {
//extensions: fullPayload, 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("lower authentication options", options); };
const assertion = await navigator.credentials.get(options); const verification = await verifyAuthenticationResponse(authOpts);
// console.log("lower credential get", assertion); return verification.verified;
}
const authenticatorAssertionResponse = assertion?.response;
this.authenticatorDataBase64Url = // I'd love to use this but it doesn't verify.
authenticatorAssertionResponse.authenticatorData; // Pequires:
this.authenticatorData = Buffer.from( // npm install @noble/curves
this.authenticatorDataBase64Url as Base64URLString, // ... and this import:
"base64", // import { p256 } from "@noble/curves/p256";
).buffer; export async function verifyJwtP256(
// console.log("lower authenticator data", this.authenticatorData); 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);
this.clientDataJsonBase64Url = // Hash the client data
authenticatorAssertionResponse.clientDataJSON; const hash = sha256(clientDataFromBase);
this.clientDataJsonDecoded = JSON.parse(
new TextDecoder("utf-8").decode(
authenticatorAssertionResponse.clientDataJSON,
),
);
// console.log("lower clientDataJSON decoded", this.clientDataJsonDecoded);
this.signature = Buffer.from( // Construct the preimage
authenticatorAssertionResponse.signature, const preimage = Buffer.concat([authDataFromBase, hash]);
).toString("base64");
return this.signature; const isValid = p256.verify(
} finalSigBuffer,
new Uint8Array(preimage),
publicKeyBytes,
);
return isValid;
} }
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,

173
src/views/TestView.vue

@ -172,24 +172,51 @@
<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>
<button <div>
@click="register()"
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
>
Register Register
</button> <button
<button @click="register()"
@click="create()" 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" >
> Simplewebauthn
</button>
</div>
<div>
Create Create
</button> <button
<button @click="createJwtSimplewebauthn()"
@click="verify()" 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" >
> Simplewebauthn
</button>
<button
@click="createJwtNavigator()"
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
>
Navigator
</button>
</div>
<div>
Verify Verify
</button> <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>
</div>
</div> </div>
</section> </section>
</template> </template>
@ -206,11 +233,11 @@ 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);
const payload = {
"@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.peerSetup2 = new PeerSetup(); this.peerSetup = new PeerSetup();
const rawJwt2 = await this.peerSetup2.createJwt2( 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,
const decoded = await verifyJwt( this.peerSetup.authenticatorData as ArrayBuffer,
this.jwt, 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);
}
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