@groundnuty/macf-core 0.2.0-rc.0

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.
Files changed (77) hide show
  1. package/dist/certs/agent-cert.d.ts +91 -0
  2. package/dist/certs/agent-cert.d.ts.map +1 -0
  3. package/dist/certs/agent-cert.js +263 -0
  4. package/dist/certs/agent-cert.js.map +1 -0
  5. package/dist/certs/ca.d.ts +103 -0
  6. package/dist/certs/ca.d.ts.map +1 -0
  7. package/dist/certs/ca.js +306 -0
  8. package/dist/certs/ca.js.map +1 -0
  9. package/dist/certs/challenge-store.d.ts +28 -0
  10. package/dist/certs/challenge-store.d.ts.map +1 -0
  11. package/dist/certs/challenge-store.js +94 -0
  12. package/dist/certs/challenge-store.js.map +1 -0
  13. package/dist/certs/challenge.d.ts +70 -0
  14. package/dist/certs/challenge.d.ts.map +1 -0
  15. package/dist/certs/challenge.js +54 -0
  16. package/dist/certs/challenge.js.map +1 -0
  17. package/dist/certs/crypto-provider.d.ts +14 -0
  18. package/dist/certs/crypto-provider.d.ts.map +1 -0
  19. package/dist/certs/crypto-provider.js +18 -0
  20. package/dist/certs/crypto-provider.js.map +1 -0
  21. package/dist/certs/index.d.ts +7 -0
  22. package/dist/certs/index.d.ts.map +1 -0
  23. package/dist/certs/index.js +5 -0
  24. package/dist/certs/index.js.map +1 -0
  25. package/dist/config.d.ts +3 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +131 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/errors.d.ts +51 -0
  30. package/dist/errors.d.ts.map +1 -0
  31. package/dist/errors.js +78 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/index.d.ts +24 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +25 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/logger.d.ts +6 -0
  38. package/dist/logger.d.ts.map +1 -0
  39. package/dist/logger.js +39 -0
  40. package/dist/logger.js.map +1 -0
  41. package/dist/mtls-health-ping.d.ts +26 -0
  42. package/dist/mtls-health-ping.d.ts.map +1 -0
  43. package/dist/mtls-health-ping.js +53 -0
  44. package/dist/mtls-health-ping.js.map +1 -0
  45. package/dist/registry/factory.d.ts +10 -0
  46. package/dist/registry/factory.d.ts.map +1 -0
  47. package/dist/registry/factory.js +26 -0
  48. package/dist/registry/factory.js.map +1 -0
  49. package/dist/registry/github-client.d.ts +14 -0
  50. package/dist/registry/github-client.d.ts.map +1 -0
  51. package/dist/registry/github-client.js +104 -0
  52. package/dist/registry/github-client.js.map +1 -0
  53. package/dist/registry/index.d.ts +7 -0
  54. package/dist/registry/index.d.ts.map +1 -0
  55. package/dist/registry/index.js +6 -0
  56. package/dist/registry/index.js.map +1 -0
  57. package/dist/registry/registry.d.ts +8 -0
  58. package/dist/registry/registry.d.ts.map +1 -0
  59. package/dist/registry/registry.js +65 -0
  60. package/dist/registry/registry.js.map +1 -0
  61. package/dist/registry/types.d.ts +56 -0
  62. package/dist/registry/types.d.ts.map +1 -0
  63. package/dist/registry/types.js +29 -0
  64. package/dist/registry/types.js.map +1 -0
  65. package/dist/registry/variable-name.d.ts +15 -0
  66. package/dist/registry/variable-name.d.ts.map +1 -0
  67. package/dist/registry/variable-name.js +17 -0
  68. package/dist/registry/variable-name.js.map +1 -0
  69. package/dist/token.d.ts +29 -0
  70. package/dist/token.d.ts.map +1 -0
  71. package/dist/token.js +44 -0
  72. package/dist/token.js.map +1 -0
  73. package/dist/types.d.ts +151 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +102 -0
  76. package/dist/types.js.map +1 -0
  77. package/package.json +37 -0
@@ -0,0 +1,91 @@
1
+ import { MacfError } from '../errors.js';
2
+ export declare class AgentCertError extends MacfError {
3
+ constructor(message: string);
4
+ }
5
+ export interface AgentCertResult {
6
+ readonly certPem: string;
7
+ readonly keyPem: string;
8
+ }
9
+ /**
10
+ * Import a PEM private key into a WebCrypto CryptoKey for signing.
11
+ *
12
+ * Return type was `Promise<unknown>` historically — DOM CryptoKey types
13
+ * weren't exposed via @types/node < v25. Since @types/node v25 (#17 /
14
+ * PR #130) CryptoKey is resolvable from `globalThis`, so we return
15
+ * the precise type instead of laundering through `unknown` at each
16
+ * call site.
17
+ *
18
+ * Rejects input that contains zero or multiple BEGIN/END marker pairs
19
+ * (e.g. two keys accidentally concatenated) — ultrareview finding H4.
20
+ * Without this shape check, `webcrypto.subtle.importKey` would be
21
+ * handed a concatenated base64 blob and throw a generic DataError,
22
+ * which propagates upstream with no hint that the input file itself
23
+ * was malformed.
24
+ */
25
+ export declare function importPrivateKey(keyPem: string): Promise<CryptoKey>;
26
+ /**
27
+ * Generate agent certificate signed by the CA.
28
+ * Used when the CA key is available locally.
29
+ *
30
+ * `advertiseHost`, when supplied, is added to the cert's SAN list on
31
+ * top of the default [127.0.0.1, localhost] pair. This is how an agent
32
+ * reachable at a Tailscale IP / DNS name passes server-hostname
33
+ * verification when the routing Action (or a sibling agent) connects
34
+ * over the network. Classification is IPv4-shape vs DNS via
35
+ * `hostToSan()`. See macf#178 Gap 3.
36
+ */
37
+ export declare function generateAgentCert(config: {
38
+ readonly agentName: string;
39
+ readonly caCertPem: string;
40
+ readonly caKeyPem: string;
41
+ readonly advertiseHost?: string;
42
+ readonly certPath?: string;
43
+ readonly keyPath?: string;
44
+ }): Promise<AgentCertResult>;
45
+ /**
46
+ * Generate a CA-signed client cert with a given CN and validity window.
47
+ * Used for non-peer clients (e.g. the routing Action's mTLS cert, per
48
+ * macf-actions#8 / #119). Unlike generateAgentCert, validity is
49
+ * parameterized in days so operator can pick the policy.
50
+ *
51
+ * Does NOT add SubjectAlternativeName — the routing Action is an
52
+ * mTLS CLIENT, so the server-hostname SAN pattern doesn't apply. Key
53
+ * usage is digital signature only (no key encipherment — we're not
54
+ * doing static-key TLS variants).
55
+ */
56
+ export declare function generateClientCert(config: {
57
+ readonly commonName: string;
58
+ readonly validityDays: number;
59
+ readonly caCertPem: string;
60
+ readonly caKeyPem: string;
61
+ }): Promise<AgentCertResult>;
62
+ /**
63
+ * Generate a CSR (Certificate Signing Request) for an agent.
64
+ * Used when requesting remote signing via /sign endpoint.
65
+ */
66
+ export declare function generateCSR(agentName: string): Promise<{
67
+ readonly csrPem: string;
68
+ readonly keyPem: string;
69
+ }>;
70
+ /**
71
+ * Extract the CN from a subject string like "CN=code-agent" or
72
+ * "O=foo,CN=code-agent,OU=bar".
73
+ *
74
+ * Returns undefined if the subject contains ZERO or MULTIPLE CN fields.
75
+ * A multi-CN subject ("CN=attacker,CN=victim") is explicitly rejected
76
+ * — signCSR surfaces a specific error so the confused-deputy attack
77
+ * can't slip through the agent-name equality check. See #89.
78
+ *
79
+ * Exported for unit tests.
80
+ */
81
+ export declare function extractCN(subject: string): string | undefined;
82
+ /**
83
+ * Sign a CSR using the CA key. Validates CN match and CSR signature (proof-of-possession).
84
+ */
85
+ export declare function signCSR(config: {
86
+ readonly csrPem: string;
87
+ readonly agentName: string;
88
+ readonly caCertPem: string;
89
+ readonly caKeyPem: string;
90
+ }): Promise<string>;
91
+ //# sourceMappingURL=agent-cert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-cert.d.ts","sourceRoot":"","sources":["../../src/certs/agent-cert.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAQD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CA2BzE;AAsGD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,eAAe,CAAC,CAuB3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,eAAe,CAAC,CAgD3B;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC,CAmBD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK7D;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,MAAM,CAAC,CAiClB"}
@@ -0,0 +1,263 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { x509, webcrypto, RSA_ALGORITHM, AGENT_CERT_VALIDITY_YEARS, } from './crypto-provider.js';
4
+ import { MacfError } from '../errors.js';
5
+ export class AgentCertError extends MacfError {
6
+ constructor(message) {
7
+ super('AGENT_CERT_ERROR', message);
8
+ this.name = 'AgentCertError';
9
+ }
10
+ }
11
+ function exportKeyToPem(exported) {
12
+ const b64 = Buffer.from(exported).toString('base64');
13
+ const lines = b64.match(/.{1,64}/g) ?? [];
14
+ return `-----BEGIN PRIVATE KEY-----\n${lines.join('\n')}\n-----END PRIVATE KEY-----\n`;
15
+ }
16
+ /**
17
+ * Import a PEM private key into a WebCrypto CryptoKey for signing.
18
+ *
19
+ * Return type was `Promise<unknown>` historically — DOM CryptoKey types
20
+ * weren't exposed via @types/node < v25. Since @types/node v25 (#17 /
21
+ * PR #130) CryptoKey is resolvable from `globalThis`, so we return
22
+ * the precise type instead of laundering through `unknown` at each
23
+ * call site.
24
+ *
25
+ * Rejects input that contains zero or multiple BEGIN/END marker pairs
26
+ * (e.g. two keys accidentally concatenated) — ultrareview finding H4.
27
+ * Without this shape check, `webcrypto.subtle.importKey` would be
28
+ * handed a concatenated base64 blob and throw a generic DataError,
29
+ * which propagates upstream with no hint that the input file itself
30
+ * was malformed.
31
+ */
32
+ export async function importPrivateKey(keyPem) {
33
+ const beginMatches = keyPem.match(/-----BEGIN PRIVATE KEY-----/g);
34
+ const endMatches = keyPem.match(/-----END PRIVATE KEY-----/g);
35
+ if (!beginMatches || beginMatches.length !== 1) {
36
+ throw new AgentCertError(`Malformed private key PEM: expected exactly one BEGIN marker, got ${beginMatches?.length ?? 0}`);
37
+ }
38
+ if (!endMatches || endMatches.length !== 1) {
39
+ throw new AgentCertError(`Malformed private key PEM: expected exactly one END marker, got ${endMatches?.length ?? 0}`);
40
+ }
41
+ const stripped = keyPem
42
+ .replace(/-----BEGIN PRIVATE KEY-----/g, '')
43
+ .replace(/-----END PRIVATE KEY-----/g, '')
44
+ .replace(/\s/g, '');
45
+ const der = Buffer.from(stripped, 'base64');
46
+ return webcrypto.subtle.importKey('pkcs8', der, RSA_ALGORITHM, false, ['sign']);
47
+ }
48
+ /**
49
+ * Classify a host string as an IP or DNS name for SubjectAlternativeName
50
+ * entries. Shape-only check (matches `999.999.999.999` too — cert
51
+ * generation doesn't validate octet ranges, and we'd rather keep the
52
+ * classifier forgiving than have it silently misclassify a typo'd IP
53
+ * as DNS). IPv6 not handled here; add `:` detection + `[]` URL-wrapping
54
+ * when there's an actual ask.
55
+ */
56
+ function hostToSan(host) {
57
+ const ipv4Shape = /^(\d{1,3}\.){3}\d{1,3}$/;
58
+ return ipv4Shape.test(host)
59
+ ? { type: 'ip', value: host }
60
+ : { type: 'dns', value: host };
61
+ }
62
+ /**
63
+ * Shared peer-cert builder used by both generateAgentCert (new peer
64
+ * certs via `macf certs init`) and signCSR (CSR-signed peer certs
65
+ * via `/sign`). Produces the DR-004-compliant extension set:
66
+ *
67
+ * - KeyUsage: digitalSignature | keyEncipherment (mTLS client+server use)
68
+ * - SubjectAlternativeName: 127.0.0.1 / localhost (always, for local-debug
69
+ * flows — curl-to-localhost for /health etc.) plus any caller-
70
+ * supplied extraSans (typically the agent's advertised host per
71
+ * macf#178 Gap 3)
72
+ * - ExtendedKeyUsage: serverAuth + clientAuth. Agents are dual-role
73
+ * peers — they act as TLS SERVERS when receiving /notify, /health,
74
+ * /sign POSTs, and as TLS CLIENTS when originating POSTs to other
75
+ * peers. Without serverAuth, OpenSSL/curl server-role validation
76
+ * rejects the presented cert with "unsuitable certificate purpose"
77
+ * (curl error 60). See macf#180. #121 still enforces clientAuth
78
+ * server-side at /health + /notify + /sign; serverAuth is purely
79
+ * additive for the client-side TLS validation of agents-as-servers.
80
+ * `generateClientCert` (routing-action) stays client-only — it's
81
+ * a pure client with no server role.
82
+ *
83
+ * Extracted per ultrareview finding A10 — both callers previously
84
+ * duplicated this ~25-line extension list. When DR-004 extensions
85
+ * evolve, a single edit here affects both paths instead of two in
86
+ * lockstep.
87
+ */
88
+ async function buildPeerCert(opts) {
89
+ const caCert = new x509.X509Certificate(opts.caCertPem);
90
+ const caKey = await importPrivateKey(opts.caKeyPem);
91
+ const notBefore = new Date();
92
+ const notAfter = new Date();
93
+ notAfter.setFullYear(notAfter.getFullYear() + AGENT_CERT_VALIDITY_YEARS);
94
+ const sans = [
95
+ { type: 'ip', value: '127.0.0.1' },
96
+ { type: 'dns', value: 'localhost' },
97
+ ...(opts.extraSans ?? []),
98
+ ];
99
+ const cert = await x509.X509CertificateGenerator.create({
100
+ serialNumber: randomBytes(8).toString('hex'),
101
+ subject: opts.subject,
102
+ issuer: caCert.subject,
103
+ notBefore,
104
+ notAfter,
105
+ signingAlgorithm: RSA_ALGORITHM,
106
+ publicKey: opts.publicKey,
107
+ signingKey: caKey,
108
+ extensions: [
109
+ new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
110
+ new x509.SubjectAlternativeNameExtension(sans),
111
+ new x509.ExtendedKeyUsageExtension([
112
+ // serverAuth OID — agents are TLS servers on /notify, /health,
113
+ // /sign. Without this, OpenSSL/curl server-role validation
114
+ // rejects with "unsuitable certificate purpose" (curl error
115
+ // 60). See macf#180.
116
+ '1.3.6.1.5.5.7.3.1',
117
+ // clientAuth OID (#125) — agents are also TLS clients when
118
+ // originating POSTs to peers. Enforced server-side at /health
119
+ // + /notify + /sign per #121.
120
+ '1.3.6.1.5.5.7.3.2',
121
+ ]),
122
+ ],
123
+ });
124
+ return cert.toString('pem');
125
+ }
126
+ /**
127
+ * Generate agent certificate signed by the CA.
128
+ * Used when the CA key is available locally.
129
+ *
130
+ * `advertiseHost`, when supplied, is added to the cert's SAN list on
131
+ * top of the default [127.0.0.1, localhost] pair. This is how an agent
132
+ * reachable at a Tailscale IP / DNS name passes server-hostname
133
+ * verification when the routing Action (or a sibling agent) connects
134
+ * over the network. Classification is IPv4-shape vs DNS via
135
+ * `hostToSan()`. See macf#178 Gap 3.
136
+ */
137
+ export async function generateAgentCert(config) {
138
+ const { agentName, caCertPem, caKeyPem, advertiseHost, certPath, keyPath } = config;
139
+ const agentKeys = await webcrypto.subtle.generateKey(RSA_ALGORITHM, true, ['sign', 'verify']);
140
+ const certPem = await buildPeerCert({
141
+ subject: `CN=${agentName}`,
142
+ caCertPem,
143
+ caKeyPem,
144
+ publicKey: agentKeys.publicKey,
145
+ extraSans: advertiseHost ? [hostToSan(advertiseHost)] : undefined,
146
+ });
147
+ const exported = await webcrypto.subtle.exportKey('pkcs8', agentKeys.privateKey);
148
+ const agentKeyPem = exportKeyToPem(exported);
149
+ if (certPath)
150
+ writeFileSync(certPath, certPem, { mode: 0o644 });
151
+ if (keyPath)
152
+ writeFileSync(keyPath, agentKeyPem, { mode: 0o600 });
153
+ return { certPem, keyPem: agentKeyPem };
154
+ }
155
+ /**
156
+ * Generate a CA-signed client cert with a given CN and validity window.
157
+ * Used for non-peer clients (e.g. the routing Action's mTLS cert, per
158
+ * macf-actions#8 / #119). Unlike generateAgentCert, validity is
159
+ * parameterized in days so operator can pick the policy.
160
+ *
161
+ * Does NOT add SubjectAlternativeName — the routing Action is an
162
+ * mTLS CLIENT, so the server-hostname SAN pattern doesn't apply. Key
163
+ * usage is digital signature only (no key encipherment — we're not
164
+ * doing static-key TLS variants).
165
+ */
166
+ export async function generateClientCert(config) {
167
+ const { commonName, validityDays, caCertPem, caKeyPem } = config;
168
+ if (!Number.isInteger(validityDays) || validityDays < 1) {
169
+ throw new AgentCertError(`validityDays must be a positive integer (got ${validityDays})`);
170
+ }
171
+ const caCert = new x509.X509Certificate(caCertPem);
172
+ const caKey = await importPrivateKey(caKeyPem);
173
+ const clientKeys = await webcrypto.subtle.generateKey(RSA_ALGORITHM, true, ['sign', 'verify']);
174
+ const notBefore = new Date();
175
+ const notAfter = new Date();
176
+ notAfter.setDate(notAfter.getDate() + validityDays);
177
+ const cert = await x509.X509CertificateGenerator.create({
178
+ serialNumber: randomBytes(8).toString('hex'),
179
+ subject: `CN=${commonName}`,
180
+ issuer: caCert.subject,
181
+ notBefore,
182
+ notAfter,
183
+ signingAlgorithm: RSA_ALGORITHM,
184
+ publicKey: clientKeys.publicKey,
185
+ signingKey: caKey,
186
+ extensions: [
187
+ new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true),
188
+ new x509.ExtendedKeyUsageExtension([
189
+ // clientAuth OID — explicit "this cert is for TLS client auth"
190
+ '1.3.6.1.5.5.7.3.2',
191
+ ]),
192
+ ],
193
+ });
194
+ const certPem = cert.toString('pem');
195
+ const exported = await webcrypto.subtle.exportKey('pkcs8', clientKeys.privateKey);
196
+ const keyPem = exportKeyToPem(exported);
197
+ return { certPem, keyPem };
198
+ }
199
+ /**
200
+ * Generate a CSR (Certificate Signing Request) for an agent.
201
+ * Used when requesting remote signing via /sign endpoint.
202
+ */
203
+ export async function generateCSR(agentName) {
204
+ const keys = await webcrypto.subtle.generateKey(RSA_ALGORITHM, true, ['sign', 'verify']);
205
+ const csr = await x509.Pkcs10CertificateRequestGenerator.create({
206
+ name: `CN=${agentName}`,
207
+ keys,
208
+ signingAlgorithm: RSA_ALGORITHM,
209
+ });
210
+ const exported = await webcrypto.subtle.exportKey('pkcs8', keys.privateKey);
211
+ return {
212
+ csrPem: csr.toString('pem'),
213
+ keyPem: exportKeyToPem(exported),
214
+ };
215
+ }
216
+ /**
217
+ * Extract the CN from a subject string like "CN=code-agent" or
218
+ * "O=foo,CN=code-agent,OU=bar".
219
+ *
220
+ * Returns undefined if the subject contains ZERO or MULTIPLE CN fields.
221
+ * A multi-CN subject ("CN=attacker,CN=victim") is explicitly rejected
222
+ * — signCSR surfaces a specific error so the confused-deputy attack
223
+ * can't slip through the agent-name equality check. See #89.
224
+ *
225
+ * Exported for unit tests.
226
+ */
227
+ export function extractCN(subject) {
228
+ const matches = subject.match(/(?:^|,\s*)CN=([^,]+)/gi);
229
+ if (!matches || matches.length !== 1)
230
+ return undefined;
231
+ const inner = /CN=([^,]+)/i.exec(matches[0]);
232
+ return inner?.[1]?.trim();
233
+ }
234
+ /**
235
+ * Sign a CSR using the CA key. Validates CN match and CSR signature (proof-of-possession).
236
+ */
237
+ export async function signCSR(config) {
238
+ const { csrPem, agentName, caCertPem, caKeyPem } = config;
239
+ const csr = new x509.Pkcs10CertificateRequest(csrPem);
240
+ // Verify CSR signature (proof-of-possession — requester controls the private key)
241
+ const csrValid = await csr.verify();
242
+ if (!csrValid) {
243
+ throw new AgentCertError('CSR signature verification failed');
244
+ }
245
+ // Verify CN matches agent name. extractCN returns undefined when the
246
+ // subject has zero OR multiple CN fields — surface that specifically
247
+ // so operators see "subject malformed" rather than "CN undefined does
248
+ // not match ..." (see #89).
249
+ const cn = extractCN(csr.subject);
250
+ if (cn === undefined) {
251
+ throw new AgentCertError(`CSR subject must contain exactly one CN field (got: "${csr.subject}")`);
252
+ }
253
+ if (cn !== agentName) {
254
+ throw new AgentCertError(`CSR CN "${cn}" does not match agent name "${agentName}"`);
255
+ }
256
+ return buildPeerCert({
257
+ subject: csr.subject,
258
+ caCertPem,
259
+ caKeyPem,
260
+ publicKey: csr.publicKey,
261
+ });
262
+ }
263
+ //# sourceMappingURL=agent-cert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-cert.js","sourceRoot":"","sources":["../../src/certs/agent-cert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ import type { GitHubVariablesClient } from '../registry/types.js';
2
+ import { MacfError } from '../errors.js';
3
+ export declare class CaError extends MacfError {
4
+ constructor(message: string);
5
+ }
6
+ export interface CaKeyPair {
7
+ readonly certPem: string;
8
+ readonly keyPem: string;
9
+ }
10
+ /**
11
+ * Create a new CA certificate and key pair.
12
+ * Saves to disk and optionally uploads cert to registry.
13
+ */
14
+ export declare function createCA(config: {
15
+ readonly project: string;
16
+ readonly certPath: string;
17
+ readonly keyPath: string;
18
+ readonly client?: GitHubVariablesClient;
19
+ }): Promise<CaKeyPair>;
20
+ /**
21
+ * Backup CA key to registry, encrypted with AES-256-CBC + PBKDF2.
22
+ * Format is interoperable with openssl enc -aes-256-cbc -pbkdf2.
23
+ */
24
+ export declare function backupCAKey(config: {
25
+ readonly project: string;
26
+ readonly keyPem: string;
27
+ readonly passphrase: string;
28
+ readonly client: GitHubVariablesClient;
29
+ }): Promise<void>;
30
+ /**
31
+ * Recover CA key from registry.
32
+ */
33
+ export declare function recoverCAKey(config: {
34
+ readonly project: string;
35
+ readonly passphrase: string;
36
+ readonly keyPath: string;
37
+ readonly client: GitHubVariablesClient;
38
+ }): Promise<string>;
39
+ export declare const WIRE_FORMAT_VERSION = 2;
40
+ export declare const V2_PBKDF2_ITERS = 600000;
41
+ export declare const V1_PBKDF2_ITERS = 10000;
42
+ /**
43
+ * Encrypt CA key using AES-256-CBC + PBKDF2-SHA256 at 600k iters
44
+ * (DR-011 rev2, OWASP 2023 alignment). Output is a versioned JSON
45
+ * envelope wrapping the OpenSSL-compatible `Salted__` blob:
46
+ *
47
+ * {"v": 2, "iter": 600000, "payload": "<base64 Salted__ ...>"}
48
+ *
49
+ * Manual recovery with openssl CLI (see DR-011-rev2 for full doc):
50
+ * gh api ... --jq '.value' | jq -r .payload | base64 -d | \
51
+ * openssl enc -aes-256-cbc -pbkdf2 -md sha256 -iter 600000 -d -out ca-key.pem
52
+ */
53
+ export declare function encryptCAKey(keyPem: string, passphrase: string): string;
54
+ /**
55
+ * Decrypt a CA key from the on-wire registry value. Dispatches by
56
+ * wire format:
57
+ *
58
+ * - **v2 (JSON envelope, DR-011 rev2+):** parses `{v, iter, payload}`,
59
+ * decrypts `payload` at the envelope's iter count.
60
+ * - **v1 (raw base64 `Salted__` blob, legacy pre-2026-04-16):** treats
61
+ * the value as a raw base64 blob and decrypts at iter=10000 (the
62
+ * OpenSSL 3.0/3.1 default at the time the blob was written).
63
+ *
64
+ * Both paths share the same PEM-shape check (#94) after AES decryption
65
+ * to catch wrong-passphrase attempts that produce valid PKCS7 padding
66
+ * by chance (~6% of wrong passphrases).
67
+ *
68
+ * Disambiguation is safe by construction — base64 output never starts
69
+ * with `{`, so only v2 JSON envelopes hit the JSON path. See DR-011
70
+ * rev2 \"Wire Format\" section for the full spec.
71
+ *
72
+ * Throws CaError on:
73
+ * - malformed v2 envelope (missing/invalid v/iter/payload fields)
74
+ * - missing `Salted__` header inside the payload
75
+ * - PKCS7 padding failure (wrong passphrase, ~94% of the time)
76
+ * - decrypted content doesn't look like a PEM private key (wrong
77
+ * passphrase that happened to produce valid PKCS7 padding)
78
+ */
79
+ export declare function decryptCAKey(encryptedValue: string, passphrase: string): string;
80
+ /**
81
+ * Hand-construct a v1-shaped CA key backup (legacy wire format) at
82
+ * 10000 iters. Exported for `test/certs/wire-format-compat.test.ts`
83
+ * regression guard and for any future tooling that needs to produce
84
+ * legacy-shaped backups (none expected). NOT used by `encryptCAKey`
85
+ * itself — `encryptCAKey` always writes v2. (#115)
86
+ */
87
+ export declare function encryptCAKeyV1Legacy(keyPem: string, passphrase: string): string;
88
+ /**
89
+ * Cheap semantic check: does this look like a PEM-encoded private key?
90
+ * Exported for unit tests. Doesn't validate DER content — only the
91
+ * PEM envelope.
92
+ *
93
+ * Random bytes faking BOTH the 28-char BEGIN and 26-char END markers
94
+ * simultaneously is ~2^-432 per decrypted buffer — effectively
95
+ * impossible. No minimum body length is needed; the markers alone are
96
+ * the distinguisher.
97
+ */
98
+ export declare function isLikelyPemPrivateKey(text: string): boolean;
99
+ /**
100
+ * Load CA cert and key from disk.
101
+ */
102
+ export declare function loadCA(certPath: string, keyPath: string): CaKeyPair;
103
+ //# sourceMappingURL=ca.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ca.d.ts","sourceRoot":"","sources":["../../src/certs/ca.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,qBAAa,OAAQ,SAAQ,SAAS;gBACxB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAsBD;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,MAAM,EAAE;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;CACzC,GAAG,OAAO,CAAC,SAAS,CAAC,CA6CrB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;CACxC,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;CACxC,GAAG,OAAO,CAAC,MAAM,CAAC,CAalB;AAMD,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,eAAe,SAAS,CAAC;AACtC,eAAO,MAAM,eAAe,QAAQ,CAAC;AAoFrC;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAyBvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,YAAY,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAiB/E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAiB/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAO3D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAYnE"}