recker 1.0.79 → 1.0.80-next.70ea84d
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/browser/browser/mini.d.ts +2 -2
- package/dist/browser/browser/recker-mini.d.ts +8 -8
- package/dist/browser/browser/recker.d.ts +11 -8
- package/dist/browser/browser/recker.js +54 -6
- package/dist/browser/core/client.d.ts +15 -10
- package/dist/browser/core/client.js +54 -38
- package/dist/browser/index.iife.min.js +129 -130
- package/dist/browser/index.min.js +129 -130
- package/dist/browser/index.mini.iife.js +5697 -636
- package/dist/browser/index.mini.iife.min.js +47 -48
- package/dist/browser/index.mini.min.js +47 -48
- package/dist/browser/index.mini.umd.js +5697 -636
- package/dist/browser/index.mini.umd.min.js +47 -48
- package/dist/browser/index.umd.min.js +129 -130
- package/dist/browser/mini.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.js +6 -28
- package/dist/browser/recker-mini.d.ts +8 -8
- package/dist/browser/recker.d.ts +11 -8
- package/dist/browser/recker.js +54 -6
- package/dist/browser/scrape/document.js +2 -2
- package/dist/browser/scrape/element.js +7 -1
- package/dist/browser/scrape/parser/nodes/html.js +1 -1
- package/dist/browser/scrape/spider.d.ts +52 -0
- package/dist/browser/scrape/spider.js +620 -38
- package/dist/browser/scrape/types.d.ts +2 -0
- package/dist/browser/search/google.d.ts +26 -1
- package/dist/browser/search/google.js +427 -45
- package/dist/browser/seo/analyzer.d.ts +1 -0
- package/dist/browser/seo/analyzer.js +144 -1
- package/dist/browser/seo/index.d.ts +1 -1
- package/dist/browser/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/browser/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/browser/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/browser/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/browser/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/browser/seo/keyword-campaign-shared.js +59 -0
- package/dist/browser/seo/keyword-campaign.d.ts +4 -107
- package/dist/browser/seo/keyword-campaign.js +2 -380
- package/dist/browser/seo/keywords.js +5 -22
- package/dist/browser/seo/types.d.ts +19 -0
- package/dist/browser/transport/curl.d.ts +5 -1
- package/dist/browser/transport/curl.js +207 -50
- package/dist/browser/transport/undici.d.ts +4 -3
- package/dist/browser/transport/undici.js +123 -49
- package/dist/browser/types/index.d.ts +9 -3
- package/dist/browser/utils/binary-manager.js +26 -3
- package/dist/browser/utils/block-detector.d.ts +8 -0
- package/dist/browser/utils/block-detector.js +542 -7
- package/dist/cli/commands/hls-runner.js +5 -4
- package/dist/cli/commands/live-runner.js +5 -4
- package/dist/cli/commands/loadtest-runner.js +3 -2
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.js +105 -0
- package/dist/cli/commands/seo-runner.js +9 -7
- package/dist/cli/commands/seo.js +140 -1
- package/dist/cli/commands/serve.js +75 -131
- package/dist/cli/commands/server-runner.js +59 -82
- package/dist/cli/commands/spider-runner.d.ts +37 -1
- package/dist/cli/commands/spider-runner.js +134 -10
- package/dist/cli/commands/spider.d.ts +18 -1
- package/dist/cli/commands/spider.js +457 -27
- package/dist/cli/events/handlers/cli.js +30 -1
- package/dist/cli/events/handlers/tui.js +26 -0
- package/dist/cli/events/types.d.ts +27 -0
- package/dist/cli/handler.d.ts +2 -12
- package/dist/cli/handler.js +20 -15
- package/dist/cli/handlers/protocols.js +39 -12
- package/dist/cli/handlers/search.d.ts +2 -0
- package/dist/cli/handlers/search.js +171 -0
- package/dist/cli/handlers/seo-analyze.d.ts +1 -0
- package/dist/cli/handlers/seo-analyze.js +666 -0
- package/dist/cli/handlers/seo-robots.d.ts +1 -0
- package/dist/cli/handlers/seo-robots.js +76 -0
- package/dist/cli/handlers/seo-serp.d.ts +54 -0
- package/dist/cli/handlers/seo-serp.js +243 -0
- package/dist/cli/handlers/seo-sitemap.d.ts +1 -0
- package/dist/cli/handlers/seo-sitemap.js +55 -0
- package/dist/cli/handlers/seo-spider.d.ts +1 -0
- package/dist/cli/handlers/seo-spider.js +334 -0
- package/dist/cli/handlers/seo.d.ts +8 -4
- package/dist/cli/handlers/seo.js +294 -442
- package/dist/cli/handlers/spider.js +94 -17
- package/dist/cli/handlers/streaming.js +5 -1
- package/dist/cli/index.js +11 -2
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +15 -4
- package/dist/cli/tui/app.js +6 -1
- package/dist/cli/tui/components/rich-response.d.ts +72 -0
- package/dist/cli/tui/components/rich-response.js +117 -0
- package/dist/cli/tui/executor-commands/background.js +30 -24
- package/dist/cli/tui/executor-commands/testing.js +3 -2
- package/dist/cli/tui/hooks/useDomains.d.ts +17 -0
- package/dist/cli/tui/hooks/useHelp.js +15 -2
- package/dist/cli/tui/job-manager.d.ts +4 -4
- package/dist/cli/tui/job-manager.js +5 -1
- package/dist/cli/tui/spider-tui.d.ts +63 -0
- package/dist/cli/tui/spider-tui.js +120 -2
- package/dist/cli/types.d.ts +12 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utils/option-helpers.d.ts +10 -0
- package/dist/cli/utils/option-helpers.js +63 -0
- package/dist/cli/utils/score-color.d.ts +11 -0
- package/dist/cli/utils/score-color.js +11 -0
- package/dist/cli/utils/serp-campaign.d.ts +53 -0
- package/dist/cli/utils/serp-campaign.js +53 -0
- package/dist/cli/utils/serp-config.d.ts +26 -0
- package/dist/cli/utils/serp-config.js +125 -0
- package/dist/core/client.d.ts +15 -10
- package/dist/core/client.js +54 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +35 -34
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/client.js +20 -2
- package/dist/mcp/contract.d.ts +1 -1
- package/dist/mcp/profiles.js +5 -1
- package/dist/mcp/prompts/index.js +8 -4
- package/dist/mcp/resources/index.js +46 -23
- package/dist/mcp/server.js +9 -6
- package/dist/mcp/tools/protocols.js +9 -2
- package/dist/mcp/tools/registry.js +13 -3
- package/dist/mcp/tools/seo.js +427 -2
- package/dist/mcp/types.d.ts +5 -1
- package/dist/plugins/proxy-rotator.d.ts +2 -2
- package/dist/plugins/proxy-rotator.js +6 -28
- package/dist/raffel/client.d.ts +38 -0
- package/dist/raffel/client.js +282 -0
- package/dist/raffel/index.d.ts +2 -0
- package/dist/raffel/index.js +2 -0
- package/dist/raffel/types.d.ts +40 -0
- package/dist/raffel/types.js +14 -0
- package/dist/recker.d.ts +13 -7
- package/dist/recker.js +58 -6
- package/dist/scrape/document.js +2 -2
- package/dist/scrape/element.js +7 -1
- package/dist/scrape/parser/nodes/html.js +1 -1
- package/dist/scrape/spider.d.ts +52 -0
- package/dist/scrape/spider.js +620 -38
- package/dist/scrape/types.d.ts +2 -0
- package/dist/search/google.d.ts +26 -1
- package/dist/search/google.js +427 -45
- package/dist/search/index.d.ts +1 -1
- package/dist/seo/analyzer.d.ts +1 -0
- package/dist/seo/analyzer.js +144 -1
- package/dist/seo/index.d.ts +1 -1
- package/dist/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/seo/keyword-campaign-shared.js +59 -0
- package/dist/seo/keyword-campaign.d.ts +4 -107
- package/dist/seo/keyword-campaign.js +2 -380
- package/dist/seo/keywords.js +5 -22
- package/dist/seo/types.d.ts +19 -0
- package/dist/template/index.d.ts +1 -1
- package/dist/template/types.d.ts +0 -2
- package/dist/testing/index.d.ts +1 -22
- package/dist/testing/index.js +1 -11
- package/dist/transport/curl.d.ts +5 -1
- package/dist/transport/curl.js +207 -50
- package/dist/transport/undici.d.ts +4 -3
- package/dist/transport/undici.js +123 -49
- package/dist/types/index.d.ts +9 -3
- package/dist/utils/binary-manager.js +26 -3
- package/dist/utils/block-detector.d.ts +8 -0
- package/dist/utils/block-detector.js +542 -7
- package/dist/version.js +1 -1
- package/package.json +12 -1
- package/dist/testing/mock-dns-server.d.ts +0 -69
- package/dist/testing/mock-dns-server.js +0 -269
- package/dist/testing/mock-ftp-server.d.ts +0 -89
- package/dist/testing/mock-ftp-server.js +0 -562
- package/dist/testing/mock-hls-server.d.ts +0 -80
- package/dist/testing/mock-hls-server.js +0 -381
- package/dist/testing/mock-http-server.d.ts +0 -124
- package/dist/testing/mock-http-server.js +0 -343
- package/dist/testing/mock-proxy-server.d.ts +0 -108
- package/dist/testing/mock-proxy-server.js +0 -615
- package/dist/testing/mock-sse-server.d.ts +0 -76
- package/dist/testing/mock-sse-server.js +0 -291
- package/dist/testing/mock-telnet-server.d.ts +0 -59
- package/dist/testing/mock-telnet-server.js +0 -274
- package/dist/testing/mock-udp-server.d.ts +0 -43
- package/dist/testing/mock-udp-server.js +0 -188
- package/dist/testing/mock-websocket-server.d.ts +0 -76
- package/dist/testing/mock-websocket-server.js +0 -334
- package/dist/testing/mock-whois-server.d.ts +0 -56
- package/dist/testing/mock-whois-server.js +0 -234
- package/dist/testing/proxy-certs.d.ts +0 -19
- package/dist/testing/proxy-certs.js +0 -208
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'node:events';
|
|
2
|
-
import * as net from 'node:net';
|
|
3
|
-
export class MockWhoisServer extends EventEmitter {
|
|
4
|
-
options;
|
|
5
|
-
server = null;
|
|
6
|
-
domains = new Map();
|
|
7
|
-
started = false;
|
|
8
|
-
stats = {
|
|
9
|
-
queriesReceived: 0,
|
|
10
|
-
responseSent: 0,
|
|
11
|
-
queryLog: [],
|
|
12
|
-
};
|
|
13
|
-
constructor(options = {}) {
|
|
14
|
-
super();
|
|
15
|
-
this.options = {
|
|
16
|
-
port: 4343,
|
|
17
|
-
host: '127.0.0.1',
|
|
18
|
-
delay: 0,
|
|
19
|
-
...options,
|
|
20
|
-
};
|
|
21
|
-
this.addDefaultDomains();
|
|
22
|
-
}
|
|
23
|
-
get isRunning() {
|
|
24
|
-
return this.started;
|
|
25
|
-
}
|
|
26
|
-
get port() {
|
|
27
|
-
return this.options.port;
|
|
28
|
-
}
|
|
29
|
-
get host() {
|
|
30
|
-
return this.options.host;
|
|
31
|
-
}
|
|
32
|
-
get url() {
|
|
33
|
-
return `${this.options.host}:${this.options.port}`;
|
|
34
|
-
}
|
|
35
|
-
get statistics() {
|
|
36
|
-
return { ...this.stats };
|
|
37
|
-
}
|
|
38
|
-
addDomain(domain, data) {
|
|
39
|
-
this.domains.set(domain.toLowerCase(), data);
|
|
40
|
-
}
|
|
41
|
-
removeDomain(domain) {
|
|
42
|
-
this.domains.delete(domain.toLowerCase());
|
|
43
|
-
}
|
|
44
|
-
getDomain(domain) {
|
|
45
|
-
return this.domains.get(domain.toLowerCase());
|
|
46
|
-
}
|
|
47
|
-
clearDomains() {
|
|
48
|
-
this.domains.clear();
|
|
49
|
-
this.addDefaultDomains();
|
|
50
|
-
}
|
|
51
|
-
addDefaultDomains() {
|
|
52
|
-
this.addDomain('example.com', {
|
|
53
|
-
registrar: 'RESERVED-Internet Assigned Numbers Authority',
|
|
54
|
-
registrarUrl: 'http://www.iana.org',
|
|
55
|
-
createdDate: '1995-08-14T04:00:00Z',
|
|
56
|
-
updatedDate: '2023-08-14T07:01:38Z',
|
|
57
|
-
expiryDate: '2024-08-13T04:00:00Z',
|
|
58
|
-
status: ['client delete prohibited', 'client transfer prohibited', 'client update prohibited'],
|
|
59
|
-
nameservers: ['a.iana-servers.net', 'b.iana-servers.net'],
|
|
60
|
-
dnssec: 'signedDelegation',
|
|
61
|
-
});
|
|
62
|
-
this.addDomain('google.com', {
|
|
63
|
-
registrar: 'MarkMonitor Inc.',
|
|
64
|
-
registrarUrl: 'http://www.markmonitor.com',
|
|
65
|
-
createdDate: '1997-09-15T04:00:00Z',
|
|
66
|
-
updatedDate: '2019-09-09T15:39:04Z',
|
|
67
|
-
expiryDate: '2028-09-14T04:00:00Z',
|
|
68
|
-
status: ['client delete prohibited', 'client transfer prohibited', 'client update prohibited', 'server delete prohibited', 'server transfer prohibited', 'server update prohibited'],
|
|
69
|
-
nameservers: ['ns1.google.com', 'ns2.google.com', 'ns3.google.com', 'ns4.google.com'],
|
|
70
|
-
registrantOrg: 'Google LLC',
|
|
71
|
-
registrantEmail: 'Select Request Email Form at https://domains.markmonitor.com/whois/google.com',
|
|
72
|
-
dnssec: 'unsigned',
|
|
73
|
-
});
|
|
74
|
-
this.addDomain('test.local', {
|
|
75
|
-
registrar: 'Mock Registrar',
|
|
76
|
-
registrarUrl: 'http://localhost',
|
|
77
|
-
createdDate: '2020-01-01T00:00:00Z',
|
|
78
|
-
updatedDate: '2024-01-01T00:00:00Z',
|
|
79
|
-
expiryDate: '2030-01-01T00:00:00Z',
|
|
80
|
-
status: ['ok'],
|
|
81
|
-
nameservers: ['ns1.test.local', 'ns2.test.local'],
|
|
82
|
-
registrantName: 'Test User',
|
|
83
|
-
registrantOrg: 'Test Organization',
|
|
84
|
-
registrantEmail: 'admin@test.local',
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
async start() {
|
|
88
|
-
if (this.started) {
|
|
89
|
-
throw new Error('Server already started');
|
|
90
|
-
}
|
|
91
|
-
return new Promise((resolve, reject) => {
|
|
92
|
-
this.server = net.createServer((socket) => {
|
|
93
|
-
this.handleConnection(socket);
|
|
94
|
-
});
|
|
95
|
-
this.server.on('error', (err) => {
|
|
96
|
-
this.emit('error', err);
|
|
97
|
-
if (!this.started) {
|
|
98
|
-
reject(err);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
this.server.listen(this.options.port, this.options.host, () => {
|
|
102
|
-
this.started = true;
|
|
103
|
-
this.emit('start');
|
|
104
|
-
resolve();
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
async stop() {
|
|
109
|
-
if (!this.started || !this.server)
|
|
110
|
-
return;
|
|
111
|
-
return new Promise((resolve) => {
|
|
112
|
-
this.server.close(() => {
|
|
113
|
-
this.server = null;
|
|
114
|
-
this.started = false;
|
|
115
|
-
this.emit('stop');
|
|
116
|
-
resolve();
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
reset() {
|
|
121
|
-
this.stats = {
|
|
122
|
-
queriesReceived: 0,
|
|
123
|
-
responseSent: 0,
|
|
124
|
-
queryLog: [],
|
|
125
|
-
};
|
|
126
|
-
this.clearDomains();
|
|
127
|
-
this.emit('reset');
|
|
128
|
-
}
|
|
129
|
-
handleConnection(socket) {
|
|
130
|
-
let data = '';
|
|
131
|
-
socket.on('data', (chunk) => {
|
|
132
|
-
data += chunk.toString();
|
|
133
|
-
if (data.includes('\n') || data.includes('\r')) {
|
|
134
|
-
this.handleQuery(data.trim(), socket);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
socket.on('error', (err) => {
|
|
138
|
-
this.emit('error', err);
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
async handleQuery(query, socket) {
|
|
142
|
-
this.stats.queriesReceived++;
|
|
143
|
-
this.stats.queryLog.push({ query, timestamp: Date.now() });
|
|
144
|
-
this.emit('query', query);
|
|
145
|
-
if (this.options.delay > 0) {
|
|
146
|
-
await new Promise((resolve) => setTimeout(resolve, this.options.delay));
|
|
147
|
-
}
|
|
148
|
-
const response = this.buildResponse(query);
|
|
149
|
-
socket.write(response, () => {
|
|
150
|
-
this.stats.responseSent++;
|
|
151
|
-
this.emit('response', query);
|
|
152
|
-
socket.end();
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
buildResponse(query) {
|
|
156
|
-
const domain = query.toLowerCase().trim();
|
|
157
|
-
const data = this.domains.get(domain);
|
|
158
|
-
if (!data) {
|
|
159
|
-
return this.buildNotFoundResponse(domain);
|
|
160
|
-
}
|
|
161
|
-
return this.buildDomainResponse(domain, data);
|
|
162
|
-
}
|
|
163
|
-
buildNotFoundResponse(domain) {
|
|
164
|
-
return `
|
|
165
|
-
% WHOIS Mock Server
|
|
166
|
-
|
|
167
|
-
No match for domain "${domain}".
|
|
168
|
-
|
|
169
|
-
>>> Last update of WHOIS database: ${new Date().toISOString()} <<<
|
|
170
|
-
|
|
171
|
-
NOTICE: This is a mock WHOIS server for testing purposes.
|
|
172
|
-
`.trim() + '\n';
|
|
173
|
-
}
|
|
174
|
-
buildDomainResponse(domain, data) {
|
|
175
|
-
const lines = [
|
|
176
|
-
'% WHOIS Mock Server',
|
|
177
|
-
'',
|
|
178
|
-
`Domain Name: ${domain.toUpperCase()}`,
|
|
179
|
-
];
|
|
180
|
-
if (data.registrar) {
|
|
181
|
-
lines.push(`Registrar: ${data.registrar}`);
|
|
182
|
-
}
|
|
183
|
-
if (data.registrarUrl) {
|
|
184
|
-
lines.push(`Registrar URL: ${data.registrarUrl}`);
|
|
185
|
-
}
|
|
186
|
-
if (data.createdDate) {
|
|
187
|
-
lines.push(`Creation Date: ${data.createdDate}`);
|
|
188
|
-
}
|
|
189
|
-
if (data.updatedDate) {
|
|
190
|
-
lines.push(`Updated Date: ${data.updatedDate}`);
|
|
191
|
-
}
|
|
192
|
-
if (data.expiryDate) {
|
|
193
|
-
lines.push(`Registry Expiry Date: ${data.expiryDate}`);
|
|
194
|
-
}
|
|
195
|
-
if (data.status && data.status.length > 0) {
|
|
196
|
-
for (const status of data.status) {
|
|
197
|
-
lines.push(`Domain Status: ${status}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (data.nameservers && data.nameservers.length > 0) {
|
|
201
|
-
for (const ns of data.nameservers) {
|
|
202
|
-
lines.push(`Name Server: ${ns}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (data.registrantName) {
|
|
206
|
-
lines.push(`Registrant Name: ${data.registrantName}`);
|
|
207
|
-
}
|
|
208
|
-
if (data.registrantOrg) {
|
|
209
|
-
lines.push(`Registrant Organization: ${data.registrantOrg}`);
|
|
210
|
-
}
|
|
211
|
-
if (data.registrantEmail) {
|
|
212
|
-
lines.push(`Registrant Email: ${data.registrantEmail}`);
|
|
213
|
-
}
|
|
214
|
-
if (data.adminEmail) {
|
|
215
|
-
lines.push(`Admin Email: ${data.adminEmail}`);
|
|
216
|
-
}
|
|
217
|
-
if (data.techEmail) {
|
|
218
|
-
lines.push(`Tech Email: ${data.techEmail}`);
|
|
219
|
-
}
|
|
220
|
-
if (data.dnssec) {
|
|
221
|
-
lines.push(`DNSSEC: ${data.dnssec}`);
|
|
222
|
-
}
|
|
223
|
-
lines.push('');
|
|
224
|
-
lines.push(`>>> Last update of WHOIS database: ${new Date().toISOString()} <<<`);
|
|
225
|
-
lines.push('');
|
|
226
|
-
lines.push('NOTICE: This is a mock WHOIS server for testing purposes.');
|
|
227
|
-
return lines.join('\n') + '\n';
|
|
228
|
-
}
|
|
229
|
-
static async create(options = {}) {
|
|
230
|
-
const server = new MockWhoisServer(options);
|
|
231
|
-
await server.start();
|
|
232
|
-
return server;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export interface CertificateInfo {
|
|
2
|
-
key: string;
|
|
3
|
-
cert: string;
|
|
4
|
-
ca?: string;
|
|
5
|
-
}
|
|
6
|
-
export interface CertificateOptions {
|
|
7
|
-
caCert?: string;
|
|
8
|
-
caKey?: string;
|
|
9
|
-
validityDays?: number;
|
|
10
|
-
}
|
|
11
|
-
export declare function generateCA(): {
|
|
12
|
-
key: string;
|
|
13
|
-
cert: string;
|
|
14
|
-
};
|
|
15
|
-
export declare function generateCertificate(host: string, options?: CertificateOptions): Promise<CertificateInfo>;
|
|
16
|
-
export declare function getDefaultCA(): {
|
|
17
|
-
key: string;
|
|
18
|
-
cert: string;
|
|
19
|
-
};
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { generateKeyPairSync, createSign, randomBytes } from 'node:crypto';
|
|
2
|
-
let defaultCA = null;
|
|
3
|
-
export function generateCA() {
|
|
4
|
-
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
|
|
5
|
-
modulusLength: 2048,
|
|
6
|
-
});
|
|
7
|
-
const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
|
|
8
|
-
const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
|
|
9
|
-
const cert = createSelfSignedCert({
|
|
10
|
-
commonName: 'Recker Proxy CA',
|
|
11
|
-
isCA: true,
|
|
12
|
-
privateKey: privateKeyPem,
|
|
13
|
-
publicKey: publicKeyPem,
|
|
14
|
-
validityDays: 3650,
|
|
15
|
-
});
|
|
16
|
-
return {
|
|
17
|
-
key: privateKeyPem,
|
|
18
|
-
cert,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
export async function generateCertificate(host, options = {}) {
|
|
22
|
-
let caKey = options.caKey;
|
|
23
|
-
let caCert = options.caCert;
|
|
24
|
-
if (!caKey || !caCert) {
|
|
25
|
-
if (!defaultCA) {
|
|
26
|
-
defaultCA = generateCA();
|
|
27
|
-
}
|
|
28
|
-
caKey = defaultCA.key;
|
|
29
|
-
caCert = defaultCA.cert;
|
|
30
|
-
}
|
|
31
|
-
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
|
|
32
|
-
modulusLength: 2048,
|
|
33
|
-
});
|
|
34
|
-
const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
|
|
35
|
-
const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
|
|
36
|
-
const cert = createSignedCert({
|
|
37
|
-
commonName: host,
|
|
38
|
-
sans: [host],
|
|
39
|
-
privateKey: privateKeyPem,
|
|
40
|
-
publicKey: publicKeyPem,
|
|
41
|
-
caKey,
|
|
42
|
-
caCert,
|
|
43
|
-
validityDays: options.validityDays ?? 365,
|
|
44
|
-
});
|
|
45
|
-
return {
|
|
46
|
-
key: privateKeyPem,
|
|
47
|
-
cert,
|
|
48
|
-
ca: caCert,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function createSelfSignedCert(options) {
|
|
52
|
-
const now = new Date();
|
|
53
|
-
const notBefore = formatDate(now);
|
|
54
|
-
const notAfter = formatDate(new Date(now.getTime() + options.validityDays * 24 * 60 * 60 * 1000));
|
|
55
|
-
const serialNumber = randomBytes(8).toString('hex').toUpperCase();
|
|
56
|
-
const tbsCertificate = buildTBSCertificate({
|
|
57
|
-
serialNumber,
|
|
58
|
-
issuer: { CN: options.commonName },
|
|
59
|
-
subject: { CN: options.commonName },
|
|
60
|
-
notBefore,
|
|
61
|
-
notAfter,
|
|
62
|
-
publicKey: options.publicKey,
|
|
63
|
-
isCA: options.isCA,
|
|
64
|
-
sans: [],
|
|
65
|
-
});
|
|
66
|
-
const signature = signData(tbsCertificate, options.privateKey);
|
|
67
|
-
return buildCertificate(tbsCertificate, signature);
|
|
68
|
-
}
|
|
69
|
-
function createSignedCert(options) {
|
|
70
|
-
const now = new Date();
|
|
71
|
-
const notBefore = formatDate(now);
|
|
72
|
-
const notAfter = formatDate(new Date(now.getTime() + options.validityDays * 24 * 60 * 60 * 1000));
|
|
73
|
-
const serialNumber = randomBytes(8).toString('hex').toUpperCase();
|
|
74
|
-
const issuerCN = extractCN(options.caCert);
|
|
75
|
-
const tbsCertificate = buildTBSCertificate({
|
|
76
|
-
serialNumber,
|
|
77
|
-
issuer: { CN: issuerCN },
|
|
78
|
-
subject: { CN: options.commonName },
|
|
79
|
-
notBefore,
|
|
80
|
-
notAfter,
|
|
81
|
-
publicKey: options.publicKey,
|
|
82
|
-
isCA: false,
|
|
83
|
-
sans: options.sans,
|
|
84
|
-
});
|
|
85
|
-
const signature = signData(tbsCertificate, options.caKey);
|
|
86
|
-
return buildCertificate(tbsCertificate, signature);
|
|
87
|
-
}
|
|
88
|
-
function buildTBSCertificate(options) {
|
|
89
|
-
const parts = [];
|
|
90
|
-
parts.push(wrapTag(0xa0, wrapTag(0x02, Buffer.from([0x02]))));
|
|
91
|
-
const serialBytes = Buffer.from(options.serialNumber, 'hex');
|
|
92
|
-
parts.push(wrapTag(0x02, serialBytes));
|
|
93
|
-
parts.push(buildAlgorithmIdentifier());
|
|
94
|
-
parts.push(buildName(options.issuer.CN));
|
|
95
|
-
parts.push(buildValidity(options.notBefore, options.notAfter));
|
|
96
|
-
parts.push(buildName(options.subject.CN));
|
|
97
|
-
parts.push(extractPublicKeyInfo(options.publicKey));
|
|
98
|
-
const extensions = [];
|
|
99
|
-
if (options.isCA) {
|
|
100
|
-
extensions.push(buildBasicConstraintsCA());
|
|
101
|
-
}
|
|
102
|
-
if (options.sans.length > 0) {
|
|
103
|
-
extensions.push(buildSANExtension(options.sans));
|
|
104
|
-
}
|
|
105
|
-
if (extensions.length > 0) {
|
|
106
|
-
const extsSeq = wrapTag(0x30, Buffer.concat(extensions));
|
|
107
|
-
parts.push(wrapTag(0xa3, extsSeq));
|
|
108
|
-
}
|
|
109
|
-
return wrapTag(0x30, Buffer.concat(parts));
|
|
110
|
-
}
|
|
111
|
-
function buildCertificate(tbsCertificate, signature) {
|
|
112
|
-
const parts = [];
|
|
113
|
-
parts.push(tbsCertificate);
|
|
114
|
-
parts.push(buildAlgorithmIdentifier());
|
|
115
|
-
const sigBitString = Buffer.concat([Buffer.from([0x00]), signature]);
|
|
116
|
-
parts.push(wrapTag(0x03, sigBitString));
|
|
117
|
-
const certDer = wrapTag(0x30, Buffer.concat(parts));
|
|
118
|
-
const certBase64 = certDer.toString('base64');
|
|
119
|
-
const lines = ['-----BEGIN CERTIFICATE-----'];
|
|
120
|
-
for (let i = 0; i < certBase64.length; i += 64) {
|
|
121
|
-
lines.push(certBase64.slice(i, i + 64));
|
|
122
|
-
}
|
|
123
|
-
lines.push('-----END CERTIFICATE-----');
|
|
124
|
-
return lines.join('\n');
|
|
125
|
-
}
|
|
126
|
-
function buildAlgorithmIdentifier() {
|
|
127
|
-
const oid = Buffer.from([
|
|
128
|
-
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b,
|
|
129
|
-
]);
|
|
130
|
-
const nullParam = Buffer.from([0x05, 0x00]);
|
|
131
|
-
return wrapTag(0x30, Buffer.concat([oid, nullParam]));
|
|
132
|
-
}
|
|
133
|
-
function buildName(cn) {
|
|
134
|
-
const cnOid = Buffer.from([0x06, 0x03, 0x55, 0x04, 0x03]);
|
|
135
|
-
const cnValue = wrapTag(0x0c, Buffer.from(cn, 'utf8'));
|
|
136
|
-
const atv = wrapTag(0x30, Buffer.concat([cnOid, cnValue]));
|
|
137
|
-
const rdn = wrapTag(0x31, atv);
|
|
138
|
-
return wrapTag(0x30, rdn);
|
|
139
|
-
}
|
|
140
|
-
function buildValidity(notBefore, notAfter) {
|
|
141
|
-
const notBeforeUtc = wrapTag(0x17, Buffer.from(notBefore, 'ascii'));
|
|
142
|
-
const notAfterUtc = wrapTag(0x17, Buffer.from(notAfter, 'ascii'));
|
|
143
|
-
return wrapTag(0x30, Buffer.concat([notBeforeUtc, notAfterUtc]));
|
|
144
|
-
}
|
|
145
|
-
function buildBasicConstraintsCA() {
|
|
146
|
-
const oid = Buffer.from([0x06, 0x03, 0x55, 0x1d, 0x13]);
|
|
147
|
-
const critical = Buffer.from([0x01, 0x01, 0xff]);
|
|
148
|
-
const caTrue = wrapTag(0x01, Buffer.from([0xff]));
|
|
149
|
-
const bcValue = wrapTag(0x30, caTrue);
|
|
150
|
-
const bcOctet = wrapTag(0x04, bcValue);
|
|
151
|
-
return wrapTag(0x30, Buffer.concat([oid, critical, bcOctet]));
|
|
152
|
-
}
|
|
153
|
-
function buildSANExtension(sans) {
|
|
154
|
-
const oid = Buffer.from([0x06, 0x03, 0x55, 0x1d, 0x11]);
|
|
155
|
-
const names = sans.map((name) => {
|
|
156
|
-
return wrapTag(0x82, Buffer.from(name, 'ascii'));
|
|
157
|
-
});
|
|
158
|
-
const sanSeq = wrapTag(0x30, Buffer.concat(names));
|
|
159
|
-
const sanOctet = wrapTag(0x04, sanSeq);
|
|
160
|
-
return wrapTag(0x30, Buffer.concat([oid, sanOctet]));
|
|
161
|
-
}
|
|
162
|
-
function extractPublicKeyInfo(publicKeyPem) {
|
|
163
|
-
const base64 = publicKeyPem
|
|
164
|
-
.replace(/-----BEGIN PUBLIC KEY-----/, '')
|
|
165
|
-
.replace(/-----END PUBLIC KEY-----/, '')
|
|
166
|
-
.replace(/\s/g, '');
|
|
167
|
-
return Buffer.from(base64, 'base64');
|
|
168
|
-
}
|
|
169
|
-
function extractCN(certPem) {
|
|
170
|
-
const match = certPem.match(/CN=([^,\/\n]+)/i);
|
|
171
|
-
return match ? match[1] : 'Unknown';
|
|
172
|
-
}
|
|
173
|
-
function signData(data, privateKeyPem) {
|
|
174
|
-
const sign = createSign('SHA256');
|
|
175
|
-
sign.update(data);
|
|
176
|
-
return sign.sign(privateKeyPem);
|
|
177
|
-
}
|
|
178
|
-
function wrapTag(tag, content) {
|
|
179
|
-
const length = encodeLength(content.length);
|
|
180
|
-
return Buffer.concat([Buffer.from([tag]), length, content]);
|
|
181
|
-
}
|
|
182
|
-
function encodeLength(len) {
|
|
183
|
-
if (len < 128) {
|
|
184
|
-
return Buffer.from([len]);
|
|
185
|
-
}
|
|
186
|
-
const bytes = [];
|
|
187
|
-
let temp = len;
|
|
188
|
-
while (temp > 0) {
|
|
189
|
-
bytes.unshift(temp & 0xff);
|
|
190
|
-
temp >>= 8;
|
|
191
|
-
}
|
|
192
|
-
return Buffer.from([0x80 | bytes.length, ...bytes]);
|
|
193
|
-
}
|
|
194
|
-
function formatDate(date) {
|
|
195
|
-
const y = date.getUTCFullYear().toString().slice(-2);
|
|
196
|
-
const m = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
197
|
-
const d = date.getUTCDate().toString().padStart(2, '0');
|
|
198
|
-
const h = date.getUTCHours().toString().padStart(2, '0');
|
|
199
|
-
const min = date.getUTCMinutes().toString().padStart(2, '0');
|
|
200
|
-
const s = date.getUTCSeconds().toString().padStart(2, '0');
|
|
201
|
-
return `${y}${m}${d}${h}${min}${s}Z`;
|
|
202
|
-
}
|
|
203
|
-
export function getDefaultCA() {
|
|
204
|
-
if (!defaultCA) {
|
|
205
|
-
defaultCA = generateCA();
|
|
206
|
-
}
|
|
207
|
-
return defaultCA;
|
|
208
|
-
}
|