@open-agent-trust/cli
Advanced tools
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.compile = compile; | ||
| const promises_1 = require("fs/promises"); | ||
| const path_1 = require("path"); | ||
| const path_2 = require("path"); | ||
| const registry_artifacts_1 = require("../lib/registry-artifacts"); | ||
| async function compile(options) { | ||
| try { | ||
| console.log('Compiling registry manifest...'); | ||
| const registryDir = (0, path_1.join)(process.cwd(), '../registry'); | ||
| const issuersDir = (0, path_1.join)(registryDir, 'issuers'); | ||
| const files = await (0, promises_1.readdir)(issuersDir); | ||
| const entries = []; | ||
| for (const file of files) { | ||
| if (file.endsWith('.json')) { | ||
| const content = await (0, promises_1.readFile)((0, path_1.join)(issuersDir, file), 'utf8'); | ||
| const parsed = JSON.parse(content); | ||
| entries.push(parsed); | ||
| } | ||
| } | ||
| entries.sort((a, b) => a.issuer_id.localeCompare(b.issuer_id)); | ||
| const timestamp = new Date().toISOString(); | ||
| const manifestExpiry = new Date(Date.now() + 60 * 60 * 1000).toISOString(); | ||
| const revocationExpiry = new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString(); | ||
| const privateKeyPath = (0, path_2.resolve)(process.cwd(), options.privateKey); | ||
| const privateSeed = (await (0, promises_1.readFile)(privateKeyPath, 'utf8')).trim(); | ||
| const rootKey = await (0, registry_artifacts_1.resolveActiveRootKey)(registryDir, privateSeed); | ||
| const unsignedManifest = { | ||
| schema_version: '1.0.0', | ||
| registry_id: 'open-trust-registry', | ||
| generated_at: timestamp, | ||
| expires_at: manifestExpiry, | ||
| entries | ||
| }; | ||
| const manifest = { | ||
| ...unsignedManifest, | ||
| signature: await (0, registry_artifacts_1.signRegistryArtifact)(unsignedManifest, privateSeed, rootKey.kid) | ||
| }; | ||
| const revocationsPath = (0, path_1.join)(registryDir, 'revocations.json'); | ||
| const currentRevocations = JSON.parse(await (0, promises_1.readFile)(revocationsPath, 'utf8')); | ||
| const unsignedRevocations = { | ||
| schema_version: currentRevocations.schema_version ?? '1.0.0', | ||
| generated_at: timestamp, | ||
| expires_at: revocationExpiry, | ||
| revoked_keys: currentRevocations.revoked_keys ?? [], | ||
| revoked_issuers: currentRevocations.revoked_issuers ?? [] | ||
| }; | ||
| const revocations = { | ||
| ...unsignedRevocations, | ||
| signature: await (0, registry_artifacts_1.signRegistryArtifact)(unsignedRevocations, privateSeed, rootKey.kid) | ||
| }; | ||
| const outputPath = (0, path_1.join)(registryDir, 'manifest.json'); | ||
| await (0, promises_1.writeFile)(outputPath, JSON.stringify(manifest, null, 2)); | ||
| await (0, promises_1.writeFile)(revocationsPath, JSON.stringify(revocations, null, 2)); | ||
| console.log('✅ Success!'); | ||
| console.log(`Signed Manifest saved to: ${outputPath}`); | ||
| console.log(`Signed Revocations saved to: ${revocationsPath}`); | ||
| console.log(`Total Issuers: ${manifest.entries.length}`); | ||
| } | ||
| catch (error) { | ||
| console.error('❌ Failed to compile registry:', error.message); | ||
| process.exit(1); | ||
| } | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.issue = void 0; | ||
| const jose_1 = require("jose"); | ||
| const promises_1 = require("fs/promises"); | ||
| const path_1 = require("path"); | ||
| const crypto_1 = require("crypto"); | ||
| const issue = async (options) => { | ||
| try { | ||
| console.log('[1/3] Loading Ed25519 private key...'); | ||
| const keyPath = (0, path_1.resolve)(process.cwd(), options.privateKey); | ||
| const privateKeyB64 = await (0, promises_1.readFile)(keyPath, 'utf-8'); | ||
| const privateKeyBytes = Buffer.from(privateKeyB64.trim(), 'base64url'); | ||
| const seedHex = privateKeyBytes.subarray(0, 32).toString('hex'); | ||
| console.log('[2/3] Preparing agent-attestation+jwt payload...'); | ||
| const now = Math.floor(Date.now() / 1000); | ||
| const payload = { | ||
| sub: 'agent-instance-' + crypto.randomUUID().slice(0, 8), | ||
| aud: options.audience, | ||
| iat: now, | ||
| nonce: crypto.randomUUID(), | ||
| scope: options.scope.split(',').map(s => s.trim()), | ||
| constraints: { | ||
| time_bound: true | ||
| }, | ||
| user_pseudonym: 'pairwise-' + crypto.randomUUID().slice(0, 8), | ||
| runtime_version: '1.0.0' | ||
| }; | ||
| console.log('[3/3] Signing EdDSA attestation token...'); | ||
| // Wrap the raw 32-byte Ed25519 seed in a PKCS#8 DER ASN.1 structure | ||
| // 302e020100300506032b657004220420 is the standard ASN.1 prefix for Ed25519 private keys | ||
| const pkcs8Der = Buffer.from('302e020100300506032b657004220420' + seedHex, 'hex'); | ||
| const privateKeyObj = (0, crypto_1.createPrivateKey)({ | ||
| key: pkcs8Der, | ||
| format: 'der', | ||
| type: 'pkcs8' | ||
| }); | ||
| const jwt = await new jose_1.SignJWT(payload) | ||
| .setProtectedHeader({ | ||
| alg: 'EdDSA', | ||
| kid: options.kid, | ||
| iss: options.issuerId, | ||
| typ: 'agent-attestation+jwt' | ||
| }) | ||
| .setExpirationTime(now + options.expiresIn) | ||
| .sign(privateKeyObj); | ||
| console.log('\n✓ Test Attestation Generated Successfully:'); | ||
| console.log('--------------------------------------------------'); | ||
| console.log(jwt); | ||
| console.log('--------------------------------------------------'); | ||
| console.log(`\nTo test verification, copy the string above and run:\nagent-trust verify <TOKEN> --audience ${options.audience}`); | ||
| } | ||
| catch (err) { | ||
| if (err instanceof Error) { | ||
| console.error('\n❌ Failed to generate test attestation:', err.message); | ||
| } | ||
| else { | ||
| console.error('\n❌ An unexpected error occurred while generating the test attestation.'); | ||
| } | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| exports.issue = issue; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.keygen = void 0; | ||
| const ed = __importStar(require("@noble/ed25519")); | ||
| const crypto = __importStar(require("crypto")); | ||
| const fs = __importStar(require("fs")); | ||
| const path = __importStar(require("path")); | ||
| // Polyfill for noble in raw node environments | ||
| if (!globalThis.crypto) { | ||
| globalThis.crypto = crypto.webcrypto; | ||
| } | ||
| const keygen = async (options) => { | ||
| try { | ||
| console.log(`\nGenerating Ed25519 keypair for issuer '${options.issuerId}'...\n`); | ||
| const privateKeyRaw = ed.utils.randomSecretKey(); | ||
| const publicKeyRaw = await ed.getPublicKeyAsync(privateKeyRaw); | ||
| const privateKeyBase64Url = Buffer.from(privateKeyRaw).toString('base64url'); | ||
| const publicKeyBase64Url = Buffer.from(publicKeyRaw).toString('base64url'); | ||
| // Create the kid (Key ID) using a stable date prefix | ||
| const dateStr = new Date().toISOString().substring(0, 7); // e.g., 2026-03 | ||
| const kid = `${options.issuerId}-${dateStr}`; | ||
| // Industry standard .pem extension with 'private' in filename for clarity | ||
| const privateKeyPath = path.join(options.outDir, `${options.issuerId}.private.pem`); | ||
| fs.writeFileSync(privateKeyPath, privateKeyBase64Url, { mode: 0o600 }); | ||
| console.log(`✅ Keypair generated successfully!\n`); | ||
| console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`); | ||
| console.log(` Private Key: ${privateKeyPath}`); | ||
| console.log(` KID: ${kid}`); | ||
| console.log(` Algorithm: Ed25519`); | ||
| console.log(` Public Key: ${publicKeyBase64Url}`); | ||
| console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`); | ||
| console.log(`⚠️ Keep your private key secret. Never commit it to a repo.\n`); | ||
| console.log(` To view it: cat ${privateKeyPath}`); | ||
| console.log(` To secure it: chmod 600 ${privateKeyPath}\n`); | ||
| console.log(` Note: Do not double-click the .pem file. On macOS, this opens`); | ||
| console.log(` Keychain Access. Always use 'cat' or a text editor from the terminal.\n`); | ||
| console.log(`Next steps:\n`); | ||
| console.log(` 1. Add your public key to your agent.json for Tier 3 identity:\n`); | ||
| console.log(` "identity": {`); | ||
| console.log(` "did": "did:web:yourdomain.com",`); | ||
| console.log(` "public_key": "${publicKeyBase64Url}"`); | ||
| console.log(` }\n`); | ||
| console.log(` 2. Host a DID document at https://yourdomain.com/.well-known/did.json`); | ||
| console.log(` (See docs: https://agentinternetruntime.com/spec/agent-json#becoming-tier-3)\n`); | ||
| console.log(` 3. To register as a trusted runtime issuer in the Trust Registry:`); | ||
| console.log(` npx @open-agent-trust/cli register --issuer-id ${options.issuerId} \\`); | ||
| console.log(` --display-name "Your Display Name" --website https://yourdomain.com \\`); | ||
| console.log(` --contact security@yourdomain.com --public-key ${publicKeyBase64Url}\n`); | ||
| } | ||
| catch (err) { | ||
| console.error('Failed to generate keypair:', err); | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| exports.keygen = keygen; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.prove = void 0; | ||
| const promises_1 = require("fs/promises"); | ||
| const path_1 = require("path"); | ||
| const ed = __importStar(require("@noble/ed25519")); | ||
| const PROOF_VERSION = 'oatr-proof-v1'; | ||
| const ISSUER_ID_PATTERN = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/; | ||
| const prove = async (options) => { | ||
| try { | ||
| // Validate issuer_id format | ||
| if (!ISSUER_ID_PATTERN.test(options.issuerId)) { | ||
| throw new Error(`Invalid issuer_id "${options.issuerId}". Must be lowercase alphanumeric and hyphens only, ` + | ||
| `no leading/trailing hyphens. Example: my-runtime`); | ||
| } | ||
| console.log('[1/3] Loading Ed25519 private key...'); | ||
| const keyPath = (0, path_1.resolve)(process.cwd(), options.privateKey); | ||
| const privateKeyB64 = await (0, promises_1.readFile)(keyPath, 'utf-8'); | ||
| const privateKeyBuffer = Buffer.from(privateKeyB64.trim(), 'base64url'); | ||
| if (privateKeyBuffer.length !== 32) { | ||
| throw new Error(`Invalid private key length (${privateKeyBuffer.length} bytes). ` + | ||
| `Must be 32 bytes (base64url encoded). Ensure this is an Ed25519 seed from 'agent-trust keygen'.`); | ||
| } | ||
| console.log('[2/3] Signing proof-of-key-ownership...'); | ||
| const canonicalMessage = `${PROOF_VERSION}:${options.issuerId}`; | ||
| const messageBytes = Buffer.from(canonicalMessage, 'utf8'); | ||
| const signatureBytes = await ed.signAsync(messageBytes, privateKeyBuffer); | ||
| const signature = Buffer.from(signatureBytes).toString('base64url'); | ||
| const proofContent = [ | ||
| '-----BEGIN OATR KEY OWNERSHIP PROOF-----', | ||
| `Canonical-Message: ${canonicalMessage}`, | ||
| `Signature: ${signature}`, | ||
| '-----END OATR KEY OWNERSHIP PROOF-----', | ||
| '' // trailing newline for POSIX compliance | ||
| ].join('\n'); | ||
| console.log('[3/3] Writing proof file...'); | ||
| const outPath = options.outFile | ||
| ? (0, path_1.resolve)(process.cwd(), options.outFile) | ||
| : (0, path_1.join)(process.cwd(), 'registry', 'proofs', `${options.issuerId}.proof`); | ||
| await (0, promises_1.mkdir)((0, path_1.dirname)(outPath), { recursive: true }); | ||
| await (0, promises_1.writeFile)(outPath, proofContent); | ||
| console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`); | ||
| console.log(` ✅ Proof of Key Ownership Generated`); | ||
| console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`); | ||
| console.log(` Issuer ID: ${options.issuerId}`); | ||
| console.log(` Proof file: ${outPath}`); | ||
| console.log(` Format: ${PROOF_VERSION}`); | ||
| console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`); | ||
| console.log(`Next steps:\n`); | ||
| console.log(` 1. Ensure your issuer JSON is at registry/issuers/${options.issuerId}.json`); | ||
| console.log(` 2. Ensure your domain verification is live at:`); | ||
| console.log(` https://yourdomain.com/.well-known/agent-trust.json\n`); | ||
| console.log(` 3. Submit a Pull Request with both files:`); | ||
| console.log(` - registry/issuers/${options.issuerId}.json`); | ||
| console.log(` - registry/proofs/${options.issuerId}.proof\n`); | ||
| console.log(` The CI pipeline will verify your proof, check your domain,`); | ||
| console.log(` and auto-merge if all checks pass.\n`); | ||
| } | ||
| catch (error) { | ||
| console.error('❌ Failed to generate proof:', error.message); | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| exports.prove = prove; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.register = void 0; | ||
| const fs = __importStar(require("fs")); | ||
| const path = __importStar(require("path")); | ||
| const register = async (options) => { | ||
| const nowISO = new Date().toISOString(); | ||
| // Hardcoded to standard generic capabilities for Phase 1 scaffolding. | ||
| // Real users would edit this draft JSON file. | ||
| let entry = { | ||
| "issuer_id": options.issuerId, | ||
| "display_name": options.displayName, | ||
| "website": options.website, | ||
| "security_contact": options.contact, | ||
| "status": "active", | ||
| "added_at": nowISO, | ||
| "last_verified": nowISO, | ||
| "public_keys": [ | ||
| { | ||
| "kid": `${options.issuerId}-${nowISO.substring(0, 7)}`, | ||
| "algorithm": "Ed25519", | ||
| "public_key": options.publicKey, | ||
| "status": "active", | ||
| "issued_at": nowISO, | ||
| "expires_at": new Date(Date.now() + 31536000000).toISOString(), // +1 year | ||
| "deprecated_at": null, | ||
| "revoked_at": null | ||
| } | ||
| ], | ||
| "capabilities": { | ||
| "supervision_model": "tiered", | ||
| "audit_logging": true, | ||
| "immutable_audit": false, | ||
| "attestation_format": "jwt", | ||
| "max_attestation_ttl_seconds": 3600, | ||
| "capabilities_verified": false | ||
| } | ||
| }; | ||
| const outPath = options.outFile || path.join(process.cwd(), `${options.issuerId}.json`); | ||
| fs.writeFileSync(outPath, JSON.stringify(entry, null, 2)); | ||
| console.log(`✅ Draft Issuer Entry generated at: ${outPath}`); | ||
| console.log(`\nPlease review the \"capabilities\" block to ensure it matches your runtime's exact profile.`); | ||
| console.log(`When ready, submit this file as a Pull Request to 'registry/issuers/' in the open source repository.`); | ||
| }; | ||
| exports.register = register; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.verify = void 0; | ||
| const src_1 = require("../../../sdk/typescript/src"); | ||
| const verify = async (attestation, options) => { | ||
| try { | ||
| console.log(`[1/2] Fetching registry manifest from mirror (${options.mirror})...`); | ||
| const registry = await src_1.OpenAgentTrustRegistry.load(options.mirror); | ||
| console.log(`[2/2] Attempting 14-step verification against audience bounds (${options.audience})...`); | ||
| const result = await registry.verifyToken(attestation, options.audience); | ||
| if (result.valid) { | ||
| console.log(`\n✅ Valid Attestation`); | ||
| console.log(`Issuer: ${result.issuer?.display_name} (${result.issuer?.issuer_id})`); | ||
| console.log(`Token Subject: ${result.claims?.sub}`); | ||
| console.log(`Authorized As: ${result.claims?.user_pseudonym}`); | ||
| console.log(`Expires: ${new Date(result.claims?.exp * 1000).toISOString()}`); | ||
| console.log(`Constraints: `, result.claims?.constraints); | ||
| process.exit(0); | ||
| } | ||
| else { | ||
| console.error(`\n❌ Token mathematically rejected by the Registry.`); | ||
| console.error(`Reason: ${result.reason}`); | ||
| if (result.issuer) { | ||
| console.error(`Identified Issuer: ${result.issuer.display_name}`); | ||
| } | ||
| process.exit(1); | ||
| } | ||
| } | ||
| catch (err) { | ||
| if (err instanceof src_1.OpenAgentTrustRegistryError) { | ||
| console.error(`\n❌ Registry state rejected: ${err.code}`); | ||
| console.error(err.message); | ||
| process.exit(1); | ||
| } | ||
| console.error(`\n❌ Verification engine failure: ${err.message}`); | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| exports.verify = verify; |
| #!/usr/bin/env node | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| const commander_1 = require("commander"); | ||
| const keygen_1 = require("./commands/keygen"); | ||
| const register_1 = require("./commands/register"); | ||
| const verify_1 = require("./commands/verify"); | ||
| const issue_1 = require("./commands/issue"); | ||
| const compile_1 = require("./commands/compile"); | ||
| const prove_1 = require("./commands/prove"); | ||
| const program = new commander_1.Command(); | ||
| program | ||
| .name('agent-trust') | ||
| .description('Open Agent Trust Registry CLI Utilities') | ||
| .version('1.0.0'); | ||
| program | ||
| .command('keygen') | ||
| .description('Generate a new Ed25519 keypair for your runtime') | ||
| .requiredOption('-i, --issuer-id <string>', 'Your globally unique issuer identifier') | ||
| .option('-o, --out-dir <path>', 'Directory to save the private key', './') | ||
| .action(keygen_1.keygen); | ||
| program | ||
| .command('register') | ||
| .description('Generate a compliant issuer_entry JSON file') | ||
| .requiredOption('-i, --issuer-id <string>', 'Your globally unique issuer identifier') | ||
| .requiredOption('-n, --display-name <string>', 'Human-readable name of the runtime') | ||
| .requiredOption('-w, --website <url>', 'Public resolving website of the operator') | ||
| .requiredOption('-c, --contact <email>', 'Security responsible contact email') | ||
| .requiredOption('-k, --public-key <base64url>', 'The base64url Ed25519 public key generated from keygen') | ||
| .option('-o, --out-file <path>', 'Output path for the generated JSON') | ||
| .action(register_1.register); | ||
| program | ||
| .command('verify') | ||
| .description('Verify an agent attestation JWS against the registry') | ||
| .argument('<attestation>', 'The raw JWT/JWS token string') | ||
| .requiredOption('-a, --audience <url>', 'The origin URL of the service expecting the token') | ||
| .option('-m, --mirror <url>', 'Custom registry mirror URL', 'http://localhost:3000') | ||
| .action(verify_1.verify); | ||
| program | ||
| .command('issue') | ||
| .description('Generate a signed test agent-attestation+jwt token (useful for local integration testing)') | ||
| .requiredOption('-i, --issuer-id <string>', 'Your globally unique issuer identifier') | ||
| .requiredOption('-k, --kid <string>', 'The key ID (kid) of the key used to sign') | ||
| .requiredOption('-p, --private-key <path>', 'Path to your .key file generated by keygen') | ||
| .requiredOption('-a, --audience <url>', 'The origin URL of the service expecting the token') | ||
| .option('-s, --scope <string>', 'Comma-separated list of scopes (e.g., read:email,send:email)', 'read:data') | ||
| .option('-e, --expires-in <seconds>', 'Expiration time in seconds', (val) => parseInt(val, 10), 3600) | ||
| .action(issue_1.issue); | ||
| program | ||
| .command('compile') | ||
| .description('Compiles and signs manifest.json and revocations.json from the registry folder') | ||
| .requiredOption('-p, --private-key <path>', 'Path to the Ed25519 root private key seed file') | ||
| .action(compile_1.compile); | ||
| program | ||
| .command('prove') | ||
| .description('Generate a cryptographic proof-of-key-ownership for registry registration') | ||
| .requiredOption('-i, --issuer-id <string>', 'Your globally unique issuer identifier') | ||
| .requiredOption('-p, --private-key <path>', 'Path to your .private.pem key file') | ||
| .option('-o, --out-file <path>', 'Output path for the proof file') | ||
| .action(prove_1.prove); | ||
| program.parse(process.argv); |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.canonicalizeJson = canonicalizeJson; | ||
| exports.createPrivateKeyFromSeed = createPrivateKeyFromSeed; | ||
| exports.loadRootKeySet = loadRootKeySet; | ||
| exports.resolveActiveRootKey = resolveActiveRootKey; | ||
| exports.signRegistryArtifact = signRegistryArtifact; | ||
| const promises_1 = require("fs/promises"); | ||
| const crypto_1 = require("crypto"); | ||
| const path_1 = require("path"); | ||
| const ed = __importStar(require("@noble/ed25519")); | ||
| function canonicalizeJson(value) { | ||
| if (value === null) | ||
| return 'null'; | ||
| switch (typeof value) { | ||
| case 'boolean': | ||
| return value ? 'true' : 'false'; | ||
| case 'number': | ||
| if (!Number.isFinite(value)) { | ||
| throw new Error('Canonical JSON does not support non-finite numbers.'); | ||
| } | ||
| return JSON.stringify(value); | ||
| case 'string': | ||
| return JSON.stringify(value); | ||
| case 'object': | ||
| if (Array.isArray(value)) { | ||
| return `[${value.map((item) => canonicalizeJson(item)).join(',')}]`; | ||
| } | ||
| return `{${Object.keys(value) | ||
| .sort() | ||
| .map((key) => `${JSON.stringify(key)}:${canonicalizeJson(value[key])}`) | ||
| .join(',')}}`; | ||
| default: | ||
| throw new Error(`Unsupported value type for canonical JSON: ${typeof value}`); | ||
| } | ||
| } | ||
| function createPrivateKeyFromSeed(seedBase64Url) { | ||
| const privateKeyBytes = Buffer.from(seedBase64Url.trim(), 'base64url'); | ||
| if (privateKeyBytes.length !== 32) { | ||
| throw new Error(`Invalid private key length (${privateKeyBytes.length} bytes). Must be 32 bytes.`); | ||
| } | ||
| const seedHex = privateKeyBytes.toString('hex'); | ||
| const pkcs8Der = Buffer.from(`302e020100300506032b657004220420${seedHex}`, 'hex'); | ||
| return (0, crypto_1.createPrivateKey)({ | ||
| key: pkcs8Der, | ||
| format: 'der', | ||
| type: 'pkcs8' | ||
| }); | ||
| } | ||
| async function loadRootKeySet(registryDir) { | ||
| const raw = await (0, promises_1.readFile)((0, path_1.join)(registryDir, 'root-keys.json'), 'utf8'); | ||
| return JSON.parse(raw); | ||
| } | ||
| async function resolveActiveRootKey(registryDir, privateSeed) { | ||
| const publicKey = Buffer.from(await ed.getPublicKeyAsync(Buffer.from(privateSeed.trim(), 'base64url'))).toString('base64url'); | ||
| const rootKeys = await loadRootKeySet(registryDir); | ||
| const rootKey = rootKeys.keys.find((entry) => entry.public_key === publicKey && entry.status === 'active'); | ||
| if (!rootKey) { | ||
| throw new Error('The supplied root private key does not match any active key in registry/root-keys.json.'); | ||
| } | ||
| return rootKey; | ||
| } | ||
| async function signRegistryArtifact(unsignedArtifact, privateSeed, signatureKid) { | ||
| const canonicalPayload = canonicalizeJson(unsignedArtifact); | ||
| const signatureBytes = await ed.signAsync(Buffer.from(canonicalPayload, 'utf8'), Buffer.from(privateSeed.trim(), 'base64url')); | ||
| return { | ||
| algorithm: 'Ed25519', | ||
| kid: signatureKid, | ||
| value: Buffer.from(signatureBytes).toString('base64url') | ||
| }; | ||
| } |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.canonicalizeJson = canonicalizeJson; | ||
| exports.createPrivateKeyFromSeed = createPrivateKeyFromSeed; | ||
| exports.loadRootKeySet = loadRootKeySet; | ||
| exports.resolveActiveRootKey = resolveActiveRootKey; | ||
| exports.signRegistryArtifact = signRegistryArtifact; | ||
| const promises_1 = require("fs/promises"); | ||
| const crypto_1 = require("crypto"); | ||
| const path_1 = require("path"); | ||
| const ed = __importStar(require("@noble/ed25519")); | ||
| function canonicalizeJson(value) { | ||
| if (value === null) | ||
| return 'null'; | ||
| switch (typeof value) { | ||
| case 'boolean': | ||
| return value ? 'true' : 'false'; | ||
| case 'number': | ||
| if (!Number.isFinite(value)) { | ||
| throw new Error('Canonical JSON does not support non-finite numbers.'); | ||
| } | ||
| return JSON.stringify(value); | ||
| case 'string': | ||
| return JSON.stringify(value); | ||
| case 'object': | ||
| if (Array.isArray(value)) { | ||
| return `[${value.map((item) => canonicalizeJson(item)).join(',')}]`; | ||
| } | ||
| return `{${Object.keys(value) | ||
| .sort() | ||
| .map((key) => `${JSON.stringify(key)}:${canonicalizeJson(value[key])}`) | ||
| .join(',')}}`; | ||
| default: | ||
| throw new Error(`Unsupported value type for canonical JSON: ${typeof value}`); | ||
| } | ||
| } | ||
| function createPrivateKeyFromSeed(seedBase64Url) { | ||
| const privateKeyBytes = Buffer.from(seedBase64Url.trim(), 'base64url'); | ||
| if (privateKeyBytes.length !== 32) { | ||
| throw new Error(`Invalid private key length (${privateKeyBytes.length} bytes). Must be 32 bytes.`); | ||
| } | ||
| const seedHex = privateKeyBytes.toString('hex'); | ||
| const pkcs8Der = Buffer.from(`302e020100300506032b657004220420${seedHex}`, 'hex'); | ||
| return (0, crypto_1.createPrivateKey)({ | ||
| key: pkcs8Der, | ||
| format: 'der', | ||
| type: 'pkcs8' | ||
| }); | ||
| } | ||
| async function loadRootKeySet(registryDir) { | ||
| const raw = await (0, promises_1.readFile)((0, path_1.join)(registryDir, 'root-keys.json'), 'utf8'); | ||
| return JSON.parse(raw); | ||
| } | ||
| async function resolveActiveRootKey(registryDir, privateSeed) { | ||
| const publicKey = Buffer.from(await ed.getPublicKeyAsync(Buffer.from(privateSeed.trim(), 'base64url'))).toString('base64url'); | ||
| const rootKeys = await loadRootKeySet(registryDir); | ||
| const rootKey = rootKeys.keys.find((entry) => entry.public_key === publicKey && entry.status === 'active'); | ||
| if (!rootKey) { | ||
| throw new Error('The supplied root private key does not match any active key in registry/root-keys.json.'); | ||
| } | ||
| return rootKey; | ||
| } | ||
| async function signRegistryArtifact(unsignedArtifact, privateSeed, signatureKid) { | ||
| const canonicalPayload = canonicalizeJson(unsignedArtifact); | ||
| const signatureBytes = await ed.signAsync(Buffer.from(canonicalPayload, 'utf8'), Buffer.from(privateSeed.trim(), 'base64url')); | ||
| return { | ||
| algorithm: 'Ed25519', | ||
| kid: signatureKid, | ||
| value: Buffer.from(signatureBytes).toString('base64url') | ||
| }; | ||
| } |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
| for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.OpenAgentTrustRegistry = void 0; | ||
| var registry_1 = require("./registry"); | ||
| Object.defineProperty(exports, "OpenAgentTrustRegistry", { enumerable: true, get: function () { return registry_1.OpenAgentTrustRegistry; } }); | ||
| __exportStar(require("./verify"), exports); | ||
| __exportStar(require("./registry-artifacts"), exports); | ||
| __exportStar(require("./types"), exports); |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.OpenAgentTrustRegistryError = void 0; | ||
| exports.loadBundledRootKeys = loadBundledRootKeys; | ||
| exports.verifyRegistryArtifacts = verifyRegistryArtifacts; | ||
| exports.canonicalizeRegistryArtifact = canonicalizeRegistryArtifact; | ||
| const fs_1 = require("fs"); | ||
| const path_1 = require("path"); | ||
| const crypto_1 = require("crypto"); | ||
| class OpenAgentTrustRegistryError extends Error { | ||
| code; | ||
| constructor(code, message) { | ||
| super(message); | ||
| this.name = 'OpenAgentTrustRegistryError'; | ||
| this.code = code; | ||
| } | ||
| } | ||
| exports.OpenAgentTrustRegistryError = OpenAgentTrustRegistryError; | ||
| function isRecord(value) { | ||
| return typeof value === 'object' && value !== null && !Array.isArray(value); | ||
| } | ||
| function assertString(value, label) { | ||
| if (typeof value !== 'string' || value.length === 0) { | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', `${label} must be a non-empty string`); | ||
| } | ||
| return value; | ||
| } | ||
| function parseDate(value, label) { | ||
| const parsed = new Date(value); | ||
| if (Number.isNaN(parsed.getTime())) { | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', `${label} must be a valid ISO 8601 timestamp`); | ||
| } | ||
| return parsed; | ||
| } | ||
| function canonicalize(value) { | ||
| if (value === null) | ||
| return 'null'; | ||
| switch (typeof value) { | ||
| case 'boolean': | ||
| return value ? 'true' : 'false'; | ||
| case 'number': | ||
| if (!Number.isFinite(value)) { | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', 'Canonical JSON does not support non-finite numbers'); | ||
| } | ||
| return JSON.stringify(value); | ||
| case 'string': | ||
| return JSON.stringify(value); | ||
| case 'object': | ||
| if (Array.isArray(value)) { | ||
| return `[${value.map((item) => canonicalize(item)).join(',')}]`; | ||
| } | ||
| return `{${Object.keys(value) | ||
| .sort() | ||
| .map((key) => `${JSON.stringify(key)}:${canonicalize(value[key])}`) | ||
| .join(',')}}`; | ||
| default: | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', `Unsupported canonical JSON value type: ${typeof value}`); | ||
| } | ||
| } | ||
| function loadBundledJson(relativePath) { | ||
| const absolutePath = (0, path_1.resolve)(__dirname, '..', relativePath); | ||
| return JSON.parse((0, fs_1.readFileSync)(absolutePath, 'utf8')); | ||
| } | ||
| function loadBundledRootKeys() { | ||
| const rootKeys = loadBundledJson('root-keys.json'); | ||
| if (!isRecord(rootKeys) || !Array.isArray(rootKeys.keys)) { | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', 'Bundled root-keys.json is malformed'); | ||
| } | ||
| return rootKeys; | ||
| } | ||
| function assertRegistrySignature(signature) { | ||
| if (!isRecord(signature)) { | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', 'signature must be an object'); | ||
| } | ||
| const algorithm = assertString(signature.algorithm, 'signature.algorithm'); | ||
| if (algorithm !== 'Ed25519') { | ||
| throw new OpenAgentTrustRegistryError('invalid_registry_signature', `Unsupported registry signature algorithm: ${algorithm}`); | ||
| } | ||
| return { | ||
| algorithm: 'Ed25519', | ||
| kid: assertString(signature.kid, 'signature.kid'), | ||
| value: assertString(signature.value, 'signature.value') | ||
| }; | ||
| } | ||
| function assertManifestLike(manifest) { | ||
| if (!isRecord(manifest)) { | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', 'manifest must be an object'); | ||
| } | ||
| assertString(manifest.schema_version, 'manifest.schema_version'); | ||
| assertString(manifest.registry_id, 'manifest.registry_id'); | ||
| assertString(manifest.generated_at, 'manifest.generated_at'); | ||
| assertString(manifest.expires_at, 'manifest.expires_at'); | ||
| if (!Array.isArray(manifest.entries)) { | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', 'manifest.entries must be an array'); | ||
| } | ||
| assertRegistrySignature(manifest.signature); | ||
| } | ||
| function assertRevocationListLike(revocations) { | ||
| if (!isRecord(revocations)) { | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', 'revocations must be an object'); | ||
| } | ||
| assertString(revocations.schema_version, 'revocations.schema_version'); | ||
| assertString(revocations.generated_at, 'revocations.generated_at'); | ||
| assertString(revocations.expires_at, 'revocations.expires_at'); | ||
| if (!Array.isArray(revocations.revoked_keys) || !Array.isArray(revocations.revoked_issuers)) { | ||
| throw new OpenAgentTrustRegistryError('malformed_registry_state', 'revocation lists must be arrays'); | ||
| } | ||
| assertRegistrySignature(revocations.signature); | ||
| } | ||
| function findTrustedRootKey(rootKeys, signature, now) { | ||
| const rootKey = rootKeys.keys.find((entry) => entry.kid === signature.kid); | ||
| if (!rootKey) { | ||
| throw new OpenAgentTrustRegistryError('unknown_root_key', `Unknown root key id: ${signature.kid}`); | ||
| } | ||
| if (rootKey.status !== 'active') { | ||
| throw new OpenAgentTrustRegistryError('invalid_registry_signature', `Root key ${signature.kid} is not active`); | ||
| } | ||
| const notBefore = parseDate(rootKey.not_before, `root key ${signature.kid} not_before`); | ||
| if (now < notBefore) { | ||
| throw new OpenAgentTrustRegistryError('invalid_registry_signature', `Root key ${signature.kid} is not valid yet`); | ||
| } | ||
| if (rootKey.not_after) { | ||
| const notAfter = parseDate(rootKey.not_after, `root key ${signature.kid} not_after`); | ||
| if (now > notAfter) { | ||
| throw new OpenAgentTrustRegistryError('invalid_registry_signature', `Root key ${signature.kid} has expired`); | ||
| } | ||
| } | ||
| return rootKey; | ||
| } | ||
| function verifySignedArtifact(artifact, kind, rootKeys, now) { | ||
| const signature = assertRegistrySignature(artifact.signature); | ||
| const trustedRootKey = findTrustedRootKey(rootKeys, signature, now); | ||
| const expiresAt = parseDate(artifact.expires_at, `${kind}.expires_at`); | ||
| if (now > expiresAt) { | ||
| throw new OpenAgentTrustRegistryError('stale_registry_state', `${kind} is expired as of ${artifact.expires_at}`); | ||
| } | ||
| const unsignedArtifact = { ...artifact, signature: undefined }; | ||
| delete unsignedArtifact.signature; | ||
| const canonicalBytes = Buffer.from(canonicalize(unsignedArtifact), 'utf8'); | ||
| const signatureBytes = Buffer.from(signature.value, 'base64url'); | ||
| const publicKey = (0, crypto_1.createPublicKey)({ | ||
| key: { kty: 'OKP', crv: 'Ed25519', x: trustedRootKey.public_key }, | ||
| format: 'jwk' | ||
| }); | ||
| const isValid = (0, crypto_1.verify)(null, canonicalBytes, publicKey, signatureBytes); | ||
| if (!isValid) { | ||
| throw new OpenAgentTrustRegistryError('invalid_registry_signature', `${kind} signature verification failed`); | ||
| } | ||
| } | ||
| function verifyRegistryArtifacts(manifest, revocations, options = {}) { | ||
| const now = options.now ?? new Date(); | ||
| const rootKeys = options.rootKeys ?? loadBundledRootKeys(); | ||
| assertManifestLike(manifest); | ||
| assertRevocationListLike(revocations); | ||
| verifySignedArtifact(manifest, 'manifest', rootKeys, now); | ||
| verifySignedArtifact(revocations, 'revocations', rootKeys, now); | ||
| return { manifest, revocations }; | ||
| } | ||
| function canonicalizeRegistryArtifact(value) { | ||
| return canonicalize(value); | ||
| } |
| "use strict"; | ||
| // sdk/typescript/src/registry.ts | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.OpenAgentTrustRegistry = void 0; | ||
| const verify_1 = require("./verify"); | ||
| const registry_artifacts_1 = require("./registry-artifacts"); | ||
| class OpenAgentTrustRegistry { | ||
| mirrorUrl; | ||
| manifest = null; | ||
| revocations = null; | ||
| lastFetchTime = 0; | ||
| // Refresh cache every 15 minutes by default | ||
| CACHE_TTL_MS = 15 * 60 * 1000; | ||
| constructor(mirrorUrl) { | ||
| this.mirrorUrl = mirrorUrl; | ||
| } | ||
| /** | ||
| * Initialize a new Registry Client and fetch the initial state. | ||
| */ | ||
| static async load(mirrorUrl) { | ||
| const registry = new OpenAgentTrustRegistry(mirrorUrl.replace(/\/$/, '')); | ||
| await registry.refresh(); | ||
| return registry; | ||
| } | ||
| /** | ||
| * Manually trigger a refresh of the cached registry state from the network. | ||
| */ | ||
| async refresh() { | ||
| try { | ||
| const [manifestRes, revocationsRes] = await Promise.all([ | ||
| fetch(`${this.mirrorUrl}/v1/registry`), | ||
| fetch(`${this.mirrorUrl}/v1/revocations`) | ||
| ]); | ||
| if (!manifestRes.ok || !revocationsRes.ok) { | ||
| throw new registry_artifacts_1.OpenAgentTrustRegistryError('fetch_failed', `Failed to fetch registry state: ${manifestRes.status} / ${revocationsRes.status}`); | ||
| } | ||
| const manifest = await manifestRes.json(); | ||
| const revocations = await revocationsRes.json(); | ||
| const verifiedState = (0, registry_artifacts_1.verifyRegistryArtifacts)(manifest, revocations); | ||
| this.manifest = verifiedState.manifest; | ||
| this.revocations = verifiedState.revocations; | ||
| this.lastFetchTime = Date.now(); | ||
| } | ||
| catch (err) { | ||
| console.error('[OpenAgentTrustRegistry] Failed to refresh state', err); | ||
| throw err; | ||
| } | ||
| } | ||
| /** | ||
| * Verify an incoming agent attestation JWS token locally against the loaded registry state. | ||
| * | ||
| * @param attestationJws The raw string JWS token | ||
| * @param expectedAudience The specific service origin (aud) expecting this token | ||
| * @param expectedNonce Optional nonce if the service requires intra-service replay protection | ||
| */ | ||
| async verifyToken(attestationJws, expectedAudience, expectedNonce) { | ||
| // Auto-refresh if cache is stale | ||
| if (Date.now() - this.lastFetchTime > this.CACHE_TTL_MS) { | ||
| await this.refresh(); | ||
| } | ||
| if (!this.manifest || !this.revocations) { | ||
| throw new registry_artifacts_1.OpenAgentTrustRegistryError('registry_not_loaded', 'Registry state not loaded'); | ||
| } | ||
| const verifiedState = (0, registry_artifacts_1.verifyRegistryArtifacts)(this.manifest, this.revocations); | ||
| this.manifest = verifiedState.manifest; | ||
| this.revocations = verifiedState.revocations; | ||
| return (0, verify_1.verifyAttestation)(attestationJws, this.manifest, this.revocations, expectedAudience, expectedNonce); | ||
| } | ||
| } | ||
| exports.OpenAgentTrustRegistry = OpenAgentTrustRegistry; |
| "use strict"; | ||
| // sdk/typescript/src/types/attestation.ts | ||
| Object.defineProperty(exports, "__esModule", { value: true }); |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
| for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| // sdk/typescript/src/types/index.ts | ||
| __exportStar(require("./registry"), exports); | ||
| __exportStar(require("./attestation"), exports); |
| "use strict"; | ||
| // sdk/typescript/src/types/registry.ts | ||
| Object.defineProperty(exports, "__esModule", { value: true }); |
| "use strict"; | ||
| // sdk/typescript/src/verify.ts | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.verifyAttestation = verifyAttestation; | ||
| const jose = __importStar(require("jose")); | ||
| /** | ||
| * Executes the 14-step Verification Protocol to assess an agent attestation. | ||
| * Operates purely locally in <1ms without any network calls. | ||
| */ | ||
| async function verifyAttestation(attestationJws, manifest, revocations, expectedAudience, expectedNonce) { | ||
| try { | ||
| // Step 1 & 2: Parse JWS and extract headers | ||
| const protectedHeader = jose.decodeProtectedHeader(attestationJws); | ||
| if (!protectedHeader.iss || !protectedHeader.kid || protectedHeader.alg !== 'EdDSA') { | ||
| return { valid: false, reason: 'invalid_signature' }; | ||
| } | ||
| const issuerId = protectedHeader.iss; | ||
| const kid = protectedHeader.kid; | ||
| // Fast reject if issuer or key is explicitly on the 5-min revocation list | ||
| const isKeyRevoked = revocations.revoked_keys.some(k => k.kid === kid && k.issuer_id === issuerId); | ||
| const isIssuerRevoked = revocations.revoked_issuers.some(i => i.issuer_id === issuerId); | ||
| if (isKeyRevoked) | ||
| return { valid: false, reason: 'revoked_key' }; | ||
| if (isIssuerRevoked) | ||
| return { valid: false, reason: 'revoked_issuer' }; | ||
| // Step 3: Look up issuer | ||
| const issuer = manifest.entries.find(e => e.issuer_id === issuerId); | ||
| // Step 4: Unknown Issuer | ||
| if (!issuer) | ||
| return { valid: false, reason: 'unknown_issuer' }; | ||
| // Step 5: Active check | ||
| if (issuer.status !== 'active') | ||
| return { valid: false, reason: 'revoked_issuer', issuer }; | ||
| // Step 6: Locate key | ||
| const key = issuer.public_keys.find(k => k.kid === kid); | ||
| // Step 7: Unknown Key | ||
| if (!key) | ||
| return { valid: false, reason: 'unknown_key', issuer }; | ||
| // Step 8: Revoked Key status check | ||
| if (key.status === 'revoked') | ||
| return { valid: false, reason: 'revoked_key', issuer }; | ||
| // Step 9: Deprecated logic gracefully handled (we just accept it, perhaps logging later) | ||
| // Step 10: Check key expiration against current date | ||
| const now = new Date(); | ||
| const keyExpiry = new Date(key.expires_at); | ||
| if (now > keyExpiry) | ||
| return { valid: false, reason: 'invalid_signature', issuer }; | ||
| // Step 11 & 12: Cryptographically verify the signature | ||
| try { | ||
| // Convert base64url Ed25519 key to Uint8Array for noble/ed25519 or jose | ||
| // Jose handles the EdDSA verification inherently mapping base64 keys | ||
| const jwkDecoded = { | ||
| kty: 'OKP', | ||
| crv: 'Ed25519', | ||
| x: key.public_key // Base64url encoded | ||
| }; | ||
| const importedKey = await jose.importJWK(jwkDecoded, 'EdDSA'); | ||
| // This validates the signature and standard JWT claims (exp, etc.) concurrently | ||
| const { payload } = await jose.jwtVerify(attestationJws, importedKey, { | ||
| audience: expectedAudience, | ||
| algorithms: ['EdDSA'] | ||
| }); | ||
| // The payload is strictly typed as an agnostic claim map | ||
| const claims = payload; | ||
| // Step 13: Additional manual specific checks | ||
| if (expectedNonce && claims.nonce !== expectedNonce) { | ||
| return { valid: false, reason: 'nonce_mismatch', issuer }; | ||
| } | ||
| // Must explicitly check aud since jwtVerify might be lenient depending on config | ||
| if (claims.aud !== expectedAudience) { | ||
| return { valid: false, reason: 'audience_mismatch', issuer }; | ||
| } | ||
| // Step 14: All checks passed. | ||
| return { | ||
| valid: true, | ||
| issuer, | ||
| claims | ||
| }; | ||
| } | ||
| catch (cryptoErr) { | ||
| // Includes JWT expired, invalid signature mathematically, or audience mismatch thrown by jose | ||
| if (cryptoErr instanceof jose.errors.JWTExpired) { | ||
| return { valid: false, reason: 'expired_attestation', issuer }; | ||
| } | ||
| if (cryptoErr instanceof jose.errors.JWTClaimValidationFailed && cryptoErr.claim === 'aud') { | ||
| return { valid: false, reason: 'audience_mismatch', issuer }; | ||
| } | ||
| return { valid: false, reason: 'invalid_signature', issuer }; | ||
| } | ||
| } | ||
| catch (globalErr) { | ||
| // Catches malformed JWS tokens | ||
| return { valid: false, reason: 'invalid_signature' }; | ||
| } | ||
| } |
| import { readFile } from 'fs/promises'; | ||
| import { createPrivateKey } from 'crypto'; | ||
| import { join } from 'path'; | ||
| import * as ed from '@noble/ed25519'; | ||
| interface RootKeyEntry { | ||
| kid: string; | ||
| algorithm: 'Ed25519'; | ||
| public_key: string; | ||
| status: 'active' | 'retired'; | ||
| not_before: string; | ||
| not_after: string | null; | ||
| } | ||
| interface RootKeySet { | ||
| schema_version: string; | ||
| registry_id: string; | ||
| generated_at: string; | ||
| keys: RootKeyEntry[]; | ||
| } | ||
| export interface RegistrySignature { | ||
| algorithm: 'Ed25519'; | ||
| kid: string; | ||
| value: string; | ||
| } | ||
| export function canonicalizeJson(value: unknown): string { | ||
| if (value === null) return 'null'; | ||
| switch (typeof value) { | ||
| case 'boolean': | ||
| return value ? 'true' : 'false'; | ||
| case 'number': | ||
| if (!Number.isFinite(value)) { | ||
| throw new Error('Canonical JSON does not support non-finite numbers.'); | ||
| } | ||
| return JSON.stringify(value); | ||
| case 'string': | ||
| return JSON.stringify(value); | ||
| case 'object': | ||
| if (Array.isArray(value)) { | ||
| return `[${value.map((item) => canonicalizeJson(item)).join(',')}]`; | ||
| } | ||
| return `{${Object.keys(value as Record<string, unknown>) | ||
| .sort() | ||
| .map((key) => `${JSON.stringify(key)}:${canonicalizeJson((value as Record<string, unknown>)[key])}`) | ||
| .join(',')}}`; | ||
| default: | ||
| throw new Error(`Unsupported value type for canonical JSON: ${typeof value}`); | ||
| } | ||
| } | ||
| export function createPrivateKeyFromSeed(seedBase64Url: string) { | ||
| const privateKeyBytes = Buffer.from(seedBase64Url.trim(), 'base64url'); | ||
| if (privateKeyBytes.length !== 32) { | ||
| throw new Error(`Invalid private key length (${privateKeyBytes.length} bytes). Must be 32 bytes.`); | ||
| } | ||
| const seedHex = privateKeyBytes.toString('hex'); | ||
| const pkcs8Der = Buffer.from(`302e020100300506032b657004220420${seedHex}`, 'hex'); | ||
| return createPrivateKey({ | ||
| key: pkcs8Der, | ||
| format: 'der', | ||
| type: 'pkcs8' | ||
| }); | ||
| } | ||
| export async function loadRootKeySet(registryDir: string): Promise<RootKeySet> { | ||
| const raw = await readFile(join(registryDir, 'root-keys.json'), 'utf8'); | ||
| return JSON.parse(raw) as RootKeySet; | ||
| } | ||
| export async function resolveActiveRootKey(registryDir: string, privateSeed: string): Promise<RootKeyEntry> { | ||
| const publicKey = Buffer.from(await ed.getPublicKeyAsync(Buffer.from(privateSeed.trim(), 'base64url'))).toString('base64url'); | ||
| const rootKeys = await loadRootKeySet(registryDir); | ||
| const rootKey = rootKeys.keys.find((entry) => entry.public_key === publicKey && entry.status === 'active'); | ||
| if (!rootKey) { | ||
| throw new Error('The supplied root private key does not match any active key in registry/root-keys.json.'); | ||
| } | ||
| return rootKey; | ||
| } | ||
| export async function signRegistryArtifact( | ||
| unsignedArtifact: Record<string, unknown>, | ||
| privateSeed: string, | ||
| signatureKid: string | ||
| ): Promise<RegistrySignature> { | ||
| const canonicalPayload = canonicalizeJson(unsignedArtifact); | ||
| const signatureBytes = await ed.signAsync(Buffer.from(canonicalPayload, 'utf8'), Buffer.from(privateSeed.trim(), 'base64url')); | ||
| return { | ||
| algorithm: 'Ed25519', | ||
| kid: signatureKid, | ||
| value: Buffer.from(signatureBytes).toString('base64url') | ||
| }; | ||
| } |
+34
-58
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -39,3 +6,4 @@ exports.compile = compile; | ||
| const path_1 = require("path"); | ||
| const ed = __importStar(require("@noble/ed25519")); | ||
| const path_2 = require("path"); | ||
| const registry_artifacts_1 = require("../lib/registry-artifacts"); | ||
| async function compile(options) { | ||
@@ -47,3 +15,3 @@ try { | ||
| const files = await (0, promises_1.readdir)(issuersDir); | ||
| const issuers = {}; | ||
| const entries = []; | ||
| for (const file of files) { | ||
@@ -53,35 +21,43 @@ if (file.endsWith('.json')) { | ||
| const parsed = JSON.parse(content); | ||
| issuers[parsed.issuer_id] = parsed; | ||
| entries.push(parsed); | ||
| } | ||
| } | ||
| entries.sort((a, b) => a.issuer_id.localeCompare(b.issuer_id)); | ||
| const timestamp = new Date().toISOString(); | ||
| const expires = new Date(Date.now() + 60 * 60 * 1000).toISOString(); // +1 hour | ||
| const manifestExpiry = new Date(Date.now() + 60 * 60 * 1000).toISOString(); | ||
| const revocationExpiry = new Date(Date.now() + 5 * 60 * 1000).toISOString(); | ||
| const privateKeyPath = (0, path_2.resolve)(process.cwd(), options.privateKey); | ||
| const privateSeed = (await (0, promises_1.readFile)(privateKeyPath, 'utf8')).trim(); | ||
| const rootKey = await (0, registry_artifacts_1.resolveActiveRootKey)(registryDir, privateSeed); | ||
| const unsignedManifest = { | ||
| schema_version: '1.0.0', | ||
| registry_id: 'open-trust-registry', | ||
| generated_at: timestamp, | ||
| expires_at: manifestExpiry, | ||
| entries | ||
| }; | ||
| const manifest = { | ||
| version: "1.0", | ||
| ...unsignedManifest, | ||
| signature: await (0, registry_artifacts_1.signRegistryArtifact)(unsignedManifest, privateSeed, rootKey.kid) | ||
| }; | ||
| const revocationsPath = (0, path_1.join)(registryDir, 'revocations.json'); | ||
| const currentRevocations = JSON.parse(await (0, promises_1.readFile)(revocationsPath, 'utf8')); | ||
| const unsignedRevocations = { | ||
| schema_version: currentRevocations.schema_version ?? '1.0.0', | ||
| generated_at: timestamp, | ||
| expires_at: expires, | ||
| total_issuers: Object.keys(issuers).length, | ||
| issuers: issuers, | ||
| signature: "" | ||
| expires_at: revocationExpiry, | ||
| revoked_keys: currentRevocations.revoked_keys ?? [], | ||
| revoked_issuers: currentRevocations.revoked_issuers ?? [] | ||
| }; | ||
| const payloadToSign = JSON.stringify({ | ||
| version: manifest.version, | ||
| generated_at: manifest.generated_at, | ||
| expires_at: manifest.expires_at, | ||
| total_issuers: manifest.total_issuers, | ||
| issuers: manifest.issuers | ||
| }); | ||
| const privateKeyBuffer = Buffer.from(options.privateKey, 'base64url'); | ||
| if (privateKeyBuffer.length !== 32) { | ||
| throw new Error("Invalid private key length. Must be 32 bytes (base64url encoded)."); | ||
| } | ||
| // Use async signing to avoid needing the sync sha512 configuration | ||
| const signatureBytes = await ed.signAsync(Buffer.from(payloadToSign, 'utf8'), privateKeyBuffer); | ||
| const signatureStr = Buffer.from(signatureBytes).toString('base64url'); | ||
| manifest.signature = `ed25519:${signatureStr}`; | ||
| const revocations = { | ||
| ...unsignedRevocations, | ||
| signature: await (0, registry_artifacts_1.signRegistryArtifact)(unsignedRevocations, privateSeed, rootKey.kid) | ||
| }; | ||
| const outputPath = (0, path_1.join)(registryDir, 'manifest.json'); | ||
| await (0, promises_1.writeFile)(outputPath, JSON.stringify(manifest, null, 2)); | ||
| await (0, promises_1.writeFile)(revocationsPath, JSON.stringify(revocations, null, 2)); | ||
| console.log('✅ Success!'); | ||
| console.log(`Signed Manifest saved to: ${outputPath}`); | ||
| console.log(`Total Issuers: ${manifest.total_issuers}`); | ||
| console.log(`Signed Revocations saved to: ${revocationsPath}`); | ||
| console.log(`Total Issuers: ${manifest.entries.length}`); | ||
| } | ||
@@ -88,0 +64,0 @@ catch (error) { |
@@ -30,2 +30,7 @@ "use strict"; | ||
| catch (err) { | ||
| if (err instanceof registry_1.OpenAgentTrustRegistryError) { | ||
| console.error(`\n❌ Registry state rejected: ${err.code}`); | ||
| console.error(err.message); | ||
| process.exit(1); | ||
| } | ||
| console.error(`\n❌ Verification engine failure: ${err.message}`); | ||
@@ -32,0 +37,0 @@ process.exit(1); |
+2
-2
@@ -51,4 +51,4 @@ #!/usr/bin/env node | ||
| .command('compile') | ||
| .description('Compiles and signs the final manifest.json from the registry folder') | ||
| .requiredOption('-p, --private-key <key>', 'Ed25519 private key to sign the manifest') | ||
| .description('Compiles and signs manifest.json and revocations.json from the registry folder') | ||
| .requiredOption('-p, --private-key <path>', 'Path to the Ed25519 root private key seed file') | ||
| .action(compile_1.compile); | ||
@@ -55,0 +55,0 @@ program |
+2
-2
| { | ||
| "name": "@open-agent-trust/cli", | ||
| "version": "1.0.7", | ||
| "version": "1.0.8", | ||
| "description": "CLI utilities for the Open Agent Trust Registry", | ||
@@ -8,3 +8,3 @@ "license": "MIT", | ||
| "type": "git", | ||
| "url": "https://github.com/open-agent-trust/open-agent-trust-registry.git" | ||
| "url": "https://github.com/FransDevelopment/open-agent-trust-registry.git" | ||
| }, | ||
@@ -11,0 +11,0 @@ "author": "Open Agent Trust Registry Contributors", |
+38
-30
| import { readdir, readFile, writeFile } from 'fs/promises'; | ||
| import { join } from 'path'; | ||
| import * as ed from '@noble/ed25519'; | ||
| import { resolve } from 'path'; | ||
| import { resolveActiveRootKey, signRegistryArtifact } from '../lib/registry-artifacts'; | ||
@@ -12,5 +13,4 @@ export async function compile(options: { privateKey: string }) { | ||
| const files = await readdir(issuersDir); | ||
| const entries: Record<string, any>[] = []; | ||
| const issuers: Record<string, any> = {}; | ||
| for (const file of files) { | ||
@@ -20,43 +20,51 @@ if (file.endsWith('.json')) { | ||
| const parsed = JSON.parse(content); | ||
| issuers[parsed.issuer_id] = parsed; | ||
| entries.push(parsed); | ||
| } | ||
| } | ||
| entries.sort((a, b) => a.issuer_id.localeCompare(b.issuer_id)); | ||
| const timestamp = new Date().toISOString(); | ||
| const expires = new Date(Date.now() + 60 * 60 * 1000).toISOString(); // +1 hour | ||
| const manifest = { | ||
| version: "1.0", | ||
| const manifestExpiry = new Date(Date.now() + 60 * 60 * 1000).toISOString(); | ||
| const revocationExpiry = new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString(); | ||
| const privateKeyPath = resolve(process.cwd(), options.privateKey); | ||
| const privateSeed = (await readFile(privateKeyPath, 'utf8')).trim(); | ||
| const rootKey = await resolveActiveRootKey(registryDir, privateSeed); | ||
| const unsignedManifest = { | ||
| schema_version: '1.0.0', | ||
| registry_id: 'open-trust-registry', | ||
| generated_at: timestamp, | ||
| expires_at: expires, | ||
| total_issuers: Object.keys(issuers).length, | ||
| issuers: issuers, | ||
| signature: "" | ||
| expires_at: manifestExpiry, | ||
| entries | ||
| }; | ||
| const payloadToSign = JSON.stringify({ | ||
| version: manifest.version, | ||
| generated_at: manifest.generated_at, | ||
| expires_at: manifest.expires_at, | ||
| total_issuers: manifest.total_issuers, | ||
| issuers: manifest.issuers | ||
| }); | ||
| const manifest = { | ||
| ...unsignedManifest, | ||
| signature: await signRegistryArtifact(unsignedManifest, privateSeed, rootKey.kid) | ||
| }; | ||
| const privateKeyBuffer = Buffer.from(options.privateKey, 'base64url'); | ||
| if (privateKeyBuffer.length !== 32) { | ||
| throw new Error("Invalid private key length. Must be 32 bytes (base64url encoded)."); | ||
| } | ||
| const revocationsPath = join(registryDir, 'revocations.json'); | ||
| const currentRevocations = JSON.parse(await readFile(revocationsPath, 'utf8')); | ||
| const unsignedRevocations = { | ||
| schema_version: currentRevocations.schema_version ?? '1.0.0', | ||
| generated_at: timestamp, | ||
| expires_at: revocationExpiry, | ||
| revoked_keys: currentRevocations.revoked_keys ?? [], | ||
| revoked_issuers: currentRevocations.revoked_issuers ?? [] | ||
| }; | ||
| const revocations = { | ||
| ...unsignedRevocations, | ||
| signature: await signRegistryArtifact(unsignedRevocations, privateSeed, rootKey.kid) | ||
| }; | ||
| // Use async signing to avoid needing the sync sha512 configuration | ||
| const signatureBytes = await ed.signAsync(Buffer.from(payloadToSign, 'utf8'), privateKeyBuffer); | ||
| const signatureStr = Buffer.from(signatureBytes).toString('base64url'); | ||
| manifest.signature = `ed25519:${signatureStr}`; | ||
| const outputPath = join(registryDir, 'manifest.json'); | ||
| await writeFile(outputPath, JSON.stringify(manifest, null, 2)); | ||
| await writeFile(revocationsPath, JSON.stringify(revocations, null, 2)); | ||
| console.log('✅ Success!'); | ||
| console.log(`Signed Manifest saved to: ${outputPath}`); | ||
| console.log(`Total Issuers: ${manifest.total_issuers}`); | ||
| console.log(`Signed Revocations saved to: ${revocationsPath}`); | ||
| console.log(`Total Issuers: ${manifest.entries.length}`); | ||
@@ -63,0 +71,0 @@ } catch (error: any) { |
@@ -1,2 +0,2 @@ | ||
| import { OpenAgentTrustRegistry } from '@open-agent-trust/registry'; | ||
| import { OpenAgentTrustRegistry, OpenAgentTrustRegistryError } from '../../../sdk/typescript/src'; | ||
@@ -29,2 +29,7 @@ export const verify = async (attestation: string, options: { audience: string; mirror: string }) => { | ||
| } catch (err: any) { | ||
| if (err instanceof OpenAgentTrustRegistryError) { | ||
| console.error(`\n❌ Registry state rejected: ${err.code}`); | ||
| console.error(err.message); | ||
| process.exit(1); | ||
| } | ||
| console.error(`\n❌ Verification engine failure: ${err.message}`); | ||
@@ -31,0 +36,0 @@ process.exit(1); |
+2
-2
@@ -56,4 +56,4 @@ #!/usr/bin/env node | ||
| .command('compile') | ||
| .description('Compiles and signs the final manifest.json from the registry folder') | ||
| .requiredOption('-p, --private-key <key>', 'Ed25519 private key to sign the manifest') | ||
| .description('Compiles and signs manifest.json and revocations.json from the registry folder') | ||
| .requiredOption('-p, --private-key <path>', 'Path to the Ed25519 root private key seed file') | ||
| .action(compile); | ||
@@ -60,0 +60,0 @@ |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.compile = compile; | ||
| const promises_1 = require("fs/promises"); | ||
| const path_1 = require("path"); | ||
| const ed = __importStar(require("@noble/ed25519")); | ||
| async function compile(options) { | ||
| try { | ||
| console.log('Compiling registry manifest...'); | ||
| const registryDir = (0, path_1.join)(process.cwd(), '../registry'); | ||
| const issuersDir = (0, path_1.join)(registryDir, 'issuers'); | ||
| const files = await (0, promises_1.readdir)(issuersDir); | ||
| const issuers = {}; | ||
| for (const file of files) { | ||
| if (file.endsWith('.json')) { | ||
| const content = await (0, promises_1.readFile)((0, path_1.join)(issuersDir, file), 'utf8'); | ||
| const parsed = JSON.parse(content); | ||
| issuers[parsed.issuer_id] = parsed; | ||
| } | ||
| } | ||
| const timestamp = new Date().toISOString(); | ||
| const expires = new Date(Date.now() + 60 * 60 * 1000).toISOString(); // +1 hour | ||
| const manifest = { | ||
| version: "1.0", | ||
| generated_at: timestamp, | ||
| expires_at: expires, | ||
| total_issuers: Object.keys(issuers).length, | ||
| issuers: issuers, | ||
| signature: "" | ||
| }; | ||
| const payloadToSign = JSON.stringify({ | ||
| version: manifest.version, | ||
| generated_at: manifest.generated_at, | ||
| expires_at: manifest.expires_at, | ||
| total_issuers: manifest.total_issuers, | ||
| issuers: manifest.issuers | ||
| }); | ||
| const privateKeyBuffer = Buffer.from(options.privateKey, 'base64url'); | ||
| if (privateKeyBuffer.length !== 32) { | ||
| throw new Error("Invalid private key length. Must be 32 bytes (base64url encoded)."); | ||
| } | ||
| // Use async signing to avoid needing the sync sha512 configuration | ||
| const signatureBytes = await ed.signAsync(Buffer.from(payloadToSign, 'utf8'), privateKeyBuffer); | ||
| const signatureStr = Buffer.from(signatureBytes).toString('base64url'); | ||
| manifest.signature = `ed25519:${signatureStr}`; | ||
| const outputPath = (0, path_1.join)(registryDir, 'manifest.json'); | ||
| await (0, promises_1.writeFile)(outputPath, JSON.stringify(manifest, null, 2)); | ||
| console.log('✅ Success!'); | ||
| console.log(`Signed Manifest saved to: ${outputPath}`); | ||
| console.log(`Total Issuers: ${manifest.total_issuers}`); | ||
| } | ||
| catch (error) { | ||
| console.error('❌ Failed to compile registry:', error.message); | ||
| process.exit(1); | ||
| } | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.issue = void 0; | ||
| const jose_1 = require("jose"); | ||
| const promises_1 = require("fs/promises"); | ||
| const path_1 = require("path"); | ||
| const crypto_1 = require("crypto"); | ||
| const issue = async (options) => { | ||
| try { | ||
| console.log('[1/3] Loading Ed25519 private key...'); | ||
| const keyPath = (0, path_1.resolve)(process.cwd(), options.privateKey); | ||
| const privateKeyB64 = await (0, promises_1.readFile)(keyPath, 'utf-8'); | ||
| const privateKeyBytes = Buffer.from(privateKeyB64.trim(), 'base64url'); | ||
| const seedHex = privateKeyBytes.subarray(0, 32).toString('hex'); | ||
| console.log('[2/3] Preparing agent-attestation+jwt payload...'); | ||
| const now = Math.floor(Date.now() / 1000); | ||
| const payload = { | ||
| sub: 'agent-instance-' + crypto.randomUUID().slice(0, 8), | ||
| aud: options.audience, | ||
| iat: now, | ||
| nonce: crypto.randomUUID(), | ||
| scope: options.scope.split(',').map(s => s.trim()), | ||
| constraints: { | ||
| time_bound: true | ||
| }, | ||
| user_pseudonym: 'pairwise-' + crypto.randomUUID().slice(0, 8), | ||
| runtime_version: '1.0.0' | ||
| }; | ||
| console.log('[3/3] Signing EdDSA attestation token...'); | ||
| // Wrap the raw 32-byte Ed25519 seed in a PKCS#8 DER ASN.1 structure | ||
| // 302e020100300506032b657004220420 is the standard ASN.1 prefix for Ed25519 private keys | ||
| const pkcs8Der = Buffer.from('302e020100300506032b657004220420' + seedHex, 'hex'); | ||
| const privateKeyObj = (0, crypto_1.createPrivateKey)({ | ||
| key: pkcs8Der, | ||
| format: 'der', | ||
| type: 'pkcs8' | ||
| }); | ||
| const jwt = await new jose_1.SignJWT(payload) | ||
| .setProtectedHeader({ | ||
| alg: 'EdDSA', | ||
| kid: options.kid, | ||
| iss: options.issuerId, | ||
| typ: 'agent-attestation+jwt' | ||
| }) | ||
| .setExpirationTime(now + options.expiresIn) | ||
| .sign(privateKeyObj); | ||
| console.log('\n✓ Test Attestation Generated Successfully:'); | ||
| console.log('--------------------------------------------------'); | ||
| console.log(jwt); | ||
| console.log('--------------------------------------------------'); | ||
| console.log(`\nTo test verification, copy the string above and run:\nagent-trust verify <TOKEN> --audience ${options.audience}`); | ||
| } | ||
| catch (err) { | ||
| if (err instanceof Error) { | ||
| console.error('\n❌ Failed to generate test attestation:', err.message); | ||
| } | ||
| else { | ||
| console.error('\n❌ An unexpected error occurred while generating the test attestation.'); | ||
| } | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| exports.issue = issue; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.keygen = void 0; | ||
| const ed = __importStar(require("@noble/ed25519")); | ||
| const crypto = __importStar(require("crypto")); | ||
| const fs = __importStar(require("fs")); | ||
| const path = __importStar(require("path")); | ||
| // Polyfill for noble in raw node environments | ||
| if (!globalThis.crypto) { | ||
| globalThis.crypto = crypto.webcrypto; | ||
| } | ||
| const keygen = async (options) => { | ||
| try { | ||
| console.log(`\nGenerating Ed25519 keypair for issuer '${options.issuerId}'...\n`); | ||
| const privateKeyRaw = ed.utils.randomSecretKey(); | ||
| const publicKeyRaw = await ed.getPublicKeyAsync(privateKeyRaw); | ||
| const privateKeyBase64Url = Buffer.from(privateKeyRaw).toString('base64url'); | ||
| const publicKeyBase64Url = Buffer.from(publicKeyRaw).toString('base64url'); | ||
| // Create the kid (Key ID) using a stable date prefix | ||
| const dateStr = new Date().toISOString().substring(0, 7); // e.g., 2026-03 | ||
| const kid = `${options.issuerId}-${dateStr}`; | ||
| // Industry standard .pem extension with 'private' in filename for clarity | ||
| const privateKeyPath = path.join(options.outDir, `${options.issuerId}.private.pem`); | ||
| fs.writeFileSync(privateKeyPath, privateKeyBase64Url, { mode: 0o600 }); | ||
| console.log(`✅ Keypair generated successfully!\n`); | ||
| console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`); | ||
| console.log(` Private Key: ${privateKeyPath}`); | ||
| console.log(` KID: ${kid}`); | ||
| console.log(` Algorithm: Ed25519`); | ||
| console.log(` Public Key: ${publicKeyBase64Url}`); | ||
| console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`); | ||
| console.log(`⚠️ Keep your private key secret. Never commit it to a repo.\n`); | ||
| console.log(` To view it: cat ${privateKeyPath}`); | ||
| console.log(` To secure it: chmod 600 ${privateKeyPath}\n`); | ||
| console.log(` Note: Do not double-click the .pem file. On macOS, this opens`); | ||
| console.log(` Keychain Access. Always use 'cat' or a text editor from the terminal.\n`); | ||
| console.log(`Next steps:\n`); | ||
| console.log(` 1. Add your public key to your agent.json for Tier 3 identity:\n`); | ||
| console.log(` "identity": {`); | ||
| console.log(` "did": "did:web:yourdomain.com",`); | ||
| console.log(` "public_key": "${publicKeyBase64Url}"`); | ||
| console.log(` }\n`); | ||
| console.log(` 2. Host a DID document at https://yourdomain.com/.well-known/did.json`); | ||
| console.log(` (See docs: https://agentinternetruntime.com/spec/agent-json#becoming-tier-3)\n`); | ||
| console.log(` 3. To register as a trusted runtime issuer in the Trust Registry:`); | ||
| console.log(` npx @open-agent-trust/cli register --issuer-id ${options.issuerId} \\`); | ||
| console.log(` --display-name "Your Display Name" --website https://yourdomain.com \\`); | ||
| console.log(` --contact security@yourdomain.com --public-key ${publicKeyBase64Url}\n`); | ||
| } | ||
| catch (err) { | ||
| console.error('Failed to generate keypair:', err); | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| exports.keygen = keygen; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.prove = void 0; | ||
| const promises_1 = require("fs/promises"); | ||
| const path_1 = require("path"); | ||
| const ed = __importStar(require("@noble/ed25519")); | ||
| const PROOF_VERSION = 'oatr-proof-v1'; | ||
| const ISSUER_ID_PATTERN = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/; | ||
| const prove = async (options) => { | ||
| try { | ||
| // Validate issuer_id format | ||
| if (!ISSUER_ID_PATTERN.test(options.issuerId)) { | ||
| throw new Error(`Invalid issuer_id "${options.issuerId}". Must be lowercase alphanumeric and hyphens only, ` + | ||
| `no leading/trailing hyphens. Example: my-runtime`); | ||
| } | ||
| console.log('[1/3] Loading Ed25519 private key...'); | ||
| const keyPath = (0, path_1.resolve)(process.cwd(), options.privateKey); | ||
| const privateKeyB64 = await (0, promises_1.readFile)(keyPath, 'utf-8'); | ||
| const privateKeyBuffer = Buffer.from(privateKeyB64.trim(), 'base64url'); | ||
| if (privateKeyBuffer.length !== 32) { | ||
| throw new Error(`Invalid private key length (${privateKeyBuffer.length} bytes). ` + | ||
| `Must be 32 bytes (base64url encoded). Ensure this is an Ed25519 seed from 'agent-trust keygen'.`); | ||
| } | ||
| console.log('[2/3] Signing proof-of-key-ownership...'); | ||
| const canonicalMessage = `${PROOF_VERSION}:${options.issuerId}`; | ||
| const messageBytes = Buffer.from(canonicalMessage, 'utf8'); | ||
| const signatureBytes = await ed.signAsync(messageBytes, privateKeyBuffer); | ||
| const signature = Buffer.from(signatureBytes).toString('base64url'); | ||
| const proofContent = [ | ||
| '-----BEGIN OATR KEY OWNERSHIP PROOF-----', | ||
| `Canonical-Message: ${canonicalMessage}`, | ||
| `Signature: ${signature}`, | ||
| '-----END OATR KEY OWNERSHIP PROOF-----', | ||
| '' // trailing newline for POSIX compliance | ||
| ].join('\n'); | ||
| console.log('[3/3] Writing proof file...'); | ||
| const outPath = options.outFile | ||
| ? (0, path_1.resolve)(process.cwd(), options.outFile) | ||
| : (0, path_1.join)(process.cwd(), 'registry', 'proofs', `${options.issuerId}.proof`); | ||
| await (0, promises_1.mkdir)((0, path_1.dirname)(outPath), { recursive: true }); | ||
| await (0, promises_1.writeFile)(outPath, proofContent); | ||
| console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`); | ||
| console.log(` ✅ Proof of Key Ownership Generated`); | ||
| console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`); | ||
| console.log(` Issuer ID: ${options.issuerId}`); | ||
| console.log(` Proof file: ${outPath}`); | ||
| console.log(` Format: ${PROOF_VERSION}`); | ||
| console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`); | ||
| console.log(`Next steps:\n`); | ||
| console.log(` 1. Ensure your issuer JSON is at registry/issuers/${options.issuerId}.json`); | ||
| console.log(` 2. Ensure your domain verification is live at:`); | ||
| console.log(` https://yourdomain.com/.well-known/agent-trust.json\n`); | ||
| console.log(` 3. Submit a Pull Request with both files:`); | ||
| console.log(` - registry/issuers/${options.issuerId}.json`); | ||
| console.log(` - registry/proofs/${options.issuerId}.proof\n`); | ||
| console.log(` The CI pipeline will verify your proof, check your domain,`); | ||
| console.log(` and auto-merge if all checks pass.\n`); | ||
| } | ||
| catch (error) { | ||
| console.error('❌ Failed to generate proof:', error.message); | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| exports.prove = prove; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.register = void 0; | ||
| const fs = __importStar(require("fs")); | ||
| const path = __importStar(require("path")); | ||
| const register = async (options) => { | ||
| const nowISO = new Date().toISOString(); | ||
| // Hardcoded to standard generic capabilities for Phase 1 scaffolding. | ||
| // Real users would edit this draft JSON file. | ||
| let entry = { | ||
| "issuer_id": options.issuerId, | ||
| "display_name": options.displayName, | ||
| "website": options.website, | ||
| "security_contact": options.contact, | ||
| "status": "active", | ||
| "added_at": nowISO, | ||
| "last_verified": nowISO, | ||
| "public_keys": [ | ||
| { | ||
| "kid": `${options.issuerId}-${nowISO.substring(0, 7)}`, | ||
| "algorithm": "Ed25519", | ||
| "public_key": options.publicKey, | ||
| "status": "active", | ||
| "issued_at": nowISO, | ||
| "expires_at": new Date(Date.now() + 31536000000).toISOString(), // +1 year | ||
| "deprecated_at": null, | ||
| "revoked_at": null | ||
| } | ||
| ], | ||
| "capabilities": { | ||
| "supervision_model": "tiered", | ||
| "audit_logging": true, | ||
| "immutable_audit": false, | ||
| "attestation_format": "jwt", | ||
| "max_attestation_ttl_seconds": 3600, | ||
| "capabilities_verified": false | ||
| } | ||
| }; | ||
| const outPath = options.outFile || path.join(process.cwd(), `${options.issuerId}.json`); | ||
| fs.writeFileSync(outPath, JSON.stringify(entry, null, 2)); | ||
| console.log(`✅ Draft Issuer Entry generated at: ${outPath}`); | ||
| console.log(`\nPlease review the \"capabilities\" block to ensure it matches your runtime's exact profile.`); | ||
| console.log(`When ready, submit this file as a Pull Request to 'registry/issuers/' in the open source repository.`); | ||
| }; | ||
| exports.register = register; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.verify = void 0; | ||
| const registry_1 = require("@open-agent-trust/registry"); | ||
| const verify = async (attestation, options) => { | ||
| try { | ||
| console.log(`[1/2] Fetching registry manifest from mirror (${options.mirror})...`); | ||
| const registry = await registry_1.OpenAgentTrustRegistry.load(options.mirror); | ||
| console.log(`[2/2] Attempting 14-step verification against audience bounds (${options.audience})...`); | ||
| const result = await registry.verifyToken(attestation, options.audience); | ||
| if (result.valid) { | ||
| console.log(`\n✅ Valid Attestation`); | ||
| console.log(`Issuer: ${result.issuer?.display_name} (${result.issuer?.issuer_id})`); | ||
| console.log(`Token Subject: ${result.claims?.sub}`); | ||
| console.log(`Authorized As: ${result.claims?.user_pseudonym}`); | ||
| console.log(`Expires: ${new Date(result.claims?.exp * 1000).toISOString()}`); | ||
| console.log(`Constraints: `, result.claims?.constraints); | ||
| process.exit(0); | ||
| } | ||
| else { | ||
| console.error(`\n❌ Token mathematically rejected by the Registry.`); | ||
| console.error(`Reason: ${result.reason}`); | ||
| if (result.issuer) { | ||
| console.error(`Identified Issuer: ${result.issuer.display_name}`); | ||
| } | ||
| process.exit(1); | ||
| } | ||
| } | ||
| catch (err) { | ||
| console.error(`\n❌ Verification engine failure: ${err.message}`); | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| exports.verify = verify; |
| #!/usr/bin/env node | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| const commander_1 = require("commander"); | ||
| const keygen_1 = require("./commands/keygen"); | ||
| const register_1 = require("./commands/register"); | ||
| const verify_1 = require("./commands/verify"); | ||
| const issue_1 = require("./commands/issue"); | ||
| const compile_1 = require("./commands/compile"); | ||
| const prove_1 = require("./commands/prove"); | ||
| const program = new commander_1.Command(); | ||
| program | ||
| .name('agent-trust') | ||
| .description('Open Agent Trust Registry CLI Utilities') | ||
| .version('1.0.0'); | ||
| program | ||
| .command('keygen') | ||
| .description('Generate a new Ed25519 keypair for your runtime') | ||
| .requiredOption('-i, --issuer-id <string>', 'Your globally unique issuer identifier') | ||
| .option('-o, --out-dir <path>', 'Directory to save the private key', './') | ||
| .action(keygen_1.keygen); | ||
| program | ||
| .command('register') | ||
| .description('Generate a compliant issuer_entry JSON file') | ||
| .requiredOption('-i, --issuer-id <string>', 'Your globally unique issuer identifier') | ||
| .requiredOption('-n, --display-name <string>', 'Human-readable name of the runtime') | ||
| .requiredOption('-w, --website <url>', 'Public resolving website of the operator') | ||
| .requiredOption('-c, --contact <email>', 'Security responsible contact email') | ||
| .requiredOption('-k, --public-key <base64url>', 'The base64url Ed25519 public key generated from keygen') | ||
| .option('-o, --out-file <path>', 'Output path for the generated JSON') | ||
| .action(register_1.register); | ||
| program | ||
| .command('verify') | ||
| .description('Verify an agent attestation JWS against the registry') | ||
| .argument('<attestation>', 'The raw JWT/JWS token string') | ||
| .requiredOption('-a, --audience <url>', 'The origin URL of the service expecting the token') | ||
| .option('-m, --mirror <url>', 'Custom registry mirror URL', 'http://localhost:3000') | ||
| .action(verify_1.verify); | ||
| program | ||
| .command('issue') | ||
| .description('Generate a signed test agent-attestation+jwt token (useful for local integration testing)') | ||
| .requiredOption('-i, --issuer-id <string>', 'Your globally unique issuer identifier') | ||
| .requiredOption('-k, --kid <string>', 'The key ID (kid) of the key used to sign') | ||
| .requiredOption('-p, --private-key <path>', 'Path to your .key file generated by keygen') | ||
| .requiredOption('-a, --audience <url>', 'The origin URL of the service expecting the token') | ||
| .option('-s, --scope <string>', 'Comma-separated list of scopes (e.g., read:email,send:email)', 'read:data') | ||
| .option('-e, --expires-in <seconds>', 'Expiration time in seconds', (val) => parseInt(val, 10), 3600) | ||
| .action(issue_1.issue); | ||
| program | ||
| .command('compile') | ||
| .description('Compiles and signs the final manifest.json from the registry folder') | ||
| .requiredOption('-p, --private-key <key>', 'Ed25519 private key to sign the manifest') | ||
| .action(compile_1.compile); | ||
| program | ||
| .command('prove') | ||
| .description('Generate a cryptographic proof-of-key-ownership for registry registration') | ||
| .requiredOption('-i, --issuer-id <string>', 'Your globally unique issuer identifier') | ||
| .requiredOption('-p, --private-key <path>', 'Path to your .private.pem key file') | ||
| .option('-o, --out-file <path>', 'Output path for the proof file') | ||
| .action(prove_1.prove); | ||
| program.parse(process.argv); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Network access
Supply chain riskThis module accesses the network.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
100551
43.85%34
41.67%2078
48.22%20
25%3
Infinity%