Browse Source

add signing by non-simplewebauthn code

pull/116/head
Trent Larson 5 months ago
parent
commit
bbf1c17e62
  1. 361
      src/libs/didPeer.ts
  2. 55
      src/views/TestView.vue

361
src/libs/didPeer.ts

@ -7,7 +7,7 @@ import { sha256 } from "ethereum-cryptography/sha256.js";
import { bytesToMultibase } from "@veramo/utils";
import {
startAuthentication,
startRegistration,
startRegistration, WebAuthnAbortService,
} from "@simplewebauthn/browser";
import {
generateAuthenticationOptions,
@ -207,55 +207,33 @@ export class PeerSetup {
public signature?: Base64URLString;
public publicKeyJwk?: JWK;
public async createJwt(
payload: object,
issuerDid: string,
credentialId: string,
) {
public async createJwt(fullPayload: object, 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)),
);
this.challenge = new Uint8Array(Buffer.from(JSON.stringify(fullPayload)));
// const payloadHash: Uint8Array = sha256(this.challenge);
const options: PublicKeyCredentialRequestOptionsJSON =
await generateAuthenticationOptions({
challenge: payloadHash,
challenge: this.challenge,
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);
// console.log("simple authentication options", options);
const clientAuth = await startAuthentication(options);
console.log("custom clientAuth", clientAuth);
// console.log("simple credential get", clientAuth);
this.authenticatorDataBase64Url = clientAuth.response.authenticatorData;
this.authenticatorData = Buffer.from(
clientAuth.response.authenticatorData,
"base64",
).buffer;
this.challenge = payloadHash;
this.clientDataJsonBase64Url = clientAuth.response.clientDataJSON;
this.clientDataJsonDecoded = JSON.parse(
Buffer.from(clientAuth.response.clientDataJSON, "base64").toString(
"utf-8",
),
);
console.log("authenticatorData for signing", this.authenticatorData);
console.log(
"clientDataJSON for signing",
Buffer.from(clientAuth.response.clientDataJSON, "base64"),
);
//console.log("simple authenticatorData for signing", this.authenticatorData);
this.signature = clientAuth.response.signature;
const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64");
@ -265,25 +243,90 @@ export class PeerSetup {
return headerBase64 + "." + payloadBase64 + "." + signature;
}
public async createJwt2(
payload: object,
issuerDid: string,
credentialId: string,
) {
const signer = await this.webAuthnES256KSigner(credentialId);
public async createJwt2(fullPayload: object, credentialId: string) {
const header: JWTPayload = { typ: "JWT", alg: "ES256" };
const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64");
// from createJWT in did-jwt/src/JWT.ts
const header: JWTPayload = { typ: "JWT", alg: "ES256K" };
const timestamps: Partial<JWTPayload> = {
iat: Math.floor(Date.now() / 1000),
exp: undefined,
const dataToSignString = JSON.stringify(fullPayload);
const dataToSignBuffer = Buffer.from(dataToSignString);
//console.log("lower credentialId", credentialId);
this.challenge = new Uint8Array(dataToSignBuffer);
const options = {
publicKey: {
challenge: this.challenge.buffer,
rpID: window.location.hostname,
//allowCredentials: [{ id: credentialId, type: "public-key" }],
userVerification: "preferred",
//extensions: fullPayload,
},
};
const fullPayload = { ...timestamps, ...payload, iss: issuerDid };
const jwt = createJWS(fullPayload, signer, header);
// 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);
// console.log("lower credential get", credential);
// console.log("lower credential get in base64", {
// id: credential?.id,
// rawId: bufferToBase64URLString(credential?.rawId),
// 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.authenticatorDataBase64Url as Base64URLString,
"base64",
).buffer;
// console.log("lower authenticator data", this.authenticatorData);
this.clientDataJsonBase64Url =
authenticatorAssertionResponse.clientDataJSON;
this.clientDataJsonDecoded = JSON.parse(
new TextDecoder("utf-8").decode(
authenticatorAssertionResponse.clientDataJSON,
),
);
// console.log("lower clientDataJSON decoded", this.clientDataJsonDecoded);
const origSignature = Buffer.from(
authenticatorAssertionResponse.signature,
).toString("base64");
this.signature = origSignature
.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 jwt = headerBase64 + "." + payloadBase64 + "." + this.signature;
return jwt;
}
// 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(
@ -293,22 +336,23 @@ export class PeerSetup {
// 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("signature inside signer", signature);
console.log("buffer signature inside signer", signatureBuffer);
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("after reader");
console.log("lower after reader");
reader.readSequence();
console.log("after read sequence");
console.log("lower after read sequence");
const r = reader.readString(asn1.Ber.Integer, true);
console.log("after r");
console.log("lower after r");
const s = reader.readString(asn1.Ber.Integer, true);
console.log("after r & s");
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("after rBuffer & sBuffer", rBuffer, sBuffer);
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 =
@ -323,18 +367,18 @@ export class PeerSetup {
// Concatenate R and S to form the 64-byte array (ECDSA signature format expected by JWT)
const combinedSignature = Buffer.concat([rPadded, sPadded]);
console.log(
"combinedSignature",
"lower combinedSignature",
combinedSignature.length,
combinedSignature,
);
const combSig64 = combinedSignature.toString("base64");
console.log("combSig64", combSig64);
console.log("lower combSig64", combSig64);
const combSig64Url = combSig64
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
console.log("combSig64Url", combSig64Url);
console.log("lower combSig64Url", combSig64Url);
return combSig64Url;
};
}
@ -343,30 +387,50 @@ export class PeerSetup {
dataToSign: string | Uint8Array, // from Signer interface
credentialId: string,
) {
if (dataToSign instanceof Uint8Array) {
dataToSign = new TextDecoder("utf-8").decode(dataToSign as Uint8Array);
if (!(dataToSign instanceof Uint8Array)) {
console.log("lower dataToSign & typeof ", typeof dataToSign, dataToSign);
dataToSign = new Uint8Array(base64URLStringToBuffer(dataToSign));
}
console.log("credentialId", credentialId);
console.log("lower credentialId", credentialId);
this.challenge = dataToSign;
const options = {
challenge: new TextEncoder().encode(dataToSign).buffer,
//allowCredentials: [{ id: credentialId, type: "public-key" }],
userVerification: "preferred",
publicKey: {
challenge: this.challenge.buffer,
rpID: window.location.hostname,
//allowCredentials: [{ id: credentialId, type: "public-key" }],
userVerification: "preferred",
//extensions: fullPayload,
},
};
const assertion = await navigator.credentials.get({ publicKey: options });
console.log("assertion", assertion);
// console.log("lower authentication options", options);
const assertion = await navigator.credentials.get(options);
// console.log("lower credential get", assertion);
const authenticatorAssertionResponse = assertion?.response;
this.authenticatorDataBase64Url =
authenticatorAssertionResponse.authenticatorData;
this.authenticatorData = Buffer.from(
this.authenticatorDataBase64Url as Base64URLString,
"base64",
).buffer;
// console.log("lower authenticator data", this.authenticatorData);
this.clientDataJsonBase64Url =
authenticatorAssertionResponse.clientDataJSON;
this.clientDataJsonDecoded = JSON.parse(
new TextDecoder("utf-8").decode(
authenticatorAssertionResponse.clientDataJSON,
),
);
console.log("clientDataJSON decoded", this.clientDataJsonDecoded);
this.authenticatorData = authenticatorAssertionResponse.authenticatorData;
console.log("authenticator data", this.authenticatorData);
return authenticatorAssertionResponse.signature;
// console.log("lower clientDataJSON decoded", this.clientDataJsonDecoded);
this.signature = Buffer.from(
authenticatorAssertionResponse.signature,
).toString("base64");
return this.signature;
}
}
@ -383,109 +447,34 @@ export async function verifyJwt(
publicKeyJwk: JWK,
signature: Base64URLString,
) {
// 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 = {
// const authData = arrayToBase64Url(Buffer.from(authenticatorData));
// const authOpts: VerifyAuthenticationResponseOpts = {
// authenticator: {
// credentialID: credId,
// credentialPublicKey: publicKeyBytes,
// counter: 0,
// },
// expectedChallenge: options.challenge,
// expectedChallenge: arrayToBase64Url(challenge),
// expectedOrigin: window.location.origin,
// expectedRPID: window.location.hostname,
// response: clientAuth,
// 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("verfOpts", verfOpts);
// const verificationFromClient = await verifyAuthenticationResponse(verfOpts);
// console.log("client auth verification", verificationFromClient);
const authData = arrayToBase64Url(Buffer.from(authenticatorData));
const authOpts: VerifyAuthenticationResponseOpts = {
authenticator: {
credentialID: credId,
credentialPublicKey: 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);
// It doesn't work to use the did-jwt verifyJWT with did-resolver Resolver
// const decoded = verifyJWT(jwt, {
// resolver: new Resolver({ peer: peerDidToDidDocument }),
// });
// return decoded;
// const [headerB64, concatenatedPayloadB64, signatureB64] = jwt.split(".");
//
// const header = JSON.parse(atob(headerB64));
// const jsonPayload = JSON.parse(atob(concatenatedPayloadB64));
// const [jsonPayloadB64, otherDataHashB64] = concatenatedPayloadB64.split(".");
//
// const otherDataHash = base64urlDecode(otherDataHashB64);
// const signature = base64urlDecode(signatureB64);
//
// const dataToVerify = new TextEncoder().encode(`${headerB64}.${concatenatedPayloadB64}`);
// const dataHash = await sha256(dataToVerify);
//
// const authenticatorData = base64urlDecode(jsonPayload.authenticatorData);
// const clientDataJSON = jsonPayload.clientDataJSON;
/////////
// const clientBuffer = clientDataJsonBase64Url
// .replace(/-/g, "+")
// .replace(/_/g, "/");
// const clientData = new Uint8Array(Buffer.from(clientBuffer, "base64"));
// const clientDataHash = sha256(clientData);
//
// const verifyData = new Uint8Array([
// ...new Uint8Array(authenticatorData),
// ...new Uint8Array(clientDataHash),
// ]);
// console.log("verifyData", verifyData);
// const sigBase64Raw = signature.replace(/-/g, "+").replace(/_/g, "/");
//
// const sigHex = new Uint8Array(Buffer.from(sigBase64Raw, "base64")); //Buffer.from(sigBase64Raw, "base64").toString("hex");
// const msgHex = verifyData; //new Buffer(verifyData).toString("hex");
// const pubHex = publicKeyBytes; //new Buffer(publicKeyBytes).toString("hex");
// console.log("sig msg pub", sigHex, msgHex, pubHex);
// const isValid = p256.verify(sigHex, msgHex, pubHex);
/////////
// console.log("auth opts", authOpts);
// const verification = await verifyAuthenticationResponse(authOpts);
// console.log("auth verification", verification);
const authDataFromBase = Buffer.from(authenticatorDataBase64Url, "base64");
const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64");
@ -498,9 +487,9 @@ export async function verifyJwt(
// Construct the preimage
const preimage = Buffer.concat([authDataFromBase, hash]);
console.log("finalSigBuffer", finalSigBuffer);
console.log("preimage", preimage);
console.log("publicKeyBytes", publicKeyBytes);
// 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(
@ -510,8 +499,6 @@ export async function verifyJwt(
// );
// console.log("isValid", isValid);
/////////
const WebCrypto = await getWebCrypto();
const verifyAlgorithm = {
name: "ECDSA",
@ -521,10 +508,6 @@ export async function verifyJwt(
name: "ECDSA",
namedCurve: publicKeyJwk.crv,
};
// const publicKeyCryptoKey = await importKey({
// publicKeyJwk,
// algorithm: keyAlgorithm,
// });
const publicKeyCryptoKey = await WebCrypto.subtle.importKey(
"jwk",
publicKeyJwk,
@ -532,17 +515,17 @@ export async function verifyJwt(
false,
["verify"],
);
console.log("verifyAlgorithm", verifyAlgorithm);
console.log("publicKeyCryptoKey", publicKeyCryptoKey);
console.log("finalSigBuffer", finalSigBuffer);
console.log("preimage", preimage);
// console.log("verifyAlgorithm", verifyAlgorithm);
// console.log("publicKeyCryptoKey", publicKeyCryptoKey);
// console.log("finalSigBuffer", finalSigBuffer);
// console.log("preimage", preimage);
const verified = await WebCrypto.subtle.verify(
verifyAlgorithm,
publicKeyCryptoKey,
finalSigBuffer,
preimage,
);
console.log("verified", verified);
// console.log("verified", verified);
return verified;
}
@ -552,7 +535,8 @@ async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
"This only verifies a peer DID, method 0, encoded base58btc.",
);
}
// this is basically hard-coded based on the results from the @aviarytech/did-peer resolver
// this is basically hard-coded from https://www.w3.org/TR/did-core/#example-various-verification-method-types
// (another reference is the @aviarytech/did-peer resolver)
const id = did.split(":")[2];
const multibase = id.slice(1);
const encnumbasis = multibase.slice(1);
@ -617,6 +601,31 @@ function base64urlEncode(buffer: ArrayBuffer) {
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
// from @simplewebauthn/browser
function bufferToBase64URLString(buffer) {
const bytes = new Uint8Array(buffer);
let str = '';
for (const charCode of bytes) {
str += String.fromCharCode(charCode);
}
const base64String = btoa(str);
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
// from @simplewebauthn/browser
function base64URLStringToBuffer(base64URLString) {
const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
const padLength = (4 - (base64.length % 4)) % 4;
const padded = base64.padEnd(base64.length + padLength, '=');
const binary = atob(padded);
const buffer = new ArrayBuffer(binary.length);
const bytes = new Uint8Array(buffer);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return buffer;
}
function cborToKeys(publicKeyBytes: Uint8Array) {
const jwkObj = cborDecode(publicKeyBytes);
console.log("jwkObj from verification", jwkObj);

55
src/views/TestView.vue

@ -210,6 +210,7 @@ import {
verifyJwt,
} from "@/libs/didPeer";
import { Buffer } from "buffer";
import {JWTPayload} from "did-jwt";
const inputFileNameRef = ref<Blob>();
@ -222,8 +223,14 @@ export default class Help extends Vue {
authenticatorData?: ArrayBuffer;
credId?: Base64URLString;
jwt?: string;
payload = { a: 1 };
jwt2?: string;
payload = {
"@context": "https://schema.org",
type: "GiveAction",
description: "pizza",
};
peerSetup?: PeerSetup;
peerSetup2?: PeerSetup;
publicKeyJwk?: JWK;
publicKeyBytes?: Uint8Array;
rawId?: Uint8Array;
@ -277,19 +284,40 @@ export default class Help extends Vue {
public async create() {
console.log("Starting a create");
const did = createPeerDid(this.publicKeyBytes as Uint8Array);
console.log("did", did);
// console.log("did", did);
// from createJWT in did-jwt/src/JWT.ts
const timestamps: Partial<JWTPayload> = {
iat: Math.floor(Date.now() / 1000),
exp: undefined,
};
const fullPayload = { ...timestamps, ...this.payload, did };
this.peerSetup = new PeerSetup();
const rawJwt = await this.peerSetup.createJwt(
this.payload,
did,
fullPayload,
this.credId as string,
);
console.log("raw jwt", rawJwt);
//console.log("simple raw jwt", rawJwt);
this.jwt = rawJwt
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
console.log("jwt4url", this.jwt);
console.log("simple jwt4url", this.jwt);
// console.log("simple peerSetup", this.peerSetup);
this.peerSetup2 = new PeerSetup();
const rawJwt2 = await this.peerSetup2.createJwt2(
fullPayload,
this.credId as string,
);
// console.log("lower raw jwt2", rawJwt2);
this.jwt2 = rawJwt2
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
console.log("lower jwt4url2", this.jwt2);
// console.log("lower peerSetup2", this.peerSetup2);
}
public async verify() {
@ -311,6 +339,21 @@ export default class Help extends Vue {
this.peerSetup.signature as Base64URLString,
);
console.log("decoded", decoded);
const decoded2 = await verifyJwt(
this.jwt2 as string,
this.credId as Base64URLString,
this.rawId as Uint8Array,
this.peerSetup2.authenticatorData as ArrayBuffer,
this.peerSetup2.authenticatorDataBase64Url as Base64URLString,
this.peerSetup2.challenge as Uint8Array,
this.peerSetup2.clientDataJsonDecoded,
this.peerSetup2.clientDataJsonBase64Url as Base64URLString,
this.publicKeyBytes as Uint8Array,
this.publicKeyJwk as JWK,
this.peerSetup2.signature as Base64URLString,
);
console.log("decoded2", decoded2);
}
}
</script>

Loading…
Cancel
Save