Skip to content

Commit 6cd7883

Browse files
committed
Adapt registration flow to create JWT signing keys
1 parent 9e3adfe commit 6cd7883

File tree

3 files changed

+74
-32
lines changed

3 files changed

+74
-32
lines changed

src/components/register-button.tsx

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
"use client";
22

33
import { Dispatch, SetStateAction } from "react";
4-
import { Wallet } from "ethers";
5-
import { isoBase64URL } from "@simplewebauthn/server/helpers";
4+
import { generateKeyPairSync } from "crypto";
65
import {
76
startRegistration,
87
type WebAuthnCredential,
@@ -12,30 +11,65 @@ import { getRegistrationOptions, verifyRegistration } from "../lib/registry";
1211
interface RegisterPasskeyProps {
1312
setUserCredential: Dispatch<SetStateAction<WebAuthnCredential | null>>;
1413
}
14+
15+
function arrayBufferToPem(
16+
buffer: ArrayBuffer,
17+
type: "PUBLIC KEY" | "PRIVATE KEY"
18+
): string {
19+
// Convert ArrayBuffer to base64
20+
const base64 = Buffer.from(buffer).toString("base64");
21+
22+
// Split into 64-character lines (PEM standard)
23+
const lines = base64.match(/.{1,64}/g) || [];
24+
25+
// Add PEM headers and footers
26+
return [`-----BEGIN ${type}-----`, ...lines, `-----END ${type}-----`].join(
27+
"\n"
28+
);
29+
}
30+
1531
export default function RegisterPasskey({
1632
setUserCredential,
1733
}: RegisterPasskeyProps) {
1834
async function handleClick() {
1935
console.log("New passkey registration process started");
2036

21-
const ephemeralWallet = Wallet.createRandom();
22-
console.log(
23-
"Ephemeral wallet address (passkey's userID):",
24-
ephemeralWallet.address
37+
const userName = `user_${Math.floor(Math.random() * 1000)}`;
38+
39+
// Browser generates a public/private key pair for JWT signing
40+
const { publicKey, privateKey } =
41+
await window.crypto.subtle.generateKey(
42+
{
43+
name: "ECDSA",
44+
namedCurve: "P-256",
45+
},
46+
true,
47+
["sign", "verify"]
48+
);
49+
50+
const jwtPubKey = arrayBufferToPem(
51+
await window.crypto.subtle.exportKey("spki", publicKey),
52+
"PUBLIC KEY"
53+
);
54+
const jwtPrivKey = arrayBufferToPem(
55+
await window.crypto.subtle.exportKey("pkcs8", privateKey),
56+
"PRIVATE KEY"
2557
);
2658

59+
console.log("JWT Public Key (passkey's userID):");
60+
console.log(jwtPubKey);
61+
console.log("JWT Private Key:");
62+
console.log(jwtPrivKey);
63+
2764
console.log("Generating registration options...");
2865
const registrationOptions = await getRegistrationOptions(
29-
ephemeralWallet.address
66+
userName,
67+
jwtPubKey
3068
);
3169
console.log(
3270
"Registration options generated by server:",
3371
JSON.stringify(registrationOptions, null, 2)
3472
);
35-
console.log(
36-
"UserID (wallet address) on registration options in ASCII:",
37-
isoBase64URL.toUTF8String(registrationOptions.user.id)
38-
);
3973

4074
console.log("Starting registration (authenticator interaction)...");
4175
const registrationResponse = await startRegistration({
@@ -48,12 +82,13 @@ export default function RegisterPasskey({
4882

4983
console.log("Verifying registration...");
5084
const verificationResponse = await verifyRegistration(
51-
ephemeralWallet.address,
85+
userName,
86+
jwtPubKey,
5287
registrationResponse
5388
);
5489
console.log(
5590
"Verification response from server:",
56-
JSON.stringify(verificationResponse, null, 2)
91+
JSON.stringify(verificationResponse.verified, null, 2)
5792
);
5893

5994
if (!verificationResponse.registrationInfo) {

src/lib/database.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ export const saveRegistrationOptions = async (
2222
registrationOptions: PublicKeyCredentialCreationOptionsJSON
2323
) => {
2424
const db = await getOrCreateDatabase();
25-
const userID = isoBase64URL.toUTF8String(registrationOptions.user.id);
26-
db.registrationOptions[userID] = registrationOptions;
25+
const userName = registrationOptions.user.name;
26+
// TODO: Use userID instead of userName as key
27+
db.registrationOptions[userName] = registrationOptions;
2728
writeFileSync(DB_FILE, JSON.stringify(db, null, 2));
2829
};
2930

30-
export const removeRegistrationOptions = async (userID: string) => {
31+
export const removeRegistrationOptions = async (userName: string) => {
3132
const db = await getOrCreateDatabase();
32-
delete db.registrationOptions[userID];
33+
delete db.registrationOptions[userName];
3334
writeFileSync(DB_FILE, JSON.stringify(db, null, 2));
3435
};

src/lib/registry.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,25 @@ import {
1717
} from "./database";
1818

1919
export const getRegistrationOptions = async (
20-
ephemeralWalletAddress: string
20+
userName: string,
21+
jwtPubKey: string
2122
): Promise<PublicKeyCredentialCreationOptionsJSON> => {
22-
const challenge = await bcrypt.hash(ephemeralWalletAddress, 10);
23+
const userID = isoUint8Array.fromASCIIString(await bcrypt.hash(jwtPubKey, 0));
24+
const challenge = await bcrypt.hash(jwtPubKey, 10);
2325

26+
// Generate registration options:
27+
// challenge: JWT public key hashed
28+
// userID: JWT public key hashed
29+
// userName: string like user_xxxx
2430
const registrationOptionsParameters: GenerateRegistrationOptionsOpts = {
2531
rpName: "Passkeys TACo PoC",
2632
rpID: "localhost",
27-
userName: ephemeralWalletAddress, // to be shown in passkey popup
28-
userID: isoUint8Array.fromASCIIString(ephemeralWalletAddress),
29-
challenge: isoUint8Array.fromASCIIString(challenge),
30-
userDisplayName: ephemeralWalletAddress,
33+
userName: userName, // to be shown in passkey popup
34+
userID: userID,
35+
challenge: challenge,
3136
timeout: 60000,
3237
// excludeCredentials: [],
33-
supportedAlgorithmIDs: [-7, -257],
38+
supportedAlgorithmIDs: [-7, -257], // ES256, RS256
3439
};
3540

3641
const registrationOptions = await generateRegistrationOptions(
@@ -44,7 +49,8 @@ export const getRegistrationOptions = async (
4449
};
4550

4651
export const verifyRegistration = async (
47-
ephemeralWalletAddress: string,
52+
userName: string,
53+
jwtPubKey: string,
4854
registrationResponse: RegistrationResponseJSON
4955
): Promise<VerifiedRegistrationResponse> => {
5056
const db = await getOrCreateDatabase();
@@ -55,18 +61,18 @@ export const verifyRegistration = async (
5561
throw new Error("Invalid credentials");
5662
}
5763

58-
const challenge = db.registrationOptions[ephemeralWalletAddress].challenge;
64+
const dbChallenge = db.registrationOptions[userName].challenge;
5965

60-
if (!challenge) {
66+
if (!dbChallenge) {
6167
throw new Error(
6268
"No challenge found for this ephemeral wallet address in DB"
6369
);
6470
}
6571

66-
// Check the ephemeral wallet address provided againt the challenge in DB
72+
// Check the JWT public key provided against the challenge in DB
6773
const challengeCheck = await bcrypt.compare(
68-
ephemeralWalletAddress,
69-
isoBase64URL.toUTF8String(challenge)
74+
jwtPubKey,
75+
isoBase64URL.toUTF8String(dbChallenge)
7076
);
7177
if (!challengeCheck) {
7278
throw new Error("Challenge verification failed");
@@ -75,7 +81,7 @@ export const verifyRegistration = async (
7581
try {
7682
verificationResponse = await verifyRegistrationResponse({
7783
response: registrationResponse,
78-
expectedChallenge: challenge,
84+
expectedChallenge: dbChallenge,
7985
expectedOrigin: "http://localhost:3000",
8086
expectedRPID: "localhost",
8187
});
@@ -88,7 +94,7 @@ export const verifyRegistration = async (
8894
throw new Error("Registration verification failed");
8995
}
9096

91-
removeRegistrationOptions(ephemeralWalletAddress);
97+
removeRegistrationOptions(userName);
9298

9399
return verificationResponse;
94100
};

0 commit comments

Comments
 (0)