passkey test #116
6
package-lock.json
generated
6
package-lock.json
generated
@@ -28,6 +28,7 @@
|
||||
"@veramo/key-manager": "^5.6.0",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@zxing/text-encoding": "^0.9.0",
|
||||
"asn1-ber": "^1.2.2",
|
||||
"axios": "^1.6.8",
|
||||
"cbor-x": "^1.5.9",
|
||||
"class-transformer": "^0.5.1",
|
||||
@@ -10347,6 +10348,11 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/asn1-ber": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/asn1-ber/-/asn1-ber-1.2.2.tgz",
|
||||
"integrity": "sha512-CbNem/7hxrjSiOAOOTX4iZxu+0m3jiLqlsERQwwPM1IDR/22M8IPpA1VVndCLw5KtjRYyRODbvAEIfuTogNDng=="
|
||||
},
|
||||
"node_modules/asn1.js": {
|
||||
"version": "5.4.1",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"@veramo/key-manager": "^5.6.0",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@zxing/text-encoding": "^0.9.0",
|
||||
"asn1-ber": "^1.2.2",
|
||||
"axios": "^1.6.8",
|
||||
"cbor-x": "^1.5.9",
|
||||
"class-transformer": "^0.5.1",
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import asn1 from "asn1-ber";
|
||||
import { Buffer } from "buffer/";
|
||||
import { decode as cborDecode } from "cbor-x";
|
||||
import { createJWS, JWTPayload, verifyJWT } from "did-jwt";
|
||||
import { getResolver } from "@veramo/did-provider-peer";
|
||||
import { DIDResolutionResult, Resolver } from "did-resolver";
|
||||
import { bytesToMultibase } from "@veramo/utils";
|
||||
|
||||
import { generateRandomBytes } from "@/libs/crypto";
|
||||
|
||||
export interface JWK {
|
||||
kty: string;
|
||||
crv: string;
|
||||
@@ -102,7 +101,7 @@ export function createPeerDid(publicKeyCose: Uint8Array) {
|
||||
const methodSpecificId = bytesToMultibase(
|
||||
publicKeyCose,
|
||||
"base58btc",
|
||||
"ed25519-pub",
|
||||
"secp256k1-pub",
|
||||
);
|
||||
return "did:peer:0" + methodSpecificId;
|
||||
}
|
||||
@@ -128,18 +127,53 @@ export async function createJwt(
|
||||
async function webAuthnES256KSigner(credentialID: ArrayBuffer) {
|
||||
return async (data: string | Uint8Array) => {
|
||||
// also has clientDataJSON
|
||||
const { signature, authenticatorData } = await generateWebAuthnSignature(
|
||||
data,
|
||||
credentialID,
|
||||
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.
|
||||
const signatureBuffer = Buffer.from(signature);
|
||||
console.log("signature inside signer", signature);
|
||||
console.log("buffer signature inside signer", signatureBuffer);
|
||||
// Decode the DER-encoded signature to extract R and S values
|
||||
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
|
||||
const rBuffer = Buffer.from(r);
|
||||
const sBuffer = Buffer.from(s);
|
||||
console.log("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(
|
||||
"combinedSignature",
|
||||
combinedSignature.length,
|
||||
combinedSignature,
|
||||
);
|
||||
|
||||
// Combine the WebAuthn components into a single signature format as required by did-jwt
|
||||
const combinedSignature = Buffer.concat([
|
||||
Buffer.from(authenticatorData),
|
||||
Buffer.from(signature),
|
||||
]);
|
||||
|
||||
return combinedSignature.toString("base64");
|
||||
const combSig64 = combinedSignature.toString("base64");
|
||||
console.log("combSig64", combSig64);
|
||||
const combSig64Url = combSig64
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=+$/, "");
|
||||
console.log("combSig64Url", combSig64Url);
|
||||
return combSig64Url;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -150,17 +184,25 @@ async function generateWebAuthnSignature(
|
||||
if (!(dataToSign instanceof Uint8Array)) {
|
||||
dataToSign = new TextEncoder().encode(dataToSign as string);
|
||||
}
|
||||
const challenge = generateRandomBytes(32);
|
||||
|
||||
const options = {
|
||||
challenge: challenge,
|
||||
challenge: dataToSign,
|
||||
allowCredentials: [{ id: credentialId, type: "public-key" }],
|
||||
userVerification: "preferred",
|
||||
};
|
||||
|
||||
const assertion = await navigator.credentials.get({ publicKey: options });
|
||||
console.log("assertion", assertion);
|
||||
|
||||
const authenticatorAssertionResponse = assertion?.response;
|
||||
console.log(
|
||||
"clientDataJSON decoded",
|
||||
JSON.parse(
|
||||
new TextDecoder("utf-8").decode(
|
||||
authenticatorAssertionResponse.clientDataJSON,
|
||||
),
|
||||
),
|
||||
);
|
||||
return {
|
||||
signature: authenticatorAssertionResponse.signature,
|
||||
clientDataJSON: authenticatorAssertionResponse.clientDataJSON,
|
||||
@@ -170,15 +212,55 @@ async function generateWebAuthnSignature(
|
||||
|
||||
export async function verifyJwt(
|
||||
jwt: string,
|
||||
issuerDid: string,
|
||||
publicKey: JWK,
|
||||
issuerDid: string, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
publicKey: JWK, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
) {
|
||||
const decoded = verifyJWT(jwt, {
|
||||
didAuthenticator: {
|
||||
authenticators: [{ publicKeyJwk: publicKey }],
|
||||
issuer: issuerDid,
|
||||
},
|
||||
resolver: getResolver(),
|
||||
// didAuthenticator: {
|
||||
// authenticators: [{ publicKeyJwk: publicKey }],
|
||||
// issuer: issuerDid,
|
||||
// },
|
||||
//resolver: new Resolver({ ...getResolver() }),
|
||||
resolver: new Resolver({ peer: peerDidToDidDocument }),
|
||||
});
|
||||
return decoded;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
|
||||
if (!did.startsWith("did:peer:0z")) {
|
||||
throw new Error(
|
||||
"This only verifies a peer DID method 0 base58btc encoded.",
|
||||
);
|
||||
}
|
||||
// this is basically hard-coded based on the results from the @aviarytech/did-peer resolver
|
||||
const id = did.split(":")[2];
|
||||
const multibase = id.slice(1);
|
||||
const encnumbasis = multibase.slice(1);
|
||||
const didDocument = {
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/did/v1",
|
||||
"https://w3id.org/security/suites/jws-2020/v1",
|
||||
],
|
||||
assertionMethod: ["did:peer:" + id + "#" + encnumbasis],
|
||||
authentication: ["did:peer:" + id + "#" + encnumbasis],
|
||||
capabilityDelegation: ["did:peer:" + id + "#" + encnumbasis],
|
||||
capabilityInvocation: ["did:peer:" + id + "#" + encnumbasis],
|
||||
id: did,
|
||||
keyAgreement: undefined,
|
||||
service: undefined,
|
||||
verificationMethod: [
|
||||
{
|
||||
id: "did:peer:" + id + "#" + encnumbasis,
|
||||
type: "EcdsaSecp256k1VerificationKey2019",
|
||||
controller: did,
|
||||
publicKeyMultibase: multibase,
|
||||
},
|
||||
],
|
||||
};
|
||||
return {
|
||||
didDocument,
|
||||
didDocumentMetadata: {},
|
||||
didResolutionMetadata: { contentType: "application/did+ld+json" },
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user