@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.
- package/dist/certs/agent-cert.d.ts +91 -0
- package/dist/certs/agent-cert.d.ts.map +1 -0
- package/dist/certs/agent-cert.js +263 -0
- package/dist/certs/agent-cert.js.map +1 -0
- package/dist/certs/ca.d.ts +103 -0
- package/dist/certs/ca.d.ts.map +1 -0
- package/dist/certs/ca.js +306 -0
- package/dist/certs/ca.js.map +1 -0
- package/dist/certs/challenge-store.d.ts +28 -0
- package/dist/certs/challenge-store.d.ts.map +1 -0
- package/dist/certs/challenge-store.js +94 -0
- package/dist/certs/challenge-store.js.map +1 -0
- package/dist/certs/challenge.d.ts +70 -0
- package/dist/certs/challenge.d.ts.map +1 -0
- package/dist/certs/challenge.js +54 -0
- package/dist/certs/challenge.js.map +1 -0
- package/dist/certs/crypto-provider.d.ts +14 -0
- package/dist/certs/crypto-provider.d.ts.map +1 -0
- package/dist/certs/crypto-provider.js +18 -0
- package/dist/certs/crypto-provider.js.map +1 -0
- package/dist/certs/index.d.ts +7 -0
- package/dist/certs/index.d.ts.map +1 -0
- package/dist/certs/index.js +5 -0
- package/dist/certs/index.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +131 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +78 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +39 -0
- package/dist/logger.js.map +1 -0
- package/dist/mtls-health-ping.d.ts +26 -0
- package/dist/mtls-health-ping.d.ts.map +1 -0
- package/dist/mtls-health-ping.js +53 -0
- package/dist/mtls-health-ping.js.map +1 -0
- package/dist/registry/factory.d.ts +10 -0
- package/dist/registry/factory.d.ts.map +1 -0
- package/dist/registry/factory.js +26 -0
- package/dist/registry/factory.js.map +1 -0
- package/dist/registry/github-client.d.ts +14 -0
- package/dist/registry/github-client.d.ts.map +1 -0
- package/dist/registry/github-client.js +104 -0
- package/dist/registry/github-client.js.map +1 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +6 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/registry.d.ts +8 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +65 -0
- package/dist/registry/registry.js.map +1 -0
- package/dist/registry/types.d.ts +56 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +29 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/registry/variable-name.d.ts +15 -0
- package/dist/registry/variable-name.d.ts.map +1 -0
- package/dist/registry/variable-name.js +17 -0
- package/dist/registry/variable-name.js.map +1 -0
- package/dist/token.d.ts +29 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +44 -0
- package/dist/token.js.map +1 -0
- package/dist/types.d.ts +151 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +102 -0
- package/dist/types.js.map +1 -0
- package/package.json +37 -0
package/dist/certs/ca.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync, } from 'node:crypto';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { x509, webcrypto, RSA_ALGORITHM, CA_CERT_VALIDITY_YEARS, } from './crypto-provider.js';
|
|
5
|
+
import { toVariableSegment } from '../registry/variable-name.js';
|
|
6
|
+
import { MacfError } from '../errors.js';
|
|
7
|
+
export class CaError extends MacfError {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super('CA_ERROR', message);
|
|
10
|
+
this.name = 'CaError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
// Tight perms on the leaf so the CA private key's parent dir isn't
|
|
14
|
+
// world-traversable even when this path is hit outside `certs init`
|
|
15
|
+
// (e.g. recoverCAKey in a fresh env). mkdirSync's `mode` is ANDed
|
|
16
|
+
// with the process umask, so follow up with chmodSync to guarantee
|
|
17
|
+
// 0o700 regardless of umask. Intermediate dirs keep umask defaults —
|
|
18
|
+
// they're usually ~/.macf/ or similar and not key-adjacent. (#107)
|
|
19
|
+
function ensureDir(filePath) {
|
|
20
|
+
const dir = dirname(filePath);
|
|
21
|
+
if (!existsSync(dir)) {
|
|
22
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
23
|
+
}
|
|
24
|
+
chmodSync(dir, 0o700);
|
|
25
|
+
}
|
|
26
|
+
function exportKeyToPem(exported) {
|
|
27
|
+
const b64 = Buffer.from(exported).toString('base64');
|
|
28
|
+
const lines = b64.match(/.{1,64}/g) ?? [];
|
|
29
|
+
return `-----BEGIN PRIVATE KEY-----\n${lines.join('\n')}\n-----END PRIVATE KEY-----\n`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a new CA certificate and key pair.
|
|
33
|
+
* Saves to disk and optionally uploads cert to registry.
|
|
34
|
+
*/
|
|
35
|
+
export async function createCA(config) {
|
|
36
|
+
const { project, certPath, keyPath, client } = config;
|
|
37
|
+
const keys = await webcrypto.subtle.generateKey(RSA_ALGORITHM, true, ['sign', 'verify']);
|
|
38
|
+
const notBefore = new Date();
|
|
39
|
+
const notAfter = new Date();
|
|
40
|
+
notAfter.setFullYear(notAfter.getFullYear() + CA_CERT_VALIDITY_YEARS);
|
|
41
|
+
const cert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
42
|
+
serialNumber: randomBytes(8).toString('hex'),
|
|
43
|
+
name: `CN=${project}-ca`,
|
|
44
|
+
notBefore,
|
|
45
|
+
notAfter,
|
|
46
|
+
signingAlgorithm: RSA_ALGORITHM,
|
|
47
|
+
keys,
|
|
48
|
+
extensions: [
|
|
49
|
+
new x509.BasicConstraintsExtension(true, 2, true),
|
|
50
|
+
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
const certPem = cert.toString('pem');
|
|
54
|
+
const exported = await webcrypto.subtle.exportKey('pkcs8', keys.privateKey);
|
|
55
|
+
const keyPem = exportKeyToPem(exported);
|
|
56
|
+
// Save to disk
|
|
57
|
+
ensureDir(certPath);
|
|
58
|
+
writeFileSync(certPath, certPem, { mode: 0o644 });
|
|
59
|
+
ensureDir(keyPath);
|
|
60
|
+
writeFileSync(keyPath, keyPem, { mode: 0o600 });
|
|
61
|
+
// Upload CA cert to registry (plaintext PEM)
|
|
62
|
+
if (client) {
|
|
63
|
+
await client.writeVariable(`${toVariableSegment(project)}_CA_CERT`, certPem);
|
|
64
|
+
}
|
|
65
|
+
return { certPem, keyPem };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Backup CA key to registry, encrypted with AES-256-CBC + PBKDF2.
|
|
69
|
+
* Format is interoperable with openssl enc -aes-256-cbc -pbkdf2.
|
|
70
|
+
*/
|
|
71
|
+
export async function backupCAKey(config) {
|
|
72
|
+
const encrypted = encryptCAKey(config.keyPem, config.passphrase);
|
|
73
|
+
const varName = `${toVariableSegment(config.project)}_CA_KEY_ENCRYPTED`;
|
|
74
|
+
await config.client.writeVariable(varName, encrypted);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Recover CA key from registry.
|
|
78
|
+
*/
|
|
79
|
+
export async function recoverCAKey(config) {
|
|
80
|
+
const varName = `${toVariableSegment(config.project)}_CA_KEY_ENCRYPTED`;
|
|
81
|
+
const encrypted = await config.client.readVariable(varName);
|
|
82
|
+
if (encrypted === null) {
|
|
83
|
+
throw new CaError('No encrypted CA key found in registry');
|
|
84
|
+
}
|
|
85
|
+
const keyPem = decryptCAKey(encrypted, config.passphrase);
|
|
86
|
+
ensureDir(config.keyPath);
|
|
87
|
+
writeFileSync(config.keyPath, keyPem, { mode: 0o600 });
|
|
88
|
+
return keyPem;
|
|
89
|
+
}
|
|
90
|
+
// DR-011 rev2 constants. `iter` lives in the v2 envelope so future
|
|
91
|
+
// bumps (e.g. 600k → 1.2M) are iter-only changes without a v-bump.
|
|
92
|
+
// v-bumps are reserved for actual wire-format changes (envelope shape,
|
|
93
|
+
// algorithm swap). See design/decisions/DR-011-ca-key-backup.md.
|
|
94
|
+
export const WIRE_FORMAT_VERSION = 2;
|
|
95
|
+
export const V2_PBKDF2_ITERS = 600000;
|
|
96
|
+
export const V1_PBKDF2_ITERS = 10000;
|
|
97
|
+
// Upper bound on the envelope `iter` field. Without a cap, an
|
|
98
|
+
// attacker with registry-write access could store `{"v":2,"iter":
|
|
99
|
+
// 2147483647, ...}` and block the Node.js main thread on the next
|
|
100
|
+
// decryptCAKey (CPU-DoS via pbkdf2Sync). 10M is already ~16× the
|
|
101
|
+
// current 600k policy and well above any plausible future bump —
|
|
102
|
+
// any registry-stored value above this is treated as malformed.
|
|
103
|
+
// See ultrareview finding C1.
|
|
104
|
+
const MAX_ENVELOPE_ITER = 10_000_000;
|
|
105
|
+
/**
|
|
106
|
+
* Parse an on-wire value as a v2 JSON envelope, or return null for
|
|
107
|
+
* anything else. Disambiguation is safe by construction: a raw base64
|
|
108
|
+
* `Salted__` blob never starts with `{` (base64 alphabet excludes it),
|
|
109
|
+
* so only v2 envelopes parse as JSON objects with the `v` field.
|
|
110
|
+
*/
|
|
111
|
+
function parseV2Envelope(value) {
|
|
112
|
+
// Fast-path: base64 output never starts with `{`, so a non-`{` first
|
|
113
|
+
// char is immediately v1. Skip JSON.parse for the common case.
|
|
114
|
+
if (!value.trimStart().startsWith('{'))
|
|
115
|
+
return null;
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(value);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
124
|
+
return null;
|
|
125
|
+
const rec = parsed;
|
|
126
|
+
if (rec['v'] !== 2)
|
|
127
|
+
return null;
|
|
128
|
+
if (typeof rec['iter'] !== 'number' || rec['iter'] < 1)
|
|
129
|
+
return null;
|
|
130
|
+
if (rec['iter'] > MAX_ENVELOPE_ITER)
|
|
131
|
+
return null;
|
|
132
|
+
if (typeof rec['payload'] !== 'string' || rec['payload'].length === 0)
|
|
133
|
+
return null;
|
|
134
|
+
return { v: 2, iter: rec['iter'], payload: rec['payload'] };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Decrypt a raw `Salted__` OpenSSL-compatible blob at a given iter
|
|
138
|
+
* count. Shared by v1 and v2 paths after envelope is unwrapped.
|
|
139
|
+
* Throws CaError on bad shape, padding failure, or non-PEM output.
|
|
140
|
+
*/
|
|
141
|
+
function decryptSaltedBlob(payloadBase64, passphrase, iters) {
|
|
142
|
+
const data = Buffer.from(payloadBase64, 'base64');
|
|
143
|
+
const magic = data.subarray(0, 8).toString('utf-8');
|
|
144
|
+
if (magic !== 'Salted__') {
|
|
145
|
+
throw new CaError('Invalid encrypted CA key format (missing Salted__ header)');
|
|
146
|
+
}
|
|
147
|
+
const salt = data.subarray(8, 16);
|
|
148
|
+
const ciphertext = data.subarray(16);
|
|
149
|
+
const keyIv = pbkdf2Sync(passphrase, salt, iters, 48, 'sha256');
|
|
150
|
+
const key = keyIv.subarray(0, 32);
|
|
151
|
+
const iv = keyIv.subarray(32, 48);
|
|
152
|
+
const decipher = createDecipheriv('aes-256-cbc', key, iv);
|
|
153
|
+
let decryptedBuf;
|
|
154
|
+
try {
|
|
155
|
+
decryptedBuf = Buffer.concat([
|
|
156
|
+
decipher.update(ciphertext),
|
|
157
|
+
decipher.final(),
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// AES-CBC padding failure — most wrong-passphrase attempts hit
|
|
162
|
+
// this path. Generic error so callers can re-prompt.
|
|
163
|
+
throw new CaError('Decryption failed (wrong passphrase or corrupted ciphertext)');
|
|
164
|
+
}
|
|
165
|
+
const decrypted = decryptedBuf.toString('utf-8');
|
|
166
|
+
if (!isLikelyPemPrivateKey(decrypted)) {
|
|
167
|
+
// Passphrase happened to produce valid PKCS7 padding by chance but
|
|
168
|
+
// the plaintext is random garbage. See #94.
|
|
169
|
+
throw new CaError('Decryption failed (wrong passphrase or corrupted ciphertext)');
|
|
170
|
+
}
|
|
171
|
+
return decrypted;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Encrypt CA key using AES-256-CBC + PBKDF2-SHA256 at 600k iters
|
|
175
|
+
* (DR-011 rev2, OWASP 2023 alignment). Output is a versioned JSON
|
|
176
|
+
* envelope wrapping the OpenSSL-compatible `Salted__` blob:
|
|
177
|
+
*
|
|
178
|
+
* {"v": 2, "iter": 600000, "payload": "<base64 Salted__ ...>"}
|
|
179
|
+
*
|
|
180
|
+
* Manual recovery with openssl CLI (see DR-011-rev2 for full doc):
|
|
181
|
+
* gh api ... --jq '.value' | jq -r .payload | base64 -d | \
|
|
182
|
+
* openssl enc -aes-256-cbc -pbkdf2 -md sha256 -iter 600000 -d -out ca-key.pem
|
|
183
|
+
*/
|
|
184
|
+
export function encryptCAKey(keyPem, passphrase) {
|
|
185
|
+
const salt = randomBytes(8);
|
|
186
|
+
const keyIv = pbkdf2Sync(passphrase, salt, V2_PBKDF2_ITERS, 48, 'sha256');
|
|
187
|
+
const key = keyIv.subarray(0, 32);
|
|
188
|
+
const iv = keyIv.subarray(32, 48);
|
|
189
|
+
const cipher = createCipheriv('aes-256-cbc', key, iv);
|
|
190
|
+
const encrypted = Buffer.concat([
|
|
191
|
+
cipher.update(keyPem, 'utf-8'),
|
|
192
|
+
cipher.final(),
|
|
193
|
+
]);
|
|
194
|
+
// OpenSSL format: "Salted__" + 8-byte salt + ciphertext
|
|
195
|
+
const payload = Buffer.concat([
|
|
196
|
+
Buffer.from('Salted__'),
|
|
197
|
+
salt,
|
|
198
|
+
encrypted,
|
|
199
|
+
]).toString('base64');
|
|
200
|
+
const envelope = {
|
|
201
|
+
v: WIRE_FORMAT_VERSION,
|
|
202
|
+
iter: V2_PBKDF2_ITERS,
|
|
203
|
+
payload,
|
|
204
|
+
};
|
|
205
|
+
return JSON.stringify(envelope);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Decrypt a CA key from the on-wire registry value. Dispatches by
|
|
209
|
+
* wire format:
|
|
210
|
+
*
|
|
211
|
+
* - **v2 (JSON envelope, DR-011 rev2+):** parses `{v, iter, payload}`,
|
|
212
|
+
* decrypts `payload` at the envelope's iter count.
|
|
213
|
+
* - **v1 (raw base64 `Salted__` blob, legacy pre-2026-04-16):** treats
|
|
214
|
+
* the value as a raw base64 blob and decrypts at iter=10000 (the
|
|
215
|
+
* OpenSSL 3.0/3.1 default at the time the blob was written).
|
|
216
|
+
*
|
|
217
|
+
* Both paths share the same PEM-shape check (#94) after AES decryption
|
|
218
|
+
* to catch wrong-passphrase attempts that produce valid PKCS7 padding
|
|
219
|
+
* by chance (~6% of wrong passphrases).
|
|
220
|
+
*
|
|
221
|
+
* Disambiguation is safe by construction — base64 output never starts
|
|
222
|
+
* with `{`, so only v2 JSON envelopes hit the JSON path. See DR-011
|
|
223
|
+
* rev2 \"Wire Format\" section for the full spec.
|
|
224
|
+
*
|
|
225
|
+
* Throws CaError on:
|
|
226
|
+
* - malformed v2 envelope (missing/invalid v/iter/payload fields)
|
|
227
|
+
* - missing `Salted__` header inside the payload
|
|
228
|
+
* - PKCS7 padding failure (wrong passphrase, ~94% of the time)
|
|
229
|
+
* - decrypted content doesn't look like a PEM private key (wrong
|
|
230
|
+
* passphrase that happened to produce valid PKCS7 padding)
|
|
231
|
+
*/
|
|
232
|
+
export function decryptCAKey(encryptedValue, passphrase) {
|
|
233
|
+
// If the value looks like JSON (starts with `{`), the caller intends
|
|
234
|
+
// v2. Validate strictly and throw on malformed envelope rather than
|
|
235
|
+
// fall through to v1 — otherwise a typoed envelope would produce a
|
|
236
|
+
// confusing "missing Salted__" error that doesn't point at the real
|
|
237
|
+
// problem.
|
|
238
|
+
if (encryptedValue.trimStart().startsWith('{')) {
|
|
239
|
+
const envelope = parseV2Envelope(encryptedValue);
|
|
240
|
+
if (!envelope) {
|
|
241
|
+
throw new CaError('Invalid v2 CA key envelope (expected {"v":2, "iter":<number>, "payload":"<base64>"})');
|
|
242
|
+
}
|
|
243
|
+
return decryptSaltedBlob(envelope.payload, passphrase, envelope.iter);
|
|
244
|
+
}
|
|
245
|
+
// v1 legacy path — raw base64 Salted__ blob, implicit iter=10000.
|
|
246
|
+
return decryptSaltedBlob(encryptedValue, passphrase, V1_PBKDF2_ITERS);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Hand-construct a v1-shaped CA key backup (legacy wire format) at
|
|
250
|
+
* 10000 iters. Exported for `test/certs/wire-format-compat.test.ts`
|
|
251
|
+
* regression guard and for any future tooling that needs to produce
|
|
252
|
+
* legacy-shaped backups (none expected). NOT used by `encryptCAKey`
|
|
253
|
+
* itself — `encryptCAKey` always writes v2. (#115)
|
|
254
|
+
*/
|
|
255
|
+
export function encryptCAKeyV1Legacy(keyPem, passphrase) {
|
|
256
|
+
const salt = randomBytes(8);
|
|
257
|
+
const keyIv = pbkdf2Sync(passphrase, salt, V1_PBKDF2_ITERS, 48, 'sha256');
|
|
258
|
+
const key = keyIv.subarray(0, 32);
|
|
259
|
+
const iv = keyIv.subarray(32, 48);
|
|
260
|
+
const cipher = createCipheriv('aes-256-cbc', key, iv);
|
|
261
|
+
const encrypted = Buffer.concat([
|
|
262
|
+
cipher.update(keyPem, 'utf-8'),
|
|
263
|
+
cipher.final(),
|
|
264
|
+
]);
|
|
265
|
+
return Buffer.concat([
|
|
266
|
+
Buffer.from('Salted__'),
|
|
267
|
+
salt,
|
|
268
|
+
encrypted,
|
|
269
|
+
]).toString('base64');
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Cheap semantic check: does this look like a PEM-encoded private key?
|
|
273
|
+
* Exported for unit tests. Doesn't validate DER content — only the
|
|
274
|
+
* PEM envelope.
|
|
275
|
+
*
|
|
276
|
+
* Random bytes faking BOTH the 28-char BEGIN and 26-char END markers
|
|
277
|
+
* simultaneously is ~2^-432 per decrypted buffer — effectively
|
|
278
|
+
* impossible. No minimum body length is needed; the markers alone are
|
|
279
|
+
* the distinguisher.
|
|
280
|
+
*/
|
|
281
|
+
export function isLikelyPemPrivateKey(text) {
|
|
282
|
+
// PKCS#8 ("-----BEGIN PRIVATE KEY-----") or legacy RSA/EC variants.
|
|
283
|
+
const beginIdx = text.search(/-----BEGIN [A-Z ]*PRIVATE KEY-----/);
|
|
284
|
+
if (beginIdx < 0)
|
|
285
|
+
return false;
|
|
286
|
+
const endIdx = text.search(/-----END [A-Z ]*PRIVATE KEY-----/);
|
|
287
|
+
if (endIdx <= beginIdx)
|
|
288
|
+
return false;
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Load CA cert and key from disk.
|
|
293
|
+
*/
|
|
294
|
+
export function loadCA(certPath, keyPath) {
|
|
295
|
+
if (!existsSync(certPath)) {
|
|
296
|
+
throw new CaError(`CA certificate not found: ${certPath}`);
|
|
297
|
+
}
|
|
298
|
+
if (!existsSync(keyPath)) {
|
|
299
|
+
throw new CaError(`CA key not found: ${keyPath}`);
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
certPem: readFileSync(certPath, 'utf-8'),
|
|
303
|
+
keyPem: readFileSync(keyPath, 'utf-8'),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=ca.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ca.js","sourceRoot":"","sources":["../../src/certs/ca.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Challenge state tracked by the server for each outstanding flow. */
|
|
2
|
+
export interface ChallengeRecord {
|
|
3
|
+
readonly challengeId: string;
|
|
4
|
+
readonly agentName: string;
|
|
5
|
+
readonly expectedValue: string;
|
|
6
|
+
readonly expiresAt: number;
|
|
7
|
+
}
|
|
8
|
+
/** Default TTL for a challenge (5 minutes). */
|
|
9
|
+
export declare const DEFAULT_CHALLENGE_TTL_MS: number;
|
|
10
|
+
/**
|
|
11
|
+
* Pluggable clock so tests can advance time deterministically. In
|
|
12
|
+
* production this is just `Date.now`.
|
|
13
|
+
*/
|
|
14
|
+
export type Clock = () => number;
|
|
15
|
+
/**
|
|
16
|
+
* Create a new challenge store. Return a minimal API surface — the store
|
|
17
|
+
* is opaque to the caller except via these methods.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createChallengeStore(options?: {
|
|
20
|
+
readonly ttlMs?: number;
|
|
21
|
+
readonly clock?: Clock;
|
|
22
|
+
}): {
|
|
23
|
+
readonly issue: (agentName: string) => ChallengeRecord;
|
|
24
|
+
readonly consume: (challengeId: string, agentName: string, observedValue: string) => 'ok' | 'mismatch';
|
|
25
|
+
readonly size: () => number;
|
|
26
|
+
};
|
|
27
|
+
export type ChallengeStore = ReturnType<typeof createChallengeStore>;
|
|
28
|
+
//# sourceMappingURL=challenge-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"challenge-store.d.ts","sourceRoot":"","sources":["../../src/certs/challenge-store.ts"],"names":[],"mappings":"AAmBA,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,+CAA+C;AAC/C,eAAO,MAAM,wBAAwB,QAAgB,CAAC;AAEtD;;;GAGG;AACH,MAAM,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;AAEjC;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE;IAC5C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACnB,GAAG;IACP,QAAQ,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,eAAe,CAAC;IACvD,QAAQ,CAAC,OAAO,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,KAAK,IAAI,GAAG,UAAU,CAAC;IACvG,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC;CAC7B,CAwEA;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory challenge store for the /sign endpoint's challenge-response
|
|
3
|
+
* protocol (DR-010).
|
|
4
|
+
*
|
|
5
|
+
* Each outstanding challenge keeps its agent_name, the expected value that
|
|
6
|
+
* the client is supposed to write to the registry, and an expiry timestamp.
|
|
7
|
+
* Expired entries are discarded lazily on each access (no background timer
|
|
8
|
+
* — the server process footprint stays constant regardless of how many
|
|
9
|
+
* abandoned flows accumulate).
|
|
10
|
+
*
|
|
11
|
+
* The store is process-local. If a cert-signing peer is replaced / restarted
|
|
12
|
+
* between step 1 and step 2 of a flow, the in-memory entry is lost and the
|
|
13
|
+
* client's step 2 fails — they must restart with a fresh step 1. This is
|
|
14
|
+
* acceptable: the window is short (5 min default), and losing in-flight
|
|
15
|
+
* state during a server restart is the standard trade-off for avoiding a
|
|
16
|
+
* persistent database. See `design/decisions/DR-010-cert-signing.md`.
|
|
17
|
+
*/
|
|
18
|
+
import { randomUUID, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
19
|
+
/** Default TTL for a challenge (5 minutes). */
|
|
20
|
+
export const DEFAULT_CHALLENGE_TTL_MS = 5 * 60 * 1000;
|
|
21
|
+
/**
|
|
22
|
+
* Create a new challenge store. Return a minimal API surface — the store
|
|
23
|
+
* is opaque to the caller except via these methods.
|
|
24
|
+
*/
|
|
25
|
+
export function createChallengeStore(options = {}) {
|
|
26
|
+
const ttlMs = options.ttlMs ?? DEFAULT_CHALLENGE_TTL_MS;
|
|
27
|
+
const now = options.clock ?? Date.now;
|
|
28
|
+
const map = new Map();
|
|
29
|
+
/** Drop expired entries. O(n); acceptable for our scale (outstanding flows dozen-ish at most). */
|
|
30
|
+
function sweep() {
|
|
31
|
+
const t = now();
|
|
32
|
+
for (const [id, rec] of map) {
|
|
33
|
+
if (rec.expiresAt <= t)
|
|
34
|
+
map.delete(id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Issue a new challenge for `agentName`. */
|
|
38
|
+
function issue(agentName) {
|
|
39
|
+
sweep();
|
|
40
|
+
const challengeId = randomUUID();
|
|
41
|
+
// 32 bytes → base64url keeps the variable value printable + URL-safe
|
|
42
|
+
// (GitHub variables accept any UTF-8 but keeping it ASCII avoids edge
|
|
43
|
+
// cases around encoding). 43 chars after base64url with no padding.
|
|
44
|
+
const expectedValue = randomBytes(32).toString('base64url');
|
|
45
|
+
const rec = {
|
|
46
|
+
challengeId,
|
|
47
|
+
agentName,
|
|
48
|
+
expectedValue,
|
|
49
|
+
expiresAt: now() + ttlMs,
|
|
50
|
+
};
|
|
51
|
+
map.set(challengeId, rec);
|
|
52
|
+
return rec;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Consume a challenge: match challenge_id + agent_name + timing-safe value
|
|
56
|
+
* comparison. On both success AND mismatch, the in-memory entry is deleted
|
|
57
|
+
* to prevent replay. The caller is responsible for deleting the registry
|
|
58
|
+
* variable (so that the success-log-then-delete-variable sequence stays
|
|
59
|
+
* atomic in the server; keeping that outside the store avoids passing the
|
|
60
|
+
* GitHub client in here).
|
|
61
|
+
*
|
|
62
|
+
* Returns:
|
|
63
|
+
* 'ok' — challenge matched, entry deleted (caller should sign)
|
|
64
|
+
* 'mismatch' — anything wrong (not found / expired / wrong agent / wrong value)
|
|
65
|
+
* — do NOT leak which; caller returns generic error
|
|
66
|
+
*/
|
|
67
|
+
function consume(challengeId, agentName, observedValue) {
|
|
68
|
+
sweep();
|
|
69
|
+
const rec = map.get(challengeId);
|
|
70
|
+
if (!rec)
|
|
71
|
+
return 'mismatch';
|
|
72
|
+
// Always delete on any observation to block replay, even on mismatch.
|
|
73
|
+
map.delete(challengeId);
|
|
74
|
+
if (rec.agentName !== agentName)
|
|
75
|
+
return 'mismatch';
|
|
76
|
+
// timingSafeEqual requires equal-length buffers. Different-length
|
|
77
|
+
// observed value is an obvious mismatch — short-circuit before the
|
|
78
|
+
// crypto call so we don't throw on the length mismatch.
|
|
79
|
+
const expected = Buffer.from(rec.expectedValue, 'utf-8');
|
|
80
|
+
const observed = Buffer.from(observedValue, 'utf-8');
|
|
81
|
+
if (expected.length !== observed.length)
|
|
82
|
+
return 'mismatch';
|
|
83
|
+
if (!timingSafeEqual(expected, observed))
|
|
84
|
+
return 'mismatch';
|
|
85
|
+
return 'ok';
|
|
86
|
+
}
|
|
87
|
+
/** For tests + observability. */
|
|
88
|
+
function size() {
|
|
89
|
+
sweep();
|
|
90
|
+
return map.size;
|
|
91
|
+
}
|
|
92
|
+
return { issue, consume, size };
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=challenge-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"challenge-store.js","sourceRoot":"","sources":["../../src/certs/challenge-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAUvE,+CAA+C;AAC/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAQtD;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAGjC,EAAE;IAKJ,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,wBAAwB,CAAC;IACxD,MAAM,GAAG,GAAU,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC;IAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,EAA2B,CAAC;IAE/C,kGAAkG;IAClG,SAAS,KAAK;QACZ,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;YAC5B,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC;gBAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,6CAA6C;IAC7C,SAAS,KAAK,CAAC,SAAiB;QAC9B,KAAK,EAAE,CAAC;QACR,MAAM,WAAW,GAAG,UAAU,EAAE,CAAC;QACjC,qEAAqE;QACrE,sEAAsE;QACtE,oEAAoE;QACpE,MAAM,aAAa,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC5D,MAAM,GAAG,GAAoB;YAC3B,WAAW;YACX,SAAS;YACT,aAAa;YACb,SAAS,EAAE,GAAG,EAAE,GAAG,KAAK;SACzB,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAC1B,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,SAAS,OAAO,CACd,WAAmB,EACnB,SAAiB,EACjB,aAAqB;QAErB,KAAK,EAAE,CAAC;QACR,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACjC,IAAI,CAAC,GAAG;YAAE,OAAO,UAAU,CAAC;QAC5B,sEAAsE;QACtE,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACxB,IAAI,GAAG,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO,UAAU,CAAC;QACnD,kEAAkE;QAClE,mEAAmE;QACnE,wDAAwD;QACxD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACrD,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;YAAE,OAAO,UAAU,CAAC;QAC3D,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC;YAAE,OAAO,UAAU,CAAC;QAC5D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iCAAiC;IACjC,SAAS,IAAI;QACX,KAAK,EAAE,CAAC;QACR,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Challenge-response for /sign per DR-010 (security fix — issue #80).
|
|
3
|
+
*
|
|
4
|
+
* Protocol (corrected per science-agent's implementation guidance):
|
|
5
|
+
*
|
|
6
|
+
* Step 1 (server): createChallenge(store, agentName)
|
|
7
|
+
* — allocate (challenge_id, expected_value) in the
|
|
8
|
+
* server's in-memory store
|
|
9
|
+
* — return id + instruction to the client
|
|
10
|
+
* — **do NOT write** the registry variable; that's
|
|
11
|
+
* the client's job, and their doing so is the
|
|
12
|
+
* actual proof-of-GitHub-write-access
|
|
13
|
+
*
|
|
14
|
+
* Client: writes MACF_CHALLENGE_<agent> = <expected_value>
|
|
15
|
+
* in the registry, using ITS OWN github token
|
|
16
|
+
*
|
|
17
|
+
* Step 2 (server): verifyAndConsumeChallenge(store, client, challenge_id,
|
|
18
|
+
* agent_name)
|
|
19
|
+
* — look up challenge_id in store (reject if absent,
|
|
20
|
+
* expired, or agent_name doesn't match)
|
|
21
|
+
* — read MACF_CHALLENGE_<agent> from the registry
|
|
22
|
+
* — timing-safe compare observed vs expected
|
|
23
|
+
* — delete the registry variable + in-memory entry
|
|
24
|
+
* on ANY outcome (success or failure) to prevent
|
|
25
|
+
* replay
|
|
26
|
+
* — return 'ok' / 'mismatch' — caller returns a
|
|
27
|
+
* GENERIC error ('challenge verification failed')
|
|
28
|
+
* for all failure modes to avoid oracle attacks
|
|
29
|
+
*
|
|
30
|
+
* The previous implementation (pre-#80) had the SERVER write the variable
|
|
31
|
+
* in step 1, then read what it itself wrote in step 6 — no comparison,
|
|
32
|
+
* no client-side proof of GitHub write access. Any mTLS cert holder could
|
|
33
|
+
* obtain a cert for arbitrary agent_name. This module is the fix.
|
|
34
|
+
*/
|
|
35
|
+
import type { GitHubVariablesClient } from '../registry/types.js';
|
|
36
|
+
import { MacfError } from '../errors.js';
|
|
37
|
+
import type { ChallengeStore } from './challenge-store.js';
|
|
38
|
+
export declare class ChallengeError extends MacfError {
|
|
39
|
+
constructor(message: string);
|
|
40
|
+
}
|
|
41
|
+
/** Registry variable name for an agent's current challenge. */
|
|
42
|
+
export declare function challengeVarName(project: string, agentName: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Allocate a challenge and return the client-facing (id + instruction).
|
|
45
|
+
* Does NOT write the registry variable — the client does that in the next
|
|
46
|
+
* round-trip, proving GitHub write access at the registry scope.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createChallenge(config: {
|
|
49
|
+
readonly project: string;
|
|
50
|
+
readonly agentName: string;
|
|
51
|
+
readonly store: ChallengeStore;
|
|
52
|
+
}): {
|
|
53
|
+
readonly challengeId: string;
|
|
54
|
+
readonly instruction: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Verify a step-2 request. Caller passes the client-supplied challenge_id
|
|
58
|
+
* and agent_name. We read the registry variable, delete it regardless of
|
|
59
|
+
* outcome (prevents replay), consume the in-memory entry, and return
|
|
60
|
+
* 'ok' / 'mismatch' — the caller surfaces a generic error on mismatch to
|
|
61
|
+
* avoid telling the attacker WHICH check failed.
|
|
62
|
+
*/
|
|
63
|
+
export declare function verifyAndConsumeChallenge(config: {
|
|
64
|
+
readonly project: string;
|
|
65
|
+
readonly agentName: string;
|
|
66
|
+
readonly challengeId: string;
|
|
67
|
+
readonly store: ChallengeStore;
|
|
68
|
+
readonly client: GitHubVariablesClient;
|
|
69
|
+
}): Promise<'ok' | 'mismatch'>;
|
|
70
|
+
//# sourceMappingURL=challenge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"challenge.d.ts","sourceRoot":"","sources":["../../src/certs/challenge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC,GAAG;IAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CASjE;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;CACxC,GAAG,OAAO,CAAC,IAAI,GAAG,UAAU,CAAC,CAsB7B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { toVariableSegment } from '../registry/variable-name.js';
|
|
2
|
+
import { MacfError } from '../errors.js';
|
|
3
|
+
export class ChallengeError extends MacfError {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super('CHALLENGE_ERROR', message);
|
|
6
|
+
this.name = 'ChallengeError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** Registry variable name for an agent's current challenge. */
|
|
10
|
+
export function challengeVarName(project, agentName) {
|
|
11
|
+
return `${toVariableSegment(project)}_CHALLENGE_${toVariableSegment(agentName)}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Allocate a challenge and return the client-facing (id + instruction).
|
|
15
|
+
* Does NOT write the registry variable — the client does that in the next
|
|
16
|
+
* round-trip, proving GitHub write access at the registry scope.
|
|
17
|
+
*/
|
|
18
|
+
export function createChallenge(config) {
|
|
19
|
+
const rec = config.store.issue(config.agentName);
|
|
20
|
+
const varName = challengeVarName(config.project, config.agentName);
|
|
21
|
+
return {
|
|
22
|
+
challengeId: rec.challengeId,
|
|
23
|
+
instruction: `Write registry variable ${varName} = '${rec.expectedValue}'. ` +
|
|
24
|
+
`Then POST /sign again with { challenge_done: true, challenge_id: '${rec.challengeId}' }.`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Verify a step-2 request. Caller passes the client-supplied challenge_id
|
|
29
|
+
* and agent_name. We read the registry variable, delete it regardless of
|
|
30
|
+
* outcome (prevents replay), consume the in-memory entry, and return
|
|
31
|
+
* 'ok' / 'mismatch' — the caller surfaces a generic error on mismatch to
|
|
32
|
+
* avoid telling the attacker WHICH check failed.
|
|
33
|
+
*/
|
|
34
|
+
export async function verifyAndConsumeChallenge(config) {
|
|
35
|
+
const varName = challengeVarName(config.project, config.agentName);
|
|
36
|
+
const observedValue = await config.client.readVariable(varName);
|
|
37
|
+
// Delete the registry variable unconditionally (best-effort). Intentional:
|
|
38
|
+
// mismatch attempts don't leave a re-usable variable behind; attackers get
|
|
39
|
+
// one shot per outstanding challenge.
|
|
40
|
+
try {
|
|
41
|
+
await config.client.deleteVariable(varName);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Ignore; consuming the in-memory entry below still blocks replay
|
|
45
|
+
// server-side, which is the security-critical half.
|
|
46
|
+
}
|
|
47
|
+
if (observedValue === null) {
|
|
48
|
+
// Still consume the in-memory entry (replay-block).
|
|
49
|
+
config.store.consume(config.challengeId, config.agentName, '');
|
|
50
|
+
return 'mismatch';
|
|
51
|
+
}
|
|
52
|
+
return config.store.consume(config.challengeId, config.agentName, observedValue);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=challenge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"challenge.js","sourceRoot":"","sources":["../../src/certs/challenge.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,MAAM,OAAO,cAAe,SAAQ,SAAS;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,+DAA+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,SAAiB;IACjE,OAAO,GAAG,iBAAiB,CAAC,OAAO,CAAC,cAAc,iBAAiB,CAAC,SAAS,CAAC,EAAE,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAI/B;IACC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IACnE,OAAO;QACL,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,WAAW,EACT,2BAA2B,OAAO,OAAO,GAAG,CAAC,aAAa,KAAK;YAC/D,qEAAqE,GAAG,CAAC,WAAW,MAAM;KAC7F,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,MAM/C;IACC,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAEnE,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAEhE,2EAA2E;IAC3E,2EAA2E;IAC3E,sCAAsC;IACtC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,kEAAkE;QAClE,oDAAoD;IACtD,CAAC;IAED,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QAC3B,oDAAoD;QACpD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QAC/D,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AACnF,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import * as x509 from '@peculiar/x509';
|
|
3
|
+
import { webcrypto } from 'node:crypto';
|
|
4
|
+
export declare const RSA_ALGORITHM: {
|
|
5
|
+
readonly name: "RSASSA-PKCS1-v1_5";
|
|
6
|
+
readonly hash: "SHA-256";
|
|
7
|
+
readonly publicExponent: Uint8Array<ArrayBuffer>;
|
|
8
|
+
readonly modulusLength: 2048;
|
|
9
|
+
};
|
|
10
|
+
export declare const CA_CERT_VALIDITY_YEARS = 5;
|
|
11
|
+
export declare const AGENT_CERT_VALIDITY_YEARS = 1;
|
|
12
|
+
export { x509 };
|
|
13
|
+
export { webcrypto };
|
|
14
|
+
//# sourceMappingURL=crypto-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-provider.d.ts","sourceRoot":"","sources":["../../src/certs/crypto-provider.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,IAAI,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAOxC,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,OAAO,EAAE,IAAI,EAAE,CAAC;AAChB,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import * as x509 from '@peculiar/x509';
|
|
3
|
+
import { webcrypto } from 'node:crypto';
|
|
4
|
+
// Set Node.js WebCrypto as the provider for @peculiar/x509
|
|
5
|
+
// webcrypto satisfies the Crypto interface at runtime but TypeScript
|
|
6
|
+
// doesn't include DOM types in our Node-only tsconfig.
|
|
7
|
+
x509.cryptoProvider.set(webcrypto);
|
|
8
|
+
export const RSA_ALGORITHM = {
|
|
9
|
+
name: 'RSASSA-PKCS1-v1_5',
|
|
10
|
+
hash: 'SHA-256',
|
|
11
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
12
|
+
modulusLength: 2048,
|
|
13
|
+
};
|
|
14
|
+
export const CA_CERT_VALIDITY_YEARS = 5;
|
|
15
|
+
export const AGENT_CERT_VALIDITY_YEARS = 1;
|
|
16
|
+
export { x509 };
|
|
17
|
+
export { webcrypto };
|
|
18
|
+
//# sourceMappingURL=crypto-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-provider.js","sourceRoot":"","sources":["../../src/certs/crypto-provider.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,IAAI,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,2DAA2D;AAC3D,qEAAqE;AACrE,uDAAuD;AACvD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAgB,CAAC,CAAC;AAE1C,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,IAAI,EAAE,mBAAmB;IACzB,IAAI,EAAE,SAAS;IACf,cAAc,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACzC,aAAa,EAAE,IAAI;CACX,CAAC;AAEX,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AACxC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAE3C,OAAO,EAAE,IAAI,EAAE,CAAC;AAChB,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createCA, backupCAKey, recoverCAKey, encryptCAKey, encryptCAKeyV1Legacy, decryptCAKey, loadCA, CaError } from './ca.js';
|
|
2
|
+
export type { CaKeyPair } from './ca.js';
|
|
3
|
+
export { generateAgentCert, generateClientCert, generateCSR, signCSR, importPrivateKey, AgentCertError } from './agent-cert.js';
|
|
4
|
+
export type { AgentCertResult } from './agent-cert.js';
|
|
5
|
+
export { createChallenge, verifyAndConsumeChallenge, ChallengeError } from './challenge.js';
|
|
6
|
+
export { RSA_ALGORITHM, CA_CERT_VALIDITY_YEARS, AGENT_CERT_VALIDITY_YEARS } from './crypto-provider.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/certs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACjI,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAChI,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createCA, backupCAKey, recoverCAKey, encryptCAKey, encryptCAKeyV1Legacy, decryptCAKey, loadCA, CaError } from './ca.js';
|
|
2
|
+
export { generateAgentCert, generateClientCert, generateCSR, signCSR, importPrivateKey, AgentCertError } from './agent-cert.js';
|
|
3
|
+
export { createChallenge, verifyAndConsumeChallenge, ChallengeError } from './challenge.js';
|
|
4
|
+
export { RSA_ALGORITHM, CA_CERT_VALIDITY_YEARS, AGENT_CERT_VALIDITY_YEARS } from './crypto-provider.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|