@karpeleslab/teamclaude 1.0.7 → 1.0.8

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/src/x509.js ADDED
@@ -0,0 +1,166 @@
1
+ // Minimal pure-JS X.509 certificate generation (no external deps).
2
+ //
3
+ // node:crypto can create keypairs and sign, but cannot issue certificates, so
4
+ // we hand-encode the (small) ASN.1 DER cert envelope and sign the TBS with the
5
+ // issuer key. Used only to mint a local CA + a leaf for the MITM proxy, which
6
+ // the launched claude process trusts via NODE_EXTRA_CA_CERTS. Nothing here is a
7
+ // general-purpose ASN.1 library — just what these two certs need.
8
+
9
+ import { generateKeyPairSync, sign as cryptoSign, randomBytes } from 'node:crypto';
10
+
11
+ // ── ASN.1 DER primitives ──────────────────────────────────────
12
+
13
+ function derLen(n) {
14
+ if (n < 0x80) return Buffer.from([n]);
15
+ const bytes = [];
16
+ let x = n;
17
+ while (x > 0) { bytes.unshift(x & 0xff); x = Math.floor(x / 256); }
18
+ return Buffer.from([0x80 | bytes.length, ...bytes]);
19
+ }
20
+
21
+ function tlv(tag, content) {
22
+ return Buffer.concat([Buffer.from([tag]), derLen(content.length), content]);
23
+ }
24
+
25
+ const seq = (items) => tlv(0x30, Buffer.concat(items));
26
+ const set = (items) => tlv(0x31, Buffer.concat(items));
27
+ const NULL = Buffer.from([0x05, 0x00]);
28
+ const bool = (v) => tlv(0x01, Buffer.from([v ? 0xff : 0x00]));
29
+ const octet = (buf) => tlv(0x04, buf);
30
+ const bitString = (buf) => tlv(0x03, Buffer.concat([Buffer.from([0]), buf])); // 0 unused bits
31
+ const utf8 = (s) => tlv(0x0c, Buffer.from(s, 'utf8'));
32
+ const explicit = (n, content) => tlv(0xa0 | n, content); // [n] constructed
33
+ const ctxPrim = (n, content) => tlv(0x80 | n, content); // [n] primitive
34
+
35
+ function integer(buf) {
36
+ let b = Buffer.isBuffer(buf) ? Buffer.from(buf) : Buffer.from([buf]);
37
+ let i = 0;
38
+ while (i < b.length - 1 && b[i] === 0) i++; // strip leading zeros
39
+ b = b.subarray(i);
40
+ if (b[0] & 0x80) b = Buffer.concat([Buffer.from([0]), b]); // keep positive
41
+ return tlv(0x02, b);
42
+ }
43
+
44
+ function oid(dotted) {
45
+ const parts = dotted.split('.').map(Number);
46
+ const out = [40 * parts[0] + parts[1]];
47
+ for (let i = 2; i < parts.length; i++) {
48
+ let v = parts[i];
49
+ const group = [v & 0x7f];
50
+ v = Math.floor(v / 128);
51
+ while (v > 0) { group.unshift((v & 0x7f) | 0x80); v = Math.floor(v / 128); }
52
+ out.push(...group);
53
+ }
54
+ return tlv(0x06, Buffer.from(out));
55
+ }
56
+
57
+ function utcTime(date) {
58
+ const z = (n) => String(n).padStart(2, '0');
59
+ const s = `${z(date.getUTCFullYear() % 100)}${z(date.getUTCMonth() + 1)}${z(date.getUTCDate())}` +
60
+ `${z(date.getUTCHours())}${z(date.getUTCMinutes())}${z(date.getUTCSeconds())}Z`;
61
+ return tlv(0x17, Buffer.from(s, 'ascii'));
62
+ }
63
+
64
+ function pem(der, label) {
65
+ const b64 = der.toString('base64').replace(/(.{64})/g, '$1\n').replace(/\n$/, '');
66
+ return `-----BEGIN ${label}-----\n${b64}\n-----END ${label}-----\n`;
67
+ }
68
+
69
+ // ── cert pieces ───────────────────────────────────────────────
70
+
71
+ const SIG_ALG = seq([oid('1.2.840.113549.1.1.11'), NULL]); // sha256WithRSAEncryption
72
+
73
+ function nameCN(cn) {
74
+ return seq([set([seq([oid('2.5.4.3'), utf8(cn)])])]); // RDNSequence with one CN
75
+ }
76
+
77
+ function ext(extOid, critical, valueDer) {
78
+ const items = [oid(extOid)];
79
+ if (critical) items.push(bool(true));
80
+ items.push(octet(valueDer));
81
+ return seq(items);
82
+ }
83
+
84
+ // keyUsage BIT STRING from named bit positions (bit 0 = MSB of first byte).
85
+ function keyUsage(bits) {
86
+ const max = Math.max(...bits);
87
+ const nbytes = Math.floor(max / 8) + 1;
88
+ const bytes = Buffer.alloc(nbytes);
89
+ for (const b of bits) bytes[Math.floor(b / 8)] |= 0x80 >> (b % 8);
90
+ const unused = nbytes * 8 - (max + 1);
91
+ return tlv(0x03, Buffer.concat([Buffer.from([unused]), bytes]));
92
+ }
93
+
94
+ function buildCert({ subjectCN, issuerCN, spkiDer, signKey, isCA, altDnsNames = [], days }) {
95
+ const now = new Date();
96
+ const notBefore = new Date(now.getTime() - 60 * 60 * 1000); // 1h back for clock skew
97
+ const notAfter = new Date(now.getTime() + days * 24 * 60 * 60 * 1000);
98
+
99
+ const extList = [];
100
+ extList.push(ext('2.5.29.19', true, isCA ? seq([bool(true)]) : seq([]))); // basicConstraints
101
+ extList.push(ext('2.5.29.15', true, isCA
102
+ ? keyUsage([0, 5, 6]) // digitalSignature, keyCertSign, cRLSign
103
+ : keyUsage([0, 2]))); // digitalSignature, keyEncipherment
104
+ if (!isCA) {
105
+ extList.push(ext('2.5.29.37', false, seq([oid('1.3.6.1.5.5.7.3.1')]))); // extKeyUsage serverAuth
106
+ if (altDnsNames.length) {
107
+ extList.push(ext('2.5.29.17', false, seq(altDnsNames.map((d) => ctxPrim(2, Buffer.from(d)))))); // SAN dNSName
108
+ }
109
+ }
110
+
111
+ const tbs = seq([
112
+ explicit(0, integer(Buffer.from([2]))), // version v3
113
+ integer(randomBytes(16)), // serial
114
+ SIG_ALG,
115
+ nameCN(issuerCN),
116
+ seq([utcTime(notBefore), utcTime(notAfter)]),
117
+ nameCN(subjectCN),
118
+ spkiDer, // SubjectPublicKeyInfo (already DER)
119
+ explicit(3, seq(extList)),
120
+ ]);
121
+
122
+ const signature = cryptoSign('sha256', tbs, signKey); // RSASSA-PKCS1-v1_5
123
+ return pem(seq([tbs, SIG_ALG, bitString(signature)]), 'CERTIFICATE');
124
+ }
125
+
126
+ // ── public API ────────────────────────────────────────────────
127
+
128
+ function newRsaKey() {
129
+ const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
130
+ return {
131
+ privateKey,
132
+ keyPem: privateKey.export({ type: 'pkcs8', format: 'pem' }),
133
+ spkiDer: publicKey.export({ type: 'spki', format: 'der' }),
134
+ };
135
+ }
136
+
137
+ export function createCA(cn = 'TeamClaude Local CA') {
138
+ const key = newRsaKey();
139
+ const certPem = buildCert({
140
+ subjectCN: cn, issuerCN: cn, spkiDer: key.spkiDer, signKey: key.privateKey,
141
+ isCA: true, days: 3650,
142
+ });
143
+ return { cn, certPem, keyPem: key.keyPem, privateKey: key.privateKey };
144
+ }
145
+
146
+ export function createLeaf(hosts, ca) {
147
+ const list = Array.isArray(hosts) ? hosts : [hosts];
148
+ const key = newRsaKey();
149
+ const certPem = buildCert({
150
+ subjectCN: list[0], issuerCN: ca.cn, spkiDer: key.spkiDer, signKey: ca.privateKey,
151
+ isCA: false, altDnsNames: list, days: 825,
152
+ });
153
+ return { certPem, keyPem: key.keyPem };
154
+ }
155
+
156
+ /** Generate a fresh CA + a leaf covering `hosts` (string or array). Returns PEM strings. */
157
+ export function generateCertChain(hosts) {
158
+ const ca = createCA();
159
+ const leaf = createLeaf(hosts, ca);
160
+ return {
161
+ caCertPem: ca.certPem,
162
+ caKeyPem: ca.keyPem,
163
+ leafCertPem: leaf.certPem,
164
+ leafKeyPem: leaf.keyPem,
165
+ };
166
+ }