@kya-os/cli
Version:
CLI for MCP-I setup and management
159 lines • 5.26 kB
JavaScript
/**
* Identity management utilities for key rotation and archiving
*/
import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync, } from "fs";
import { join } from "path";
import crypto from "crypto";
/**
* Generate a new Ed25519 keypair
*/
export function generateKeyPair() {
// Generate Ed25519 keypair
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519", {
privateKeyEncoding: { type: "pkcs8", format: "der" },
publicKeyEncoding: { type: "spki", format: "der" },
});
// Convert to base64
const privateKeyBase64 = Buffer.from(privateKey).toString("base64");
const publicKeyBase64 = Buffer.from(publicKey).toString("base64");
// Generate key ID (first 8 chars of public key hash)
const kid = `key-${crypto.createHash("sha256").update(publicKey).digest("hex").slice(0, 8)}`;
return {
privateKey: privateKeyBase64,
publicKey: publicKeyBase64,
kid,
};
}
/**
* Generate a new DID based on the current environment
*/
export function generateDID(kid) {
const isDev = !process.env.AGENT_PRIVATE_KEY;
if (isDev) {
// Development DID
return `did:web:localhost:3000:agents:${kid}`;
}
else {
// Production DID - should be configured via environment
const existingDID = process.env.AGENT_DID;
if (existingDID) {
return existingDID;
}
throw new Error("AGENT_DID environment variable is required in production");
}
}
/**
* Load current identity from file or environment
*/
export function loadCurrentIdentity() {
const isDev = !process.env.AGENT_PRIVATE_KEY;
if (isDev) {
// Load from .mcpi/identity.json
const identityFile = join(process.cwd(), ".mcpi", "identity.json");
if (!existsSync(identityFile)) {
return null;
}
try {
const data = JSON.parse(readFileSync(identityFile, "utf-8"));
return data;
}
catch (error) {
console.warn(`Warning: Could not load identity file: ${error}`);
return null;
}
}
else {
// Load from environment variables
const privateKey = process.env.AGENT_PRIVATE_KEY;
const kid = process.env.AGENT_KEY_ID;
const did = process.env.AGENT_DID;
if (!privateKey || !kid || !did) {
return null;
}
// We don't have public key in env, but we can derive it from private key if needed
return {
version: "1.0",
did,
kid,
privateKey,
publicKey: "", // Not available from env
createdAt: new Date().toISOString(),
lastRotated: new Date().toISOString(),
};
}
}
/**
* Archive old key to .mcpi/keys/YYYY-MM-DD.json
*/
export function archiveOldKey(identity, reason) {
const keysDir = join(process.cwd(), ".mcpi", "keys");
if (!existsSync(keysDir)) {
mkdirSync(keysDir, { recursive: true });
}
// Create archive filename with timestamp
const timestamp = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
let archiveFile = join(keysDir, `${timestamp}.json`);
// If file exists, add a counter
let counter = 1;
while (existsSync(archiveFile)) {
archiveFile = join(keysDir, `${timestamp}-${counter}.json`);
counter++;
}
const archiveData = {
did: identity.did,
kid: identity.kid,
privateKey: identity.privateKey,
publicKey: identity.publicKey,
archivedAt: new Date().toISOString(),
reason: reason || "Key rotation",
originalCreatedAt: identity.createdAt,
originalLastRotated: identity.lastRotated,
};
writeFileSync(archiveFile, JSON.stringify(archiveData, null, 2));
return archiveFile;
}
/**
* Generate new identity with optional DID preservation
*/
export function generateNewIdentity(preserveDID) {
const { privateKey, publicKey, kid } = generateKeyPair();
const did = preserveDID || generateDID(kid);
const now = new Date().toISOString();
return {
version: "1.0",
did,
kid,
privateKey,
publicKey,
createdAt: now,
lastRotated: now,
};
}
/**
* Save identity to development file
*/
export function saveIdentityToDev(identity) {
const mcpiDir = join(process.cwd(), ".mcpi");
if (!existsSync(mcpiDir)) {
mkdirSync(mcpiDir, { recursive: true });
}
const identityFile = join(mcpiDir, "identity.json");
writeFileSync(identityFile, JSON.stringify(identity, null, 2));
}
/**
* Clear development identity file
*/
export function clearDevIdentity() {
const identityFile = join(process.cwd(), ".mcpi", "identity.json");
if (existsSync(identityFile)) {
unlinkSync(identityFile);
}
}
/**
* Generate audit line for key rotation
*/
export function generateAuditLine(oldKeyId, newKeyId, did, mode, delegated, forced) {
const ts = Math.floor(Date.now() / 1000);
return `keys.rotate.v1 ts=${ts} did=${did} oldKid=${oldKeyId} newKid=${newKeyId} mode=${mode} delegated=${delegated ? "yes" : "no"} force=${forced ? "yes" : "no"}`;
}
//# sourceMappingURL=identity-manager.js.map