Browse Source

attempt to simply verify something signed with the same library -- doesn't work

pull/116/head
Trent Larson 3 months ago
parent
commit
0d54c50e5f
  1. 9
      package-lock.json
  2. 1
      package.json
  3. 273
      src/libs/didPeer.ts
  4. 38
      src/views/StartView.vue

9
package-lock.json

@ -15,6 +15,7 @@
"@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6", "@fortawesome/vue-fontawesome": "^3.0.6",
"@pvermeer/dexie-encrypted-addon": "^3.0.0", "@pvermeer/dexie-encrypted-addon": "^3.0.0",
"@simplewebauthn/browser": "^10.0.0",
"@simplewebauthn/server": "^10.0.0", "@simplewebauthn/server": "^10.0.0",
"@tweenjs/tween.js": "^21.1.1", "@tweenjs/tween.js": "^21.1.1",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
@ -8226,6 +8227,14 @@
"optional": true, "optional": true,
"peer": true "peer": true
}, },
"node_modules/@simplewebauthn/browser": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-10.0.0.tgz",
"integrity": "sha512-hG0JMZD+LiLUbpQcAjS4d+t4gbprE/dLYop/CkE01ugU/9sKXflxV5s0DRjdz3uNMFecatRfb4ZLG3XvF8m5zg==",
"dependencies": {
"@simplewebauthn/types": "^10.0.0"
}
},
"node_modules/@simplewebauthn/server": { "node_modules/@simplewebauthn/server": {
"version": "10.0.0", "version": "10.0.0",
"license": "MIT", "license": "MIT",

1
package.json

@ -17,6 +17,7 @@
"@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6", "@fortawesome/vue-fontawesome": "^3.0.6",
"@pvermeer/dexie-encrypted-addon": "^3.0.0", "@pvermeer/dexie-encrypted-addon": "^3.0.0",
"@simplewebauthn/browser": "^10.0.0",
"@simplewebauthn/server": "^10.0.0", "@simplewebauthn/server": "^10.0.0",
"@tweenjs/tween.js": "^21.1.1", "@tweenjs/tween.js": "^21.1.1",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",

273
src/libs/didPeer.ts

@ -4,10 +4,18 @@ import { decode as cborDecode } from "cbor-x";
import { createJWS, JWTPayload, verifyJWT } from "did-jwt"; import { createJWS, JWTPayload, verifyJWT } from "did-jwt";
import { DIDResolutionResult, Resolver } from "did-resolver"; import { DIDResolutionResult, Resolver } from "did-resolver";
import { bytesToMultibase } from "@veramo/utils"; import { bytesToMultibase } from "@veramo/utils";
import { startAuthentication } from "@simplewebauthn/browser";
import { import {
generateAuthenticationOptions,
verifyAuthenticationResponse,
verifyRegistrationResponse, verifyRegistrationResponse,
VerifyRegistrationResponseOpts, VerifyRegistrationResponseOpts,
} from "@simplewebauthn/server"; } from "@simplewebauthn/server";
import { VerifyAuthenticationResponseOpts } from "@simplewebauthn/server/esm/authentication/verifyAuthenticationResponse";
import {
Base64URLString,
PublicKeyCredentialRequestOptionsJSON,
} from "@simplewebauthn/types";
export interface JWK { export interface JWK {
kty: string; kty: string;
@ -61,11 +69,8 @@ export async function registerCredential(
publicKey: publicKeyOptions, publicKey: publicKeyOptions,
}); });
console.log("credential", credential); console.log("credential", credential);
console.log(credential?.id, " is the new Id"); console.log(credential?.id, " is the new ID base64-url-encoded");
console.log( console.log(toBase64Url(credential?.rawId), " is the base64 rawId");
Buffer.from(credential?.rawId).toString("base64"),
" is the base64 rawId",
);
const attestationResponse = credential?.response; const attestationResponse = credential?.response;
const verfInput: VerifyRegistrationResponseOpts = { const verfInput: VerifyRegistrationResponseOpts = {
response: { response: {
@ -78,7 +83,6 @@ export async function registerCredential(
clientExtensionResults: {}, clientExtensionResults: {},
type: "public-key", type: "public-key",
}, },
//expectedChallenge: Buffer.from(challenge).toString("base64"),
expectedChallenge: toBase64Url(challenge), expectedChallenge: toBase64Url(challenge),
expectedOrigin: window.location.origin, expectedOrigin: window.location.origin,
}; };
@ -98,9 +102,6 @@ export async function registerCredential(
); );
console.log("attestationObject", attestationObject); console.log("attestationObject", attestationObject);
const credData = parseAuthData(attestationObject.authData);
console.log("new attempt at publicKey", credData);
const jwkObj = cborDecode( const jwkObj = cborDecode(
verification.registrationInfo?.credentialPublicKey as Uint8Array, verification.registrationInfo?.credentialPublicKey as Uint8Array,
); );
@ -129,7 +130,13 @@ export async function registerCredential(
//const publicKeyBytes = extractPublicKeyCose(attestationObject.authData); //const publicKeyBytes = extractPublicKeyCose(attestationObject.authData);
//const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData); //const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData);
return { rawId: credential?.rawId, publicKeyJwk, publicKeyBytes }; return {
authData: attestationObject.authData,
credId: credential?.id,
rawId: credential?.rawId,
publicKeyJwk,
publicKeyBytes,
};
} }
// parse authData // parse authData
@ -148,115 +155,181 @@ export function createPeerDid(publicKeyBytes: Uint8Array) {
return "did:peer:0" + methodSpecificId; return "did:peer:0" + methodSpecificId;
} }
export async function createJwt( export class PeerSetup {
payload: object, public authenticatorData?: ArrayBuffer;
issuerDid: string, public clientDataJsonDecoded?: object;
credentialId: ArrayBuffer,
) {
const signer = await webAuthnES256KSigner(credentialId);
// from createJWT in did-jwt/src/JWT.ts public async createJwt(
const header: JWTPayload = { typ: "JWT", alg: "ES256K" }; payload: object,
const timestamps: Partial<JWTPayload> = { issuerDid: string,
iat: Math.floor(Date.now() / 1000), credentialId: string,
exp: undefined, ) {
}; const signer = await this.webAuthnES256KSigner(credentialId);
const fullPayload = { ...timestamps, ...payload, iss: issuerDid };
return createJWS(fullPayload, signer, header); // 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 fullPayload = { ...timestamps, ...payload, iss: issuerDid };
async function webAuthnES256KSigner(credentialID: ArrayBuffer) { const jwt = createJWS(fullPayload, signer, header);
return async (data: string | Uint8Array) => { return jwt;
// also has clientDataJSON }
const { signature } = await generateWebAuthnSignature(data, credentialID);
// This converts from the browser ArrayBuffer to a Node.js Buffer, which is a requirement for the asn1 library. async webAuthnES256KSigner(credentialID: string) {
const signatureBuffer = Buffer.from(signature); return async (data: string | Uint8Array) => {
console.log("signature inside signer", signature); const signature = await this.generateWebAuthnSignature(
console.log("buffer signature inside signer", signatureBuffer); data,
// Decode the DER-encoded signature to extract R and S values credentialID,
const reader = new asn1.BerReader(signatureBuffer); );
console.log("after reader");
reader.readSequence();
console.log("after read sequence");
const r = reader.readString(asn1.Ber.Integer, true);
console.log("after r");
const s = reader.readString(asn1.Ber.Integer, true);
console.log("after r & s");
// Ensure R and S are 32 bytes each // This converts from the browser ArrayBuffer to a Node.js Buffer, which is a requirement for the asn1 library.
const rBuffer = Buffer.from(r); const signatureBuffer = Buffer.from(signature);
const sBuffer = Buffer.from(s); console.log("signature inside signer", signature);
console.log("after rBuffer & sBuffer", rBuffer, sBuffer); console.log("buffer signature inside signer", signatureBuffer);
const rWithoutPrefix = rBuffer.length > 32 ? rBuffer.slice(1) : rBuffer; // Decode the DER-encoded signature to extract R and S values
const sWithoutPrefix = sBuffer.length > 32 ? sBuffer.slice(1) : sBuffer; const reader = new asn1.BerReader(signatureBuffer);
const rPadded = console.log("after reader");
rWithoutPrefix.length < 32 reader.readSequence();
? Buffer.concat([Buffer.alloc(32 - rWithoutPrefix.length), rBuffer]) console.log("after read sequence");
: rWithoutPrefix; const r = reader.readString(asn1.Ber.Integer, true);
const sPadded = console.log("after r");
rWithoutPrefix.length < 32 const s = reader.readString(asn1.Ber.Integer, true);
? Buffer.concat([Buffer.alloc(32 - sWithoutPrefix.length), sBuffer]) console.log("after r & s");
: sWithoutPrefix;
// Concatenate R and S to form the 64-byte array (ECDSA signature format expected by JWT) // Ensure R and S are 32 bytes each
const combinedSignature = Buffer.concat([rPadded, sPadded]); const rBuffer = Buffer.from(r);
console.log( const sBuffer = Buffer.from(s);
"combinedSignature", console.log("after rBuffer & sBuffer", rBuffer, sBuffer);
combinedSignature.length, const rWithoutPrefix = rBuffer.length > 32 ? rBuffer.slice(1) : rBuffer;
combinedSignature, 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;
const combSig64 = combinedSignature.toString("base64"); // Concatenate R and S to form the 64-byte array (ECDSA signature format expected by JWT)
console.log("combSig64", combSig64); const combinedSignature = Buffer.concat([rPadded, sPadded]);
const combSig64Url = combSig64 console.log(
.replace(/\+/g, "-") "combinedSignature",
.replace(/\//g, "_") combinedSignature.length,
.replace(/=+$/, ""); combinedSignature,
console.log("combSig64Url", combSig64Url); );
return combSig64Url;
};
}
async function generateWebAuthnSignature( const combSig64 = combinedSignature.toString("base64");
dataToSign: string | Uint8Array, console.log("combSig64", combSig64);
credentialId: ArrayBuffer, const combSig64Url = combSig64
) { .replace(/\+/g, "-")
if (!(dataToSign instanceof Uint8Array)) { .replace(/\//g, "_")
dataToSign = new TextEncoder().encode(dataToSign as string); .replace(/=+$/, "");
console.log("combSig64Url", combSig64Url);
return combSig64Url;
};
} }
const options = { async generateWebAuthnSignature(
challenge: dataToSign, dataToSign: string | Uint8Array, // from Signer interface
allowCredentials: [{ id: credentialId, type: "public-key" }], credentialId: string,
userVerification: "preferred", ) {
}; if (dataToSign instanceof Uint8Array) {
dataToSign = new TextDecoder("utf-8").decode(dataToSign as Uint8Array);
}
const assertion = await navigator.credentials.get({ publicKey: options }); console.log("credentialId", credentialId);
console.log("assertion", assertion); const options = {
challenge: new TextEncoder().encode(dataToSign).buffer,
//allowCredentials: [{ id: credentialId, type: "public-key" }],
userVerification: "preferred",
};
const authenticatorAssertionResponse = assertion?.response; const assertion = await navigator.credentials.get({ publicKey: options });
console.log( console.log("assertion", assertion);
"clientDataJSON decoded",
JSON.parse( const authenticatorAssertionResponse = assertion?.response;
this.clientDataJsonDecoded = JSON.parse(
new TextDecoder("utf-8").decode( new TextDecoder("utf-8").decode(
authenticatorAssertionResponse.clientDataJSON, authenticatorAssertionResponse.clientDataJSON,
), ),
), );
); console.log("clientDataJSON decoded", this.clientDataJsonDecoded);
return { this.authenticatorData = authenticatorAssertionResponse.authenticatorData;
signature: authenticatorAssertionResponse.signature, console.log("authenticator data", this.authenticatorData);
clientDataJSON: authenticatorAssertionResponse.clientDataJSON, return authenticatorAssertionResponse.signature;
authenticatorData: authenticatorAssertionResponse.authenticatorData, }
};
} }
export async function verifyJwt( export async function verifyJwt(
jwt: string, jwt: string,
issuerDid: string, // eslint-disable-line @typescript-eslint/no-unused-vars credId: Base64URLString,
publicKey: JWK, // eslint-disable-line @typescript-eslint/no-unused-vars rawId: Uint8Array,
authenticatorData: ArrayBuffer,
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);
const clientAuth = await startAuthentication(options);
console.log("clientAuth", clientAuth);
const verfOpts: VerifyAuthenticationResponseOpts = {
response: clientAuth,
authenticator: {
credentialID: credId,
credentialPublicKey: publicKey,
counter: 0,
},
expectedChallenge: () => true, // options.challenge doesn't work
expectedOrigin: window.location.origin,
expectedRPID: window.location.hostname,
};
console.log("verfOpts", verfOpts);
const verificationFromClient = await verifyAuthenticationResponse(verfOpts);
console.log("client auth verification", verificationFromClient);
const authData = toBase64Url(Buffer.from(authenticatorData));
const bufferizedJson = toBase64Url(
new TextEncoder().encode(JSON.stringify(clientDataJSON)),
);
const authOpts: VerifyAuthenticationResponseOpts = {
response: {
id: credId,
rawId: toBase64Url(rawId),
response: {
authenticatorData: authData,
clientDataJSON: bufferizedJson,
signature: signature,
},
clientExtensionResults: {},
type: "public-key",
},
expectedChallenge: () => true, // options.challenge doesn't work
expectedOrigin: window.location.origin,
expectedRPID: window.location.hostname,
authenticator: {
credentialID: credId,
credentialPublicKey: publicKey,
counter: 0,
},
};
const verification = await verifyAuthenticationResponse(authOpts);
console.log("auth verification", verification);
const decoded = verifyJWT(jwt, { const decoded = verifyJWT(jwt, {
resolver: new Resolver({ peer: peerDidToDidDocument }), resolver: new Resolver({ peer: peerDidToDidDocument }),
}); });
@ -266,7 +339,7 @@ export async function verifyJwt(
async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> { async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
if (!did.startsWith("did:peer:0z")) { if (!did.startsWith("did:peer:0z")) {
throw new Error( throw new Error(
"This only verifies a peer DID method 0 encoded base58btc.", "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 based on the results from the @aviarytech/did-peer resolver

38
src/views/StartView.vue

@ -35,6 +35,7 @@
Only click "No" if you have a seed of 12 or 24 words generated Only click "No" if you have a seed of 12 or 24 words generated
elsewhere. elsewhere.
</p> </p>
<input type="text" v-model="savedCredentialId" class="border" />
<a <a
@click="onClickYes()" @click="onClickYes()"
class="block w-full text-center text-lg uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2" class="block w-full text-center text-lg uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
@ -62,14 +63,15 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Base64URLString } from "@simplewebauthn/types";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB } from "@/db/index"; import { accountsDB } from "@/db/index";
import { generateRandomBytes } from "@/libs/crypto"; import { generateRandomBytes } from "@/libs/crypto";
import { import {
createJwt,
createPeerDid, createPeerDid,
JWK, JWK,
PeerSetup,
registerCredential, registerCredential,
verifyJwt, verifyJwt,
} from "@/libs/didPeer"; } from "@/libs/didPeer";
@ -78,10 +80,14 @@ import {
components: {}, components: {},
}) })
export default class StartView extends Vue { export default class StartView extends Vue {
authenticatorData?: ArrayBuffer;
credId?: Base64URLString;
numAccounts = 0; numAccounts = 0;
publicKeyJwk?: JWK; publicKeyJwk?: JWK;
publicKeyBytes?: Uint8Array; publicKeyBytes?: Uint8Array;
userId?: Uint8Array; rawId?: Uint8Array;
savedCredentialId = "";
userId?: ArrayBuffer;
async mounted() { async mounted() {
await accountsDB.open(); await accountsDB.open();
@ -89,23 +95,30 @@ export default class StartView extends Vue {
} }
public async onClickYes() { public async onClickYes() {
this.userId = generateRandomBytes(16); this.userId = generateRandomBytes(16).buffer;
const encodedUserId = new TextDecoder("utf-8").decode(this.userId); const encodedUserId = new TextDecoder("utf-8").decode(this.userId);
console.log("encodedUserId", encodedUserId); console.log("encodedUserId", encodedUserId);
const cred = await registerCredential(this.userId, generateRandomBytes(32)); const challenge = generateRandomBytes(32);
const cred = await registerCredential(
this.userId as Uint8Array,
challenge as Uint8Array,
);
console.log("public key", cred); console.log("public key", cred);
this.publicKeyJwk = cred.publicKeyJwk; this.publicKeyJwk = cred.publicKeyJwk;
this.publicKeyBytes = cred.publicKeyBytes; this.publicKeyBytes = cred.publicKeyBytes;
this.userId = cred.rawId as Uint8Array; this.credId = cred.credId as string;
this.rawId = cred.rawId as Uint8Array;
this.savedCredentialId = this.credId;
//this.$router.push({ name: "new-identifier" }); //this.$router.push({ name: "new-identifier" });
} }
public async onClickNo() { public async onClickNo() {
const credArrBuff = this.userId;
const did = createPeerDid(this.publicKeyBytes as Uint8Array); const did = createPeerDid(this.publicKeyBytes as Uint8Array);
console.log("did", did); console.log("did", did);
const jwt = await createJwt({ a: 1 }, did, credArrBuff as Uint8Array); const payload = { a: 1 };
const peerSetup = new PeerSetup();
const jwt = await peerSetup.createJwt(payload, did, this.credId as string);
console.log("jwt", jwt); console.log("jwt", jwt);
const jwt4url = jwt const jwt4url = jwt
.replace(/\+/g, "-") .replace(/\+/g, "-")
@ -113,7 +126,16 @@ export default class StartView extends Vue {
.replace(/=+$/, ""); .replace(/=+$/, "");
console.log("jwt4url", jwt4url); console.log("jwt4url", jwt4url);
const decoded = await verifyJwt(jwt4url, did, this.publicKey as JWK); const signature = jwt4url.split(".")[2];
const decoded = await verifyJwt(
jwt4url,
this.credId as Base64URLString,
this.rawId as Uint8Array,
peerSetup.authenticatorData as ArrayBuffer,
peerSetup.clientDataJsonDecoded,
this.publicKeyBytes as Uint8Array,
signature,
);
console.log("decoded", decoded); console.log("decoded", decoded);
//this.$router.push({ name: "import-account" }); //this.$router.push({ name: "import-account" });
} }

Loading…
Cancel
Save