feat(client): add keypair generation
This commit is contained in:
119
plugin/crypto/keypair.ts
Normal file
119
plugin/crypto/keypair.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { randomBytes, sign as signMessageRaw, verify as verifySignatureRaw } from "node:crypto";
|
||||
|
||||
/**
|
||||
* Key pair for Yonexus client authentication
|
||||
* Uses Ed25519 for digital signatures
|
||||
*/
|
||||
export interface KeyPair {
|
||||
/** Base64-encoded Ed25519 private key */
|
||||
readonly privateKey: string;
|
||||
/** Base64-encoded Ed25519 public key */
|
||||
readonly publicKey: string;
|
||||
/** Algorithm identifier for compatibility */
|
||||
readonly algorithm: "Ed25519";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new Ed25519 key pair for client authentication.
|
||||
*
|
||||
* In v1, we use Node.js crypto.generateKeyPairSync for Ed25519.
|
||||
* The keys are encoded as base64 for JSON serialization.
|
||||
*/
|
||||
export async function generateKeyPair(): Promise<KeyPair> {
|
||||
const { generateKeyPair } = await import("node:crypto");
|
||||
|
||||
const { publicKey, privateKey } = await new Promise<{
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}>((resolve, reject) => {
|
||||
generateKeyPair(
|
||||
"ed25519",
|
||||
{
|
||||
publicKeyEncoding: { type: "spki", format: "pem" },
|
||||
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
||||
},
|
||||
(err, pubKey, privKey) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve({ publicKey: pubKey, privateKey: privKey });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
privateKey,
|
||||
publicKey,
|
||||
algorithm: "Ed25519"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using the client's private key.
|
||||
*
|
||||
* @param privateKeyPem - PEM-encoded private key
|
||||
* @param message - Message to sign (Buffer or string)
|
||||
* @returns Base64-encoded signature
|
||||
*/
|
||||
export async function signMessage(
|
||||
privateKeyPem: string,
|
||||
message: Buffer | string
|
||||
): Promise<string> {
|
||||
const signature = signMessageRaw(null, typeof message === "string" ? Buffer.from(message) : message, privateKeyPem);
|
||||
return signature.toString("base64");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a signature using the client's public key.
|
||||
*
|
||||
* @param publicKeyPem - PEM-encoded public key
|
||||
* @param message - Original message (Buffer or string)
|
||||
* @param signature - Base64-encoded signature
|
||||
* @returns Whether the signature is valid
|
||||
*/
|
||||
export async function verifySignature(
|
||||
publicKeyPem: string,
|
||||
message: Buffer | string,
|
||||
signature: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const sigBuffer = Buffer.from(signature, "base64");
|
||||
return verifySignatureRaw(null, typeof message === "string" ? Buffer.from(message) : message, publicKeyPem, sigBuffer);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cryptographically secure random pairing code.
|
||||
* Format: XXXX-XXXX-XXXX (12 alphanumeric characters in groups of 4)
|
||||
*/
|
||||
export function generatePairingCode(): string {
|
||||
const bytes = randomBytes(8);
|
||||
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // Excludes confusing chars (0, O, 1, I)
|
||||
|
||||
let code = "";
|
||||
for (let i = 0; i < 12; i++) {
|
||||
code += chars[bytes[i % bytes.length] % chars.length];
|
||||
}
|
||||
|
||||
// Format as XXXX-XXXX-XXXX
|
||||
return `${code.slice(0, 4)}-${code.slice(4, 8)}-${code.slice(8, 12)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a shared secret for client authentication.
|
||||
* This is issued by the server after successful pairing.
|
||||
*/
|
||||
export function generateSecret(): string {
|
||||
return randomBytes(32).toString("base64url");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a 24-character nonce for authentication.
|
||||
*/
|
||||
export function generateNonce(): string {
|
||||
const bytes = randomBytes(18);
|
||||
return bytes.toString("base64url").slice(0, 24);
|
||||
}
|
||||
Reference in New Issue
Block a user