@jsenv/https-local 1.0.2 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/package.json +27 -33
- package/src/certificate_authority.js +68 -29
- package/src/certificate_for_localhost.js +25 -9
- package/src/hosts_file_verif.js +13 -6
- package/src/internal/authority_file_infos.js +9 -3
- package/src/internal/certificate_authority_file_urls.js +21 -5
- package/src/internal/certificate_data_converter.js +6 -2
- package/src/internal/certificate_generator.js +21 -7
- package/src/internal/command.js +2 -1
- package/src/internal/exec.js +4 -1
- package/src/internal/hosts/parse_hosts.js +15 -5
- package/src/internal/hosts/write_hosts.js +23 -11
- package/src/internal/hosts/write_line_hosts.js +28 -12
- package/src/internal/linux/chrome_linux.js +8 -2
- package/src/internal/linux/firefox_linux.js +8 -2
- package/src/internal/linux/linux_trust_store.js +41 -16
- package/src/internal/linux/nss_linux.js +10 -4
- package/src/internal/mac/firefox_mac.js +8 -2
- package/src/internal/mac/mac_keychain.js +29 -12
- package/src/internal/mac/nss_mac.js +14 -6
- package/src/internal/nssdb_browser.js +17 -10
- package/src/internal/search_certificate_in_command_output.js +4 -1
- package/src/internal/unsupported_platform/unsupported_platform.js +3 -1
- package/src/internal/validity_formatting.js +3 -1
- package/src/internal/windows/chrome_windows.js +12 -10
- package/src/internal/windows/firefox_windows.js +12 -10
- package/src/internal/windows/windows_certutil.js +38 -16
- package/src/validity_duration.js +7 -3
package/README.md
CHANGED
|
@@ -31,7 +31,10 @@ npm install --save-dev @jsenv/https-local
|
|
|
31
31
|
* Read more in https://github.com/jsenv/https-local#installCertificateAuthority
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
installCertificateAuthority,
|
|
36
|
+
verifyHostsFile,
|
|
37
|
+
} from "@jsenv/https-local"
|
|
35
38
|
|
|
36
39
|
await installCertificateAuthority({
|
|
37
40
|
tryToTrust: true,
|
|
@@ -39,7 +42,7 @@ await installCertificateAuthority({
|
|
|
39
42
|
})
|
|
40
43
|
await verifyHostsFile({
|
|
41
44
|
ipMappings: {
|
|
42
|
-
"127.0.0.1": ["localhost"
|
|
45
|
+
"127.0.0.1": ["localhost"],
|
|
43
46
|
},
|
|
44
47
|
tryToUpdatesHostsFile: true,
|
|
45
48
|
})
|
|
@@ -70,9 +73,8 @@ node ./install_certificate_authority.mjs
|
|
|
70
73
|
import { createServer } from "node:https"
|
|
71
74
|
import { requestCertificateForLocalhost } from "@jsenv/https-local"
|
|
72
75
|
|
|
73
|
-
const { serverCertificate, serverCertificatePrivateKey } =
|
|
74
|
-
|
|
75
|
-
})
|
|
76
|
+
const { serverCertificate, serverCertificatePrivateKey } =
|
|
77
|
+
await requestCertificateForLocalhost()
|
|
76
78
|
|
|
77
79
|
const server = createServer(
|
|
78
80
|
{
|
|
@@ -367,9 +369,10 @@ _requestCertificateForLocalhost_ function returns a certificate and private key
|
|
|
367
369
|
import { createServer } from "node:https"
|
|
368
370
|
import { requestCertificateForLocalhost } from "@jsenv/https-local"
|
|
369
371
|
|
|
370
|
-
const { serverCertificate, serverCertificatePrivateKey } =
|
|
371
|
-
|
|
372
|
-
|
|
372
|
+
const { serverCertificate, serverCertificatePrivateKey } =
|
|
373
|
+
await requestCertificateForLocalhost({
|
|
374
|
+
serverCertificateAltNames: ["localhost", "local.example"],
|
|
375
|
+
})
|
|
373
376
|
```
|
|
374
377
|
|
|
375
378
|
[installCertificateAuthority](#installCertificateAuthority) must be called before this function.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/https-local",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "A programmatic way to generate locally trusted certificates",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -13,11 +13,10 @@
|
|
|
13
13
|
"url": "https://github.com/jsenv/https-local"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
16
|
+
"node": ">=16.13.0"
|
|
17
17
|
},
|
|
18
18
|
"publishConfig": {
|
|
19
|
-
"access": "public"
|
|
20
|
-
"registry": "https://registry.npmjs.org"
|
|
19
|
+
"access": "public"
|
|
21
20
|
},
|
|
22
21
|
"type": "module",
|
|
23
22
|
"exports": {
|
|
@@ -26,14 +25,17 @@
|
|
|
26
25
|
},
|
|
27
26
|
"./*": "./*"
|
|
28
27
|
},
|
|
28
|
+
"main": "./main.js",
|
|
29
29
|
"files": [
|
|
30
30
|
"/src/",
|
|
31
31
|
"/main.js"
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
|
-
"eslint
|
|
35
|
-
"
|
|
36
|
-
"
|
|
34
|
+
"eslint": "node ./node_modules/eslint/bin/eslint.js . --ext=.js,.mjs",
|
|
35
|
+
"importmap": "node ./script/importmap/importmap.mjs",
|
|
36
|
+
"performances": "node --expose-gc ./script/performance/generate_performance_report.mjs --log",
|
|
37
|
+
"test": "node ./script/test/test.mjs",
|
|
38
|
+
"test-with-coverage": "npm run test -- --coverage",
|
|
37
39
|
"start-node-server": "node ./script/certificate/start_node_server.mjs",
|
|
38
40
|
"log-root-certificate-trust": "node ./script/certificate/log_root_certificate_trust.mjs",
|
|
39
41
|
"trust-root-certificate": "node ./script/certificate/trust_root_certificate.mjs",
|
|
@@ -43,39 +45,31 @@
|
|
|
43
45
|
"remove-localhost-mappings": "node ./script/hosts/remove_localhost_mappings.mjs",
|
|
44
46
|
"verify-localhost-mappings": "node ./script/hosts/verify_localhost_mappings.mjs",
|
|
45
47
|
"ensure-localhost-mappings": "node ./script/hosts/ensure_localhost_mappings.mjs",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"prettier-format": "node ./script/prettier/prettier_format.mjs",
|
|
49
|
-
"prettier-format-stage": "npm run prettier-format -- --staged",
|
|
50
|
-
"prettier-check": "npm run prettier-format -- --dry-run",
|
|
51
|
-
"install-playwright": "npx playwright install-deps && npx playwright install",
|
|
52
|
-
"prepublishOnly": "node ./script/publish/remove_postinstall.mjs",
|
|
53
|
-
"postpublish": "node ./script/publish/restore_postinstall.mjs"
|
|
48
|
+
"prettier": "prettier --write .",
|
|
49
|
+
"playwright-install": "npx playwright install-deps && npx playwright install"
|
|
54
50
|
},
|
|
55
51
|
"dependencies": {
|
|
56
|
-
"@jsenv/filesystem": "2.
|
|
52
|
+
"@jsenv/filesystem": "2.7.1",
|
|
57
53
|
"@jsenv/logger": "4.0.1",
|
|
58
54
|
"command-exists": "1.2.9",
|
|
59
55
|
"is-unicode-supported": "1.1.0",
|
|
60
|
-
"node-forge": "
|
|
56
|
+
"node-forge": "1.2.1",
|
|
61
57
|
"sudo-prompt": "9.2.1",
|
|
62
|
-
"supports-color": "9.
|
|
58
|
+
"supports-color": "9.2.1",
|
|
63
59
|
"which": "2.0.2"
|
|
64
60
|
},
|
|
65
61
|
"devDependencies": {
|
|
66
|
-
"@jsenv/assert": "2.
|
|
67
|
-
"@jsenv/
|
|
68
|
-
"@jsenv/
|
|
69
|
-
"@jsenv/
|
|
70
|
-
"@jsenv/
|
|
71
|
-
"@jsenv/importmap-
|
|
72
|
-
"@jsenv/
|
|
73
|
-
"@jsenv/
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"playwright": "1.14.0",
|
|
79
|
-
"prettier": "2.3.2"
|
|
62
|
+
"@jsenv/assert": "2.4.1",
|
|
63
|
+
"@jsenv/core": "25.3.0",
|
|
64
|
+
"@jsenv/eslint-config": "16.0.9",
|
|
65
|
+
"@jsenv/github-release-package": "1.3.3",
|
|
66
|
+
"@jsenv/importmap-eslint-resolver": "5.2.5",
|
|
67
|
+
"@jsenv/importmap-node-module": "5.1.3",
|
|
68
|
+
"@jsenv/package-publish": "1.7.2",
|
|
69
|
+
"@jsenv/performance-impact": "2.2.4",
|
|
70
|
+
"eslint": "8.7.0",
|
|
71
|
+
"eslint-plugin-import": "2.25.4",
|
|
72
|
+
"playwright": "1.18.0",
|
|
73
|
+
"prettier": "2.5.1"
|
|
80
74
|
}
|
|
81
|
-
}
|
|
75
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
/* eslint-disable import/max-dependencies */
|
|
2
|
-
|
|
3
|
-
import { createLogger, createDetailedMessage } from "@jsenv/logger"
|
|
4
1
|
import { readFile, writeFile, removeFileSystemNode } from "@jsenv/filesystem"
|
|
2
|
+
import { createLogger, createDetailedMessage } from "@jsenv/logger"
|
|
5
3
|
|
|
6
4
|
import { infoSign, okSign } from "./internal/logs.js"
|
|
7
5
|
import { getAuthorityFileInfos } from "./internal/authority_file_infos.js"
|
|
8
6
|
import { attributeDescriptionFromAttributeArray } from "./internal/certificate_data_converter.js"
|
|
9
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
formatTimeDelta,
|
|
9
|
+
formatDuration,
|
|
10
|
+
} from "./internal/validity_formatting.js"
|
|
10
11
|
import { importNodeForge } from "./internal/forge.js"
|
|
11
12
|
import { createAuthorityRootCertificate } from "./internal/certificate_generator.js"
|
|
12
13
|
import { importPlatformMethods } from "./internal/platform.js"
|
|
@@ -54,8 +55,11 @@ export const installCertificateAuthority = async ({
|
|
|
54
55
|
)
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
const {
|
|
58
|
-
|
|
58
|
+
const {
|
|
59
|
+
authorityJsonFileInfo,
|
|
60
|
+
rootCertificateFileInfo,
|
|
61
|
+
rootCertificatePrivateKeyFileInfo,
|
|
62
|
+
} = getAuthorityFileInfos()
|
|
59
63
|
const authorityJsonFileUrl = authorityJsonFileInfo.url
|
|
60
64
|
const rootCertificateFileUrl = rootCertificateFileInfo.url
|
|
61
65
|
const rootPrivateKeyFileUrl = rootCertificatePrivateKeyFileInfo.url
|
|
@@ -76,16 +80,23 @@ export const installCertificateAuthority = async ({
|
|
|
76
80
|
})
|
|
77
81
|
|
|
78
82
|
const { pki } = await importNodeForge()
|
|
79
|
-
const rootCertificate = pemAsFileContent(
|
|
83
|
+
const rootCertificate = pemAsFileContent(
|
|
84
|
+
pki.certificateToPem(rootCertificateForgeObject),
|
|
85
|
+
)
|
|
80
86
|
const rootCertificatePrivateKey = pemAsFileContent(
|
|
81
87
|
pki.privateKeyToPem(rootCertificatePrivateKeyForgeObject),
|
|
82
88
|
)
|
|
83
89
|
|
|
84
90
|
await writeFile(rootCertificateFileUrl, rootCertificate)
|
|
85
91
|
await writeFile(rootPrivateKeyFileUrl, rootCertificatePrivateKey)
|
|
86
|
-
await writeFile(
|
|
92
|
+
await writeFile(
|
|
93
|
+
authorityJsonFileUrl,
|
|
94
|
+
JSON.stringify({ serialNumber: 0 }, null, " "),
|
|
95
|
+
)
|
|
87
96
|
|
|
88
|
-
logger.info(
|
|
97
|
+
logger.info(
|
|
98
|
+
`${okSign} authority root certificate written at ${rootCertificateFileInfo.path}`,
|
|
99
|
+
)
|
|
89
100
|
return {
|
|
90
101
|
rootCertificateForgeObject,
|
|
91
102
|
rootCertificatePrivateKeyForgeObject,
|
|
@@ -140,14 +151,18 @@ export const installCertificateAuthority = async ({
|
|
|
140
151
|
logger.debug(
|
|
141
152
|
`Authority root certificate is not on filesystem at ${rootCertificateFileInfo.path}`,
|
|
142
153
|
)
|
|
143
|
-
logger.info(
|
|
154
|
+
logger.info(
|
|
155
|
+
`${infoSign} authority root certificate not found in filesystem`,
|
|
156
|
+
)
|
|
144
157
|
return generate()
|
|
145
158
|
}
|
|
146
159
|
if (!rootCertificatePrivateKeyFileInfo.exists) {
|
|
147
160
|
logger.debug(
|
|
148
161
|
`Authority root certificate private key is not on filesystem at ${rootCertificatePrivateKeyFileInfo.path}`,
|
|
149
162
|
)
|
|
150
|
-
logger.info(
|
|
163
|
+
logger.info(
|
|
164
|
+
`${infoSign} authority root certificate not found in filesystem`,
|
|
165
|
+
)
|
|
151
166
|
return generate()
|
|
152
167
|
}
|
|
153
168
|
logger.debug(
|
|
@@ -155,18 +170,23 @@ export const installCertificateAuthority = async ({
|
|
|
155
170
|
)
|
|
156
171
|
logger.info(`${okSign} authority root certificate found in filesystem`)
|
|
157
172
|
|
|
158
|
-
const rootCertificate = await readFile(rootCertificateFileInfo.path, {
|
|
173
|
+
const rootCertificate = await readFile(rootCertificateFileInfo.path, {
|
|
174
|
+
as: "string",
|
|
175
|
+
})
|
|
159
176
|
const { pki } = await importNodeForge()
|
|
160
177
|
const rootCertificateForgeObject = pki.certificateFromPem(rootCertificate)
|
|
161
178
|
|
|
162
179
|
logger.info(`Checking certificate validity...`)
|
|
163
|
-
const rootCertificateValidityDurationInMs =
|
|
180
|
+
const rootCertificateValidityDurationInMs =
|
|
181
|
+
getCertificateValidityDurationInMs(rootCertificateForgeObject)
|
|
182
|
+
const rootCertificateValidityRemainingMs = getCertificateRemainingMs(
|
|
164
183
|
rootCertificateForgeObject,
|
|
165
184
|
)
|
|
166
|
-
const rootCertificateValidityRemainingMs = getCertificateRemainingMs(rootCertificateForgeObject)
|
|
167
185
|
if (rootCertificateValidityRemainingMs < 0) {
|
|
168
186
|
logger.info(
|
|
169
|
-
`${infoSign} certificate expired ${formatTimeDelta(
|
|
187
|
+
`${infoSign} certificate expired ${formatTimeDelta(
|
|
188
|
+
rootCertificateValidityRemainingMs,
|
|
189
|
+
)}`,
|
|
170
190
|
)
|
|
171
191
|
return regenerate()
|
|
172
192
|
}
|
|
@@ -174,30 +194,44 @@ export const installCertificateAuthority = async ({
|
|
|
174
194
|
rootCertificateValidityRemainingMs / rootCertificateValidityDurationInMs
|
|
175
195
|
if (rootCertificateValidityRemainingRatio < aboutToExpireRatio) {
|
|
176
196
|
logger.info(
|
|
177
|
-
`${infoSign} certificate will expire ${formatTimeDelta(
|
|
197
|
+
`${infoSign} certificate will expire ${formatTimeDelta(
|
|
198
|
+
rootCertificateValidityRemainingMs,
|
|
199
|
+
)}`,
|
|
178
200
|
)
|
|
179
201
|
return regenerate()
|
|
180
202
|
}
|
|
181
203
|
logger.info(
|
|
182
|
-
`${okSign} certificate still valid for ${formatDuration(
|
|
204
|
+
`${okSign} certificate still valid for ${formatDuration(
|
|
205
|
+
rootCertificateValidityRemainingMs,
|
|
206
|
+
)}`,
|
|
183
207
|
)
|
|
184
208
|
|
|
185
209
|
logger.info(`Detect if certificate attributes have changed...`)
|
|
186
|
-
const rootCertificateDifferences = compareRootCertificateAttributes(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
210
|
+
const rootCertificateDifferences = compareRootCertificateAttributes(
|
|
211
|
+
rootCertificateForgeObject,
|
|
212
|
+
{
|
|
213
|
+
certificateCommonName,
|
|
214
|
+
certificateValidityDurationInMs,
|
|
215
|
+
},
|
|
216
|
+
)
|
|
190
217
|
if (rootCertificateDifferences.length) {
|
|
191
218
|
const paramNames = Object.keys(rootCertificateDifferences)
|
|
192
|
-
logger.info(
|
|
219
|
+
logger.info(
|
|
220
|
+
`${infoSign} certificate attributes are outdated: ${paramNames}`,
|
|
221
|
+
)
|
|
193
222
|
return regenerate()
|
|
194
223
|
}
|
|
195
224
|
logger.info(`${okSign} certificate attributes are the same`)
|
|
196
225
|
|
|
197
|
-
const rootCertificatePrivateKey = await readFile(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
226
|
+
const rootCertificatePrivateKey = await readFile(
|
|
227
|
+
rootCertificatePrivateKeyFileInfo.path,
|
|
228
|
+
{
|
|
229
|
+
as: "string",
|
|
230
|
+
},
|
|
231
|
+
)
|
|
232
|
+
const rootCertificatePrivateKeyForgeObject = pki.privateKeyFromPem(
|
|
233
|
+
rootCertificatePrivateKey,
|
|
234
|
+
)
|
|
201
235
|
|
|
202
236
|
const trustInfo = await platformMethods.executeTrustQuery({
|
|
203
237
|
logger,
|
|
@@ -272,8 +306,11 @@ export const uninstallCertificateAuthority = async ({
|
|
|
272
306
|
logger = createLogger({ logLevel }),
|
|
273
307
|
tryToUntrust = false,
|
|
274
308
|
} = {}) => {
|
|
275
|
-
const {
|
|
276
|
-
|
|
309
|
+
const {
|
|
310
|
+
authorityJsonFileInfo,
|
|
311
|
+
rootCertificateFileInfo,
|
|
312
|
+
rootCertificatePrivateKeyFileInfo,
|
|
313
|
+
} = getAuthorityFileInfos()
|
|
277
314
|
|
|
278
315
|
const filesToRemove = []
|
|
279
316
|
|
|
@@ -283,7 +320,9 @@ export const uninstallCertificateAuthority = async ({
|
|
|
283
320
|
if (rootCertificateFileInfo.exists) {
|
|
284
321
|
// first untrust the root cert file
|
|
285
322
|
if (tryToUntrust) {
|
|
286
|
-
const rootCertificate = await readFile(rootCertificateFileInfo.url, {
|
|
323
|
+
const rootCertificate = await readFile(rootCertificateFileInfo.url, {
|
|
324
|
+
as: "string",
|
|
325
|
+
})
|
|
287
326
|
const { pki } = await importNodeForge()
|
|
288
327
|
const rootCertificateForgeObject = pki.certificateFromPem(rootCertificate)
|
|
289
328
|
const rootCertificateCommonName = attributeDescriptionFromAttributeArray(
|
|
@@ -41,8 +41,11 @@ export const requestCertificateForLocalhost = async ({
|
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const {
|
|
45
|
-
|
|
44
|
+
const {
|
|
45
|
+
authorityJsonFileInfo,
|
|
46
|
+
rootCertificateFileInfo,
|
|
47
|
+
rootCertificatePrivateKeyFileInfo,
|
|
48
|
+
} = getAuthorityFileInfos()
|
|
46
49
|
if (!rootCertificateFileInfo.exists) {
|
|
47
50
|
throw new Error(
|
|
48
51
|
`Certificate authority not found, "installCertificateAuthority" must be called before "requestCertificateForLocalhost"`,
|
|
@@ -57,16 +60,26 @@ export const requestCertificateForLocalhost = async ({
|
|
|
57
60
|
|
|
58
61
|
logger.debug(`Restoring certificate authority from filesystem...`)
|
|
59
62
|
const { pki } = await importNodeForge()
|
|
60
|
-
const rootCertificate = await readFile(rootCertificateFileInfo.url, {
|
|
61
|
-
const rootCertificatePrivateKey = await readFile(rootCertificatePrivateKeyFileInfo.url, {
|
|
63
|
+
const rootCertificate = await readFile(rootCertificateFileInfo.url, {
|
|
62
64
|
as: "string",
|
|
63
65
|
})
|
|
64
|
-
const
|
|
66
|
+
const rootCertificatePrivateKey = await readFile(
|
|
67
|
+
rootCertificatePrivateKeyFileInfo.url,
|
|
68
|
+
{
|
|
69
|
+
as: "string",
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
const certificateAuthorityData = await readFile(authorityJsonFileInfo.url, {
|
|
73
|
+
as: "json",
|
|
74
|
+
})
|
|
65
75
|
const rootCertificateForgeObject = pki.certificateFromPem(rootCertificate)
|
|
66
|
-
const rootCertificatePrivateKeyForgeObject = pki.privateKeyFromPem(
|
|
76
|
+
const rootCertificatePrivateKeyForgeObject = pki.privateKeyFromPem(
|
|
77
|
+
rootCertificatePrivateKey,
|
|
78
|
+
)
|
|
67
79
|
logger.debug(`${okSign} certificate authority restored from filesystem`)
|
|
68
80
|
|
|
69
|
-
const serverCertificateSerialNumber =
|
|
81
|
+
const serverCertificateSerialNumber =
|
|
82
|
+
certificateAuthorityData.serialNumber + 1
|
|
70
83
|
await writeFile(
|
|
71
84
|
authorityJsonFileInfo.url,
|
|
72
85
|
JSON.stringify({ serialNumber: serverCertificateSerialNumber }, null, " "),
|
|
@@ -81,14 +94,17 @@ export const requestCertificateForLocalhost = async ({
|
|
|
81
94
|
await requestCertificateFromAuthority({
|
|
82
95
|
logger,
|
|
83
96
|
authorityCertificateForgeObject: rootCertificateForgeObject,
|
|
84
|
-
auhtorityCertificatePrivateKeyForgeObject:
|
|
97
|
+
auhtorityCertificatePrivateKeyForgeObject:
|
|
98
|
+
rootCertificatePrivateKeyForgeObject,
|
|
85
99
|
serialNumber: serverCertificateSerialNumber,
|
|
86
100
|
altNames: serverCertificateAltNames,
|
|
87
101
|
commonName: serverCertificateCommonName,
|
|
88
102
|
validityDurationInMs: serverCertificateValidityDurationInMs,
|
|
89
103
|
})
|
|
90
104
|
const serverCertificate = pki.certificateToPem(certificateForgeObject)
|
|
91
|
-
const serverCertificatePrivateKey = pki.privateKeyToPem(
|
|
105
|
+
const serverCertificatePrivateKey = pki.privateKeyToPem(
|
|
106
|
+
certificatePrivateKeyForgeObject,
|
|
107
|
+
)
|
|
92
108
|
logger.debug(
|
|
93
109
|
`${okSign} server certificate generated, it will be valid for ${formatDuration(
|
|
94
110
|
serverCertificateValidityDurationInMs,
|
package/src/hosts_file_verif.js
CHANGED
|
@@ -24,7 +24,9 @@ export const verifyHostsFile = async ({
|
|
|
24
24
|
Object.keys(ipMappings).forEach((ip) => {
|
|
25
25
|
const ipHostnames = ipMappings[ip]
|
|
26
26
|
if (!Array.isArray(ipHostnames)) {
|
|
27
|
-
throw new TypeError(
|
|
27
|
+
throw new TypeError(
|
|
28
|
+
`ipMappings values must be an array, found ${ipHostnames} for ${ip}`,
|
|
29
|
+
)
|
|
28
30
|
}
|
|
29
31
|
const existingMappings = hostnames.getIpHostnames(ip)
|
|
30
32
|
const missingHostnames = normalizeHostnames(ipHostnames).filter(
|
|
@@ -47,15 +49,20 @@ export const verifyHostsFile = async ({
|
|
|
47
49
|
.map(({ ip, missingHostnames }) => `${ip} ${missingHostnames.join(" ")}`)
|
|
48
50
|
.join(EOL)
|
|
49
51
|
logger.warn(
|
|
50
|
-
createDetailedMessage(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
createDetailedMessage(
|
|
53
|
+
`${warningSign} ${formatXMappingMissingMessage(missingMappingCount)}`,
|
|
54
|
+
{
|
|
55
|
+
"hosts file path": hostsFilePath,
|
|
56
|
+
"line(s) to add": linesToAdd,
|
|
57
|
+
},
|
|
58
|
+
),
|
|
54
59
|
)
|
|
55
60
|
return
|
|
56
61
|
}
|
|
57
62
|
|
|
58
|
-
logger.info(
|
|
63
|
+
logger.info(
|
|
64
|
+
`${infoSign} ${formatXMappingMissingMessage(missingMappingCount)}`,
|
|
65
|
+
)
|
|
59
66
|
await missingMappings.reduce(async (previous, { ip, missingHostnames }) => {
|
|
60
67
|
await previous
|
|
61
68
|
const mapping = `${ip} ${missingHostnames.join(" ")}`
|
|
@@ -11,14 +11,20 @@ export const getAuthorityFileInfos = () => {
|
|
|
11
11
|
rootCertificatePrivateKeyFileUrl,
|
|
12
12
|
} = getCertificateAuthorityFileUrls()
|
|
13
13
|
|
|
14
|
-
const authorityJsonFilePath = urlToFileSystemPath(
|
|
14
|
+
const authorityJsonFilePath = urlToFileSystemPath(
|
|
15
|
+
certificateAuthorityJsonFileUrl,
|
|
16
|
+
)
|
|
15
17
|
const authorityJsonFileDetected = existsSync(authorityJsonFilePath)
|
|
16
18
|
|
|
17
19
|
const rootCertificateFilePath = urlToFileSystemPath(rootCertificateFileUrl)
|
|
18
20
|
const rootCertificateFileDetected = existsSync(rootCertificateFilePath)
|
|
19
21
|
|
|
20
|
-
const rootCertificatePrivateKeyFilePath = urlToFileSystemPath(
|
|
21
|
-
|
|
22
|
+
const rootCertificatePrivateKeyFilePath = urlToFileSystemPath(
|
|
23
|
+
rootCertificatePrivateKeyFileUrl,
|
|
24
|
+
)
|
|
25
|
+
const rootCertificatePrivateKeyFileDetected = existsSync(
|
|
26
|
+
rootCertificatePrivateKeyFilePath,
|
|
27
|
+
)
|
|
22
28
|
|
|
23
29
|
return {
|
|
24
30
|
authorityJsonFileInfo: {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
assertAndNormalizeDirectoryUrl,
|
|
3
|
+
resolveUrl,
|
|
4
|
+
urlToFilename,
|
|
5
|
+
} from "@jsenv/filesystem"
|
|
2
6
|
|
|
3
7
|
export const getCertificateAuthorityFileUrls = () => {
|
|
4
8
|
// we need a directory common to every instance of @jsenv/https-local
|
|
@@ -36,9 +40,15 @@ export const getRootCertificateSymlinkUrls = ({
|
|
|
36
40
|
const serverCertificateDirectory = resolveUrl("./", serverCertificateFileUrl)
|
|
37
41
|
|
|
38
42
|
const rootCertificateFilename = urlToFilename(rootCertificateFileUrl)
|
|
39
|
-
const rootCertificateSymlinkUrl = resolveUrl(
|
|
43
|
+
const rootCertificateSymlinkUrl = resolveUrl(
|
|
44
|
+
rootCertificateFilename,
|
|
45
|
+
serverCertificateDirectory,
|
|
46
|
+
)
|
|
40
47
|
const rootPrivateKeyFilename = urlToFilename(rootPrivateKeyFileUrl)
|
|
41
|
-
const rootPrivateKeySymlinkUrl = resolveUrl(
|
|
48
|
+
const rootPrivateKeySymlinkUrl = resolveUrl(
|
|
49
|
+
rootPrivateKeyFilename,
|
|
50
|
+
serverCertificateDirectory,
|
|
51
|
+
)
|
|
42
52
|
|
|
43
53
|
return {
|
|
44
54
|
rootCertificateSymlinkUrl,
|
|
@@ -64,12 +74,18 @@ const getJsenvApplicationDirectoryUrl = () => {
|
|
|
64
74
|
assertAndNormalizeDirectoryUrl(process.env.XDG_CONFIG_HOME),
|
|
65
75
|
)
|
|
66
76
|
}
|
|
67
|
-
return resolveUrl(
|
|
77
|
+
return resolveUrl(
|
|
78
|
+
`./.config/https_local/`,
|
|
79
|
+
assertAndNormalizeDirectoryUrl(process.env.HOME),
|
|
80
|
+
)
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
if (platform === "win32") {
|
|
71
84
|
if (process.env.LOCALAPPDATA) {
|
|
72
|
-
return resolveUrl(
|
|
85
|
+
return resolveUrl(
|
|
86
|
+
`./https_local/`,
|
|
87
|
+
assertAndNormalizeDirectoryUrl(process.env.LOCALAPPDATA),
|
|
88
|
+
)
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
return resolveUrl(
|
|
@@ -34,7 +34,9 @@ const isUrl = (value) => {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export const extensionArrayFromExtensionDescription = (
|
|
37
|
+
export const extensionArrayFromExtensionDescription = (
|
|
38
|
+
extensionDescription,
|
|
39
|
+
) => {
|
|
38
40
|
const extensionArray = []
|
|
39
41
|
Object.keys(extensionDescription).forEach((key) => {
|
|
40
42
|
const value = extensionDescription[key]
|
|
@@ -65,7 +67,9 @@ export const attributeDescriptionFromAttributeArray = (attributeArray) => {
|
|
|
65
67
|
return attributeObject
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
export const attributeArrayFromAttributeDescription = (
|
|
70
|
+
export const attributeArrayFromAttributeDescription = (
|
|
71
|
+
attributeDescription,
|
|
72
|
+
) => {
|
|
69
73
|
const attributeArray = []
|
|
70
74
|
Object.keys(attributeDescription).forEach((key) => {
|
|
71
75
|
const value = attributeDescription[key]
|
|
@@ -33,7 +33,9 @@ export const createAuthorityRootCertificate = async ({
|
|
|
33
33
|
rootCertificateForgeObject.publicKey = rootCertificatePublicKeyForgeObject
|
|
34
34
|
rootCertificateForgeObject.serialNumber = serialNumber.toString(16)
|
|
35
35
|
rootCertificateForgeObject.validity.notBefore = new Date()
|
|
36
|
-
rootCertificateForgeObject.validity.notAfter = new Date(
|
|
36
|
+
rootCertificateForgeObject.validity.notAfter = new Date(
|
|
37
|
+
Date.now() + validityDurationInMs,
|
|
38
|
+
)
|
|
37
39
|
rootCertificateForgeObject.setSubject(
|
|
38
40
|
attributeArrayFromAttributeDescription({
|
|
39
41
|
commonName,
|
|
@@ -109,7 +111,9 @@ export const requestCertificateFromAuthority = async ({
|
|
|
109
111
|
)
|
|
110
112
|
}
|
|
111
113
|
if (typeof serialNumber !== "number") {
|
|
112
|
-
throw new TypeError(
|
|
114
|
+
throw new TypeError(
|
|
115
|
+
`serialNumber must be a number but received ${serialNumber}`,
|
|
116
|
+
)
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
const forge = await importNodeForge()
|
|
@@ -122,16 +126,23 @@ export const requestCertificateFromAuthority = async ({
|
|
|
122
126
|
certificateForgeObject.publicKey = certificatePublicKeyForgeObject
|
|
123
127
|
certificateForgeObject.serialNumber = serialNumber.toString(16)
|
|
124
128
|
certificateForgeObject.validity.notBefore = new Date()
|
|
125
|
-
certificateForgeObject.validity.notAfter = new Date(
|
|
129
|
+
certificateForgeObject.validity.notAfter = new Date(
|
|
130
|
+
Date.now() + validityDurationInMs,
|
|
131
|
+
)
|
|
126
132
|
|
|
127
133
|
const attributeDescription = {
|
|
128
|
-
...attributeDescriptionFromAttributeArray(
|
|
134
|
+
...attributeDescriptionFromAttributeArray(
|
|
135
|
+
authorityCertificateForgeObject.subject.attributes,
|
|
136
|
+
),
|
|
129
137
|
commonName,
|
|
130
138
|
// organizationName: serverCertificateOrganizationName
|
|
131
139
|
}
|
|
132
|
-
const attributeArray =
|
|
140
|
+
const attributeArray =
|
|
141
|
+
attributeArrayFromAttributeDescription(attributeDescription)
|
|
133
142
|
certificateForgeObject.setSubject(attributeArray)
|
|
134
|
-
certificateForgeObject.setIssuer(
|
|
143
|
+
certificateForgeObject.setIssuer(
|
|
144
|
+
authorityCertificateForgeObject.subject.attributes,
|
|
145
|
+
)
|
|
135
146
|
certificateForgeObject.setExtensions(
|
|
136
147
|
extensionArrayFromExtensionDescription({
|
|
137
148
|
basicConstraints: {
|
|
@@ -159,7 +170,10 @@ export const requestCertificateFromAuthority = async ({
|
|
|
159
170
|
},
|
|
160
171
|
}),
|
|
161
172
|
)
|
|
162
|
-
certificateForgeObject.sign(
|
|
173
|
+
certificateForgeObject.sign(
|
|
174
|
+
auhtorityCertificatePrivateKeyForgeObject,
|
|
175
|
+
forge.sha256.create(),
|
|
176
|
+
)
|
|
163
177
|
|
|
164
178
|
return {
|
|
165
179
|
certificateForgeObject,
|
package/src/internal/command.js
CHANGED
package/src/internal/exec.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { exec as nodeExec } from "node:child_process"
|
|
2
2
|
|
|
3
|
-
export const exec = (
|
|
3
|
+
export const exec = (
|
|
4
|
+
command,
|
|
5
|
+
{ cwd, input, onLog = () => {}, onErrorLog = () => {} } = {},
|
|
6
|
+
) => {
|
|
4
7
|
return new Promise((resolve, reject) => {
|
|
5
8
|
const commandProcess = nodeExec(
|
|
6
9
|
command,
|