@a5c-ai/tasks-mux 5.0.1-staging.04ca6ab00d21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/dist/auth/forge-interface.d.ts +67 -0
- package/dist/auth/forge-interface.d.ts.map +1 -0
- package/dist/auth/forge-interface.js +69 -0
- package/dist/auth/github-app.d.ts +64 -0
- package/dist/auth/github-app.d.ts.map +1 -0
- package/dist/auth/github-app.js +141 -0
- package/dist/auth/github-oauth.d.ts +27 -0
- package/dist/auth/github-oauth.d.ts.map +1 -0
- package/dist/auth/github-oauth.js +89 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +14 -0
- package/dist/auth/jwt.d.ts +24 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/jwt.js +43 -0
- package/dist/auth/middleware.d.ts +22 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +36 -0
- package/dist/auth/ssh-keys.d.ts +21 -0
- package/dist/auth/ssh-keys.d.ts.map +1 -0
- package/dist/auth/ssh-keys.js +59 -0
- package/dist/auth/types.d.ts +165 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +53 -0
- package/dist/backend.d.ts +117 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +15 -0
- package/dist/backends/git-native.d.ts +51 -0
- package/dist/backends/git-native.d.ts.map +1 -0
- package/dist/backends/git-native.js +324 -0
- package/dist/backends/github-issues.d.ts +77 -0
- package/dist/backends/github-issues.d.ts.map +1 -0
- package/dist/backends/github-issues.js +796 -0
- package/dist/backends/index.d.ts +48 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/index.js +139 -0
- package/dist/backends/server.d.ts +41 -0
- package/dist/backends/server.d.ts.map +1 -0
- package/dist/backends/server.js +298 -0
- package/dist/cli/auth-store.d.ts +49 -0
- package/dist/cli/auth-store.d.ts.map +1 -0
- package/dist/cli/auth-store.js +150 -0
- package/dist/cli/client-config.d.ts +10 -0
- package/dist/cli/client-config.d.ts.map +1 -0
- package/dist/cli/client-config.js +87 -0
- package/dist/cli/commands/ask.d.ts +3 -0
- package/dist/cli/commands/ask.d.ts.map +1 -0
- package/dist/cli/commands/ask.js +171 -0
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +510 -0
- package/dist/cli/commands/breakpoints.d.ts +3 -0
- package/dist/cli/commands/breakpoints.d.ts.map +1 -0
- package/dist/cli/commands/breakpoints.js +152 -0
- package/dist/cli/commands/responder-loop.d.ts +3 -0
- package/dist/cli/commands/responder-loop.d.ts.map +1 -0
- package/dist/cli/commands/responder-loop.js +78 -0
- package/dist/cli/commands/responders.d.ts +3 -0
- package/dist/cli/commands/responders.d.ts.map +1 -0
- package/dist/cli/commands/responders.js +74 -0
- package/dist/cli/commands/server.d.ts +3 -0
- package/dist/cli/commands/server.d.ts.map +1 -0
- package/dist/cli/commands/server.js +34 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/output.d.ts +26 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +143 -0
- package/dist/cli/program.d.ts +6 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +32 -0
- package/dist/client/answer-poller.d.ts +52 -0
- package/dist/client/answer-poller.d.ts.map +1 -0
- package/dist/client/answer-poller.js +199 -0
- package/dist/client/auth-client.d.ts +200 -0
- package/dist/client/auth-client.d.ts.map +1 -0
- package/dist/client/auth-client.js +309 -0
- package/dist/client/breakpoint-router.d.ts +45 -0
- package/dist/client/breakpoint-router.d.ts.map +1 -0
- package/dist/client/breakpoint-router.js +45 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +16 -0
- package/dist/client/profile-validator.d.ts +34 -0
- package/dist/client/profile-validator.d.ts.map +1 -0
- package/dist/client/profile-validator.js +89 -0
- package/dist/client/responder-client.d.ts +39 -0
- package/dist/client/responder-client.d.ts.map +1 -0
- package/dist/client/responder-client.js +72 -0
- package/dist/client/responder-matcher.d.ts +49 -0
- package/dist/client/responder-matcher.d.ts.map +1 -0
- package/dist/client/responder-matcher.js +226 -0
- package/dist/client/server-client.d.ts +124 -0
- package/dist/client/server-client.d.ts.map +1 -0
- package/dist/client/server-client.js +266 -0
- package/dist/client/timeout-manager.d.ts +47 -0
- package/dist/client/timeout-manager.d.ts.map +1 -0
- package/dist/client/timeout-manager.js +77 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +93 -0
- package/dist/harness/index.d.ts +4 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +2 -0
- package/dist/harness/interaction-provider.d.ts +71 -0
- package/dist/harness/interaction-provider.d.ts.map +1 -0
- package/dist/harness/interaction-provider.js +124 -0
- package/dist/harness/routing-rules.d.ts +7 -0
- package/dist/harness/routing-rules.d.ts.map +1 -0
- package/dist/harness/routing-rules.js +37 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/mcp/backend-resolver.d.ts +43 -0
- package/dist/mcp/backend-resolver.d.ts.map +1 -0
- package/dist/mcp/backend-resolver.js +111 -0
- package/dist/mcp/http-transport.d.ts +37 -0
- package/dist/mcp/http-transport.d.ts.map +1 -0
- package/dist/mcp/http-transport.js +103 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/server.d.ts +20 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +121 -0
- package/dist/mcp/tools/answer-breakpoint.d.ts +32 -0
- package/dist/mcp/tools/answer-breakpoint.d.ts.map +1 -0
- package/dist/mcp/tools/answer-breakpoint.js +45 -0
- package/dist/mcp/tools/ask-breakpoint.d.ts +58 -0
- package/dist/mcp/tools/ask-breakpoint.d.ts.map +1 -0
- package/dist/mcp/tools/ask-breakpoint.js +78 -0
- package/dist/mcp/tools/check-status.d.ts +16 -0
- package/dist/mcp/tools/check-status.d.ts.map +1 -0
- package/dist/mcp/tools/check-status.js +18 -0
- package/dist/mcp/tools/claim-breakpoint.d.ts +18 -0
- package/dist/mcp/tools/claim-breakpoint.d.ts.map +1 -0
- package/dist/mcp/tools/claim-breakpoint.js +28 -0
- package/dist/mcp/tools/list-breakpoints.d.ts +16 -0
- package/dist/mcp/tools/list-breakpoints.d.ts.map +1 -0
- package/dist/mcp/tools/list-breakpoints.js +14 -0
- package/dist/mcp/tools/list-responders.d.ts +18 -0
- package/dist/mcp/tools/list-responders.d.ts.map +1 -0
- package/dist/mcp/tools/list-responders.js +37 -0
- package/dist/mcp/tools/poll-breakpoints.d.ts +18 -0
- package/dist/mcp/tools/poll-breakpoints.d.ts.map +1 -0
- package/dist/mcp/tools/poll-breakpoints.js +36 -0
- package/dist/mcp/tools/verify-answer.d.ts +16 -0
- package/dist/mcp/tools/verify-answer.d.ts.map +1 -0
- package/dist/mcp/tools/verify-answer.js +38 -0
- package/dist/proven/index.d.ts +5 -0
- package/dist/proven/index.d.ts.map +1 -0
- package/dist/proven/index.js +3 -0
- package/dist/proven/keys.d.ts +33 -0
- package/dist/proven/keys.d.ts.map +1 -0
- package/dist/proven/keys.js +117 -0
- package/dist/proven/sign.d.ts +16 -0
- package/dist/proven/sign.d.ts.map +1 -0
- package/dist/proven/sign.js +60 -0
- package/dist/proven/types.d.ts +26 -0
- package/dist/proven/types.d.ts.map +1 -0
- package/dist/proven/types.js +5 -0
- package/dist/proven/verify.d.ts +6 -0
- package/dist/proven/verify.d.ts.map +1 -0
- package/dist/proven/verify.js +58 -0
- package/dist/types.d.ts +4034 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +244 -0
- package/package.json +95 -0
- package/responder/README.md +42 -0
- package/responder/backend-responder.json +9 -0
- package/responder/devops-responder.json +9 -0
- package/responder/frontend-responder.json +9 -0
- package/responder/schema.json +52 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { selectBreakpointAnswer, supportsProvenAnswers, unsupportedBackendFeatureMessage, } from "../../backend.js";
|
|
3
|
+
import { isProvenBreakpointAnswer } from "../../types.js";
|
|
4
|
+
import { verifyAnswer } from "../../proven/verify.js";
|
|
5
|
+
// ── Tool Description ────────────────────────────────────────────────────
|
|
6
|
+
export const verifyBreakpointAnswerDescription = "Verify the cryptographic signature of a breakpoint answer against " +
|
|
7
|
+
"trusted public keys. Returns verification status and signer identity.";
|
|
8
|
+
// ── Tool Param Schema ───────────────────────────────────────────────────
|
|
9
|
+
export const verifyBreakpointAnswerParams = {
|
|
10
|
+
breakpointId: z.string().describe("The ID of the breakpoint whose answer to verify."),
|
|
11
|
+
backend: z.string().optional(),
|
|
12
|
+
breakpointsDir: z.string().optional(),
|
|
13
|
+
};
|
|
14
|
+
// ── Handler ─────────────────────────────────────────────────────────────
|
|
15
|
+
export async function handleVerifyBreakpointAnswer(params, backend) {
|
|
16
|
+
if (!params.breakpointId || params.breakpointId.length === 0) {
|
|
17
|
+
throw new Error("breakpointId is required and must be non-empty");
|
|
18
|
+
}
|
|
19
|
+
const breakpoint = await backend.getBreakpoint(params.breakpointId);
|
|
20
|
+
if (!breakpoint.answers || breakpoint.answers.length === 0) {
|
|
21
|
+
throw new Error("No answers found for this breakpoint");
|
|
22
|
+
}
|
|
23
|
+
const answer = selectBreakpointAnswer(breakpoint);
|
|
24
|
+
if (!answer) {
|
|
25
|
+
if (breakpoint.selectedAnswer) {
|
|
26
|
+
throw new Error(`Selected answer ${breakpoint.selectedAnswer} was not found on breakpoint ${breakpoint.id}`);
|
|
27
|
+
}
|
|
28
|
+
throw new Error("No answers found for this breakpoint");
|
|
29
|
+
}
|
|
30
|
+
if (!isProvenBreakpointAnswer(answer)) {
|
|
31
|
+
if (!supportsProvenAnswers(backend.name)) {
|
|
32
|
+
throw new Error(unsupportedBackendFeatureMessage(backend.name, "signed answers"));
|
|
33
|
+
}
|
|
34
|
+
throw new Error("Answer is not signed/proven -- no cryptographic signature found");
|
|
35
|
+
}
|
|
36
|
+
const result = await verifyAnswer(answer, params.breakpointsDir);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { generateKeyPair, saveTrustedPublicKey, savePrivateKey, loadTrustedPublicKeys, loadPrivateKey, rotateKey, } from "./keys.js";
|
|
2
|
+
export type { KeyPairMetadata, PublicKeyRecord, PrivateKeyRecord, } from "./keys.js";
|
|
3
|
+
export { signAnswer, signAnswerWithKeyRecord } from "./sign.js";
|
|
4
|
+
export { verifyAnswer } from "./verify.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proven/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,SAAS,GACV,MAAM,WAAW,CAAC;AAEnB,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PublicKeyRecord, PrivateKeyRecord } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generate a new Ed25519 key pair for a responder.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateKeyPair(responderId: string, responderName: string): {
|
|
6
|
+
publicKeyRecord: PublicKeyRecord;
|
|
7
|
+
privateKeyRecord: PrivateKeyRecord;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Save a public key to the trusted keys directory (git-tracked).
|
|
11
|
+
*/
|
|
12
|
+
export declare function saveTrustedPublicKey(publicKeyRecord: PublicKeyRecord, baseDir?: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Save a private key to the private keys directory (gitignored).
|
|
15
|
+
*/
|
|
16
|
+
export declare function savePrivateKey(privateKeyRecord: PrivateKeyRecord, baseDir?: string): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Load all trusted public keys from the trusted directory.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadTrustedPublicKeys(baseDir?: string): Promise<PublicKeyRecord[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Load a private key by fingerprint.
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadPrivateKey(fingerprint: string, baseDir?: string): Promise<PrivateKeyRecord | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Rotate a key: generate new pair, save both, mark old public key as expired.
|
|
27
|
+
*/
|
|
28
|
+
export declare function rotateKey(responderId: string, responderName: string, oldFingerprint: string, baseDir?: string): Promise<{
|
|
29
|
+
publicKeyRecord: PublicKeyRecord;
|
|
30
|
+
privateKeyRecord: PrivateKeyRecord;
|
|
31
|
+
}>;
|
|
32
|
+
export type { PublicKeyRecord, PrivateKeyRecord, KeyPairMetadata } from "./types.js";
|
|
33
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/proven/keys.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpE;;GAEG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB;IAAE,eAAe,EAAE,eAAe,CAAC;IAAC,gBAAgB,EAAE,gBAAgB,CAAA;CAAE,CAyB1E;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,eAAe,EAChC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CASjB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,gBAAgB,EAAE,gBAAgB,EAClC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CASjB;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,EAAE,CAAC,CAmB5B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAYlC;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,eAAe,EAAE,eAAe,CAAC;IAAC,gBAAgB,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAqBnF;AAED,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { generateKeyPairSync, createHash } from "node:crypto";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Generate a new Ed25519 key pair for a responder.
|
|
6
|
+
*/
|
|
7
|
+
export function generateKeyPair(responderId, responderName) {
|
|
8
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
9
|
+
const pubDer = publicKey.export({ type: "spki", format: "der" });
|
|
10
|
+
const privPkcs8 = privateKey.export({ type: "pkcs8", format: "der" });
|
|
11
|
+
const fingerprint = createHash("sha256").update(pubDer).digest("hex");
|
|
12
|
+
const now = new Date().toISOString();
|
|
13
|
+
return {
|
|
14
|
+
publicKeyRecord: {
|
|
15
|
+
publicKey: pubDer.toString("base64"),
|
|
16
|
+
metadata: {
|
|
17
|
+
fingerprint,
|
|
18
|
+
responderId,
|
|
19
|
+
responderName,
|
|
20
|
+
createdAt: now,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
privateKeyRecord: {
|
|
24
|
+
privateKey: privPkcs8.toString("base64"),
|
|
25
|
+
fingerprint,
|
|
26
|
+
responderId,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Save a public key to the trusted keys directory (git-tracked).
|
|
32
|
+
*/
|
|
33
|
+
export async function saveTrustedPublicKey(publicKeyRecord, baseDir) {
|
|
34
|
+
const dir = baseDir
|
|
35
|
+
? path.join(baseDir, ".keys", "trusted")
|
|
36
|
+
: path.resolve(process.cwd(), ".breakpoints", ".keys", "trusted");
|
|
37
|
+
await fs.mkdir(dir, { recursive: true });
|
|
38
|
+
const filePath = path.join(dir, `${publicKeyRecord.metadata.fingerprint}.pub.json`);
|
|
39
|
+
await fs.writeFile(filePath, JSON.stringify(publicKeyRecord, null, 2) + "\n", "utf-8");
|
|
40
|
+
return filePath;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Save a private key to the private keys directory (gitignored).
|
|
44
|
+
*/
|
|
45
|
+
export async function savePrivateKey(privateKeyRecord, baseDir) {
|
|
46
|
+
const dir = baseDir
|
|
47
|
+
? path.join(baseDir, ".keys", "private")
|
|
48
|
+
: path.resolve(process.cwd(), ".breakpoints", ".keys", "private");
|
|
49
|
+
await fs.mkdir(dir, { recursive: true });
|
|
50
|
+
const filePath = path.join(dir, `${privateKeyRecord.fingerprint}.key.json`);
|
|
51
|
+
await fs.writeFile(filePath, JSON.stringify(privateKeyRecord, null, 2) + "\n", "utf-8");
|
|
52
|
+
return filePath;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Load all trusted public keys from the trusted directory.
|
|
56
|
+
*/
|
|
57
|
+
export async function loadTrustedPublicKeys(baseDir) {
|
|
58
|
+
const dir = baseDir
|
|
59
|
+
? path.join(baseDir, ".keys", "trusted")
|
|
60
|
+
: path.resolve(process.cwd(), ".breakpoints", ".keys", "trusted");
|
|
61
|
+
let files;
|
|
62
|
+
try {
|
|
63
|
+
files = await fs.readdir(dir);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const keys = [];
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
if (!file.endsWith(".pub.json"))
|
|
71
|
+
continue;
|
|
72
|
+
const raw = await fs.readFile(path.join(dir, file), "utf-8");
|
|
73
|
+
keys.push(JSON.parse(raw));
|
|
74
|
+
}
|
|
75
|
+
return keys;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Load a private key by fingerprint.
|
|
79
|
+
*/
|
|
80
|
+
export async function loadPrivateKey(fingerprint, baseDir) {
|
|
81
|
+
const dir = baseDir
|
|
82
|
+
? path.join(baseDir, ".keys", "private")
|
|
83
|
+
: path.resolve(process.cwd(), ".breakpoints", ".keys", "private");
|
|
84
|
+
const filePath = path.join(dir, `${fingerprint}.key.json`);
|
|
85
|
+
try {
|
|
86
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
87
|
+
return JSON.parse(raw);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Rotate a key: generate new pair, save both, mark old public key as expired.
|
|
95
|
+
*/
|
|
96
|
+
export async function rotateKey(responderId, responderName, oldFingerprint, baseDir) {
|
|
97
|
+
// Mark old key as expired
|
|
98
|
+
const trustedDir = baseDir
|
|
99
|
+
? path.join(baseDir, ".keys", "trusted")
|
|
100
|
+
: path.resolve(process.cwd(), ".breakpoints", ".keys", "trusted");
|
|
101
|
+
const oldKeyPath = path.join(trustedDir, `${oldFingerprint}.pub.json`);
|
|
102
|
+
try {
|
|
103
|
+
const raw = await fs.readFile(oldKeyPath, "utf-8");
|
|
104
|
+
const oldKey = JSON.parse(raw);
|
|
105
|
+
oldKey.metadata.expiresAt = new Date().toISOString();
|
|
106
|
+
oldKey.metadata.note = `Rotated. Superseded by new key for ${responderId}.`;
|
|
107
|
+
await fs.writeFile(oldKeyPath, JSON.stringify(oldKey, null, 2) + "\n", "utf-8");
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Old key may not exist; that's fine
|
|
111
|
+
}
|
|
112
|
+
// Generate and save new pair
|
|
113
|
+
const newPair = generateKeyPair(responderId, responderName);
|
|
114
|
+
await saveTrustedPublicKey(newPair.publicKeyRecord, baseDir);
|
|
115
|
+
await savePrivateKey(newPair.privateKeyRecord, baseDir);
|
|
116
|
+
return newPair;
|
|
117
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BreakpointAnswer, ProvenBreakpointAnswer } from "../types.js";
|
|
2
|
+
import type { PrivateKeyRecord } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Sign a BreakpointAnswer with the responder's private key.
|
|
5
|
+
*
|
|
6
|
+
* Returns a ProvenBreakpointAnswer with the signature and key metadata.
|
|
7
|
+
*/
|
|
8
|
+
export declare function signAnswer(answer: BreakpointAnswer, fingerprint: string, baseDir?: string): Promise<ProvenBreakpointAnswer>;
|
|
9
|
+
/**
|
|
10
|
+
* Sign a BreakpointAnswer using an already-loaded PrivateKeyRecord.
|
|
11
|
+
*
|
|
12
|
+
* This avoids requiring the key to be persisted on disk and is useful
|
|
13
|
+
* for backends that load the key from a configured path.
|
|
14
|
+
*/
|
|
15
|
+
export declare function signAnswerWithKeyRecord(answer: BreakpointAnswer, keyRecord: PrivateKeyRecord): ProvenBreakpointAnswer;
|
|
16
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../src/proven/sign.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA4BnD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,CAAC,CAOjC;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,gBAAgB,GAC1B,sBAAsB,CAiBxB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createPrivateKey, sign as cryptoSign } from "node:crypto";
|
|
2
|
+
import { loadPrivateKey } from "./keys.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fields included in the signature. The order is canonical.
|
|
5
|
+
*/
|
|
6
|
+
const SIGNED_FIELDS = [
|
|
7
|
+
"id",
|
|
8
|
+
"breakpointId",
|
|
9
|
+
"responderId",
|
|
10
|
+
"text",
|
|
11
|
+
"approved",
|
|
12
|
+
"confidence",
|
|
13
|
+
"answeredAt",
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Build the canonical signing payload from an answer.
|
|
17
|
+
* Fields are sorted, null/undefined serialized as empty string.
|
|
18
|
+
*/
|
|
19
|
+
function buildSigningPayload(answer) {
|
|
20
|
+
const parts = [];
|
|
21
|
+
for (const field of SIGNED_FIELDS) {
|
|
22
|
+
const value = answer[field];
|
|
23
|
+
parts.push(`${field}=${value ?? ""}`);
|
|
24
|
+
}
|
|
25
|
+
return Buffer.from(parts.join("\n"), "utf-8");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Sign a BreakpointAnswer with the responder's private key.
|
|
29
|
+
*
|
|
30
|
+
* Returns a ProvenBreakpointAnswer with the signature and key metadata.
|
|
31
|
+
*/
|
|
32
|
+
export async function signAnswer(answer, fingerprint, baseDir) {
|
|
33
|
+
const keyRecord = await loadPrivateKey(fingerprint, baseDir);
|
|
34
|
+
if (!keyRecord) {
|
|
35
|
+
throw new Error(`Private key not found for fingerprint: ${fingerprint}`);
|
|
36
|
+
}
|
|
37
|
+
return signAnswerWithKeyRecord(answer, keyRecord);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Sign a BreakpointAnswer using an already-loaded PrivateKeyRecord.
|
|
41
|
+
*
|
|
42
|
+
* This avoids requiring the key to be persisted on disk and is useful
|
|
43
|
+
* for backends that load the key from a configured path.
|
|
44
|
+
*/
|
|
45
|
+
export function signAnswerWithKeyRecord(answer, keyRecord) {
|
|
46
|
+
const privateKey = createPrivateKey({
|
|
47
|
+
key: Buffer.from(keyRecord.privateKey, "base64"),
|
|
48
|
+
format: "der",
|
|
49
|
+
type: "pkcs8",
|
|
50
|
+
});
|
|
51
|
+
const payload = buildSigningPayload(answer);
|
|
52
|
+
const signature = cryptoSign(null, payload, privateKey);
|
|
53
|
+
return {
|
|
54
|
+
...answer,
|
|
55
|
+
signature: signature.toString("base64"),
|
|
56
|
+
publicKeyFingerprint: keyRecord.fingerprint,
|
|
57
|
+
signedAt: new Date().toISOString(),
|
|
58
|
+
signedFields: [...SIGNED_FIELDS],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface KeyPairMetadata {
|
|
2
|
+
/** Hex-encoded fingerprint (SHA-256 of public key DER). */
|
|
3
|
+
fingerprint: string;
|
|
4
|
+
/** Responder identity associated with this key. */
|
|
5
|
+
responderId: string;
|
|
6
|
+
responderName: string;
|
|
7
|
+
/** ISO timestamp of key creation. */
|
|
8
|
+
createdAt: string;
|
|
9
|
+
/** ISO timestamp when this key should no longer be used for signing. */
|
|
10
|
+
expiresAt?: string;
|
|
11
|
+
/** Human-readable note. */
|
|
12
|
+
note?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PublicKeyRecord {
|
|
15
|
+
/** Base64-encoded Ed25519 public key (DER/SPKI format). */
|
|
16
|
+
publicKey: string;
|
|
17
|
+
metadata: KeyPairMetadata;
|
|
18
|
+
}
|
|
19
|
+
export interface PrivateKeyRecord {
|
|
20
|
+
/** Base64-encoded Ed25519 private key (PKCS8 format). */
|
|
21
|
+
privateKey: string;
|
|
22
|
+
/** Fingerprint linking to the corresponding public key. */
|
|
23
|
+
fingerprint: string;
|
|
24
|
+
responderId: string;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/proven/types.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// ── Proven-specific types ───────────────────────────────────────────────
|
|
2
|
+
// These types are used internally by the proven subsystem for key management.
|
|
3
|
+
// The ProvenBreakpointAnswer and ProvenVerificationResult schemas live in
|
|
4
|
+
// src/types.ts as they are part of the public domain model.
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ProvenBreakpointAnswer, ProvenVerificationResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Verify a proven breakpoint answer against trusted public keys.
|
|
4
|
+
*/
|
|
5
|
+
export declare function verifyAnswer(provenAnswer: ProvenBreakpointAnswer, baseDir?: string): Promise<ProvenVerificationResult>;
|
|
6
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/proven/verify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,wBAAwB,EAAoB,MAAM,aAAa,CAAC;AAetG;;GAEG;AACH,wBAAsB,YAAY,CAChC,YAAY,EAAE,sBAAsB,EACpC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,CAAC,CAiDnC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createPublicKey, verify as cryptoVerify } from "node:crypto";
|
|
2
|
+
import { loadTrustedPublicKeys } from "./keys.js";
|
|
3
|
+
/**
|
|
4
|
+
* Rebuild the canonical signing payload for verification.
|
|
5
|
+
*/
|
|
6
|
+
function buildSigningPayload(answer, signedFields) {
|
|
7
|
+
const parts = [];
|
|
8
|
+
for (const field of signedFields) {
|
|
9
|
+
const value = answer[field];
|
|
10
|
+
parts.push(`${field}=${value ?? ""}`);
|
|
11
|
+
}
|
|
12
|
+
return Buffer.from(parts.join("\n"), "utf-8");
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Verify a proven breakpoint answer against trusted public keys.
|
|
16
|
+
*/
|
|
17
|
+
export async function verifyAnswer(provenAnswer, baseDir) {
|
|
18
|
+
const trustedKeys = await loadTrustedPublicKeys(baseDir);
|
|
19
|
+
const matchingKey = trustedKeys.find((k) => k.metadata.fingerprint === provenAnswer.publicKeyFingerprint);
|
|
20
|
+
if (!matchingKey) {
|
|
21
|
+
return {
|
|
22
|
+
valid: false,
|
|
23
|
+
publicKeyFingerprint: provenAnswer.publicKeyFingerprint,
|
|
24
|
+
reason: "Public key not found in trusted keys",
|
|
25
|
+
verifiedAt: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// Check key expiration
|
|
29
|
+
if (matchingKey.metadata.expiresAt) {
|
|
30
|
+
const expiresAt = new Date(matchingKey.metadata.expiresAt);
|
|
31
|
+
const signedAt = new Date(provenAnswer.signedAt);
|
|
32
|
+
if (signedAt > expiresAt) {
|
|
33
|
+
return {
|
|
34
|
+
valid: false,
|
|
35
|
+
publicKeyFingerprint: provenAnswer.publicKeyFingerprint,
|
|
36
|
+
responderName: matchingKey.metadata.responderName,
|
|
37
|
+
reason: "Key was expired at time of signing",
|
|
38
|
+
verifiedAt: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Verify signature
|
|
43
|
+
const publicKey = createPublicKey({
|
|
44
|
+
key: Buffer.from(matchingKey.publicKey, "base64"),
|
|
45
|
+
format: "der",
|
|
46
|
+
type: "spki",
|
|
47
|
+
});
|
|
48
|
+
const payload = buildSigningPayload(provenAnswer, provenAnswer.signedFields);
|
|
49
|
+
const signatureBuffer = Buffer.from(provenAnswer.signature, "base64");
|
|
50
|
+
const isValid = cryptoVerify(null, payload, publicKey, signatureBuffer);
|
|
51
|
+
return {
|
|
52
|
+
valid: isValid,
|
|
53
|
+
publicKeyFingerprint: provenAnswer.publicKeyFingerprint,
|
|
54
|
+
responderName: matchingKey.metadata.responderName,
|
|
55
|
+
reason: isValid ? "Signature verified successfully" : "Signature verification failed",
|
|
56
|
+
verifiedAt: new Date().toISOString(),
|
|
57
|
+
};
|
|
58
|
+
}
|