@jsenv/https-local 1.0.10 → 2.0.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/README.md +6 -8
- package/package.json +17 -19
- package/src/certificate_authority.js +5 -6
- package/src/certificate_for_localhost.js +34 -38
- package/src/hosts_file_verif.js +1 -2
- package/src/internal/authority_file_infos.js +4 -6
- package/src/internal/certificate_authority_file_urls.js +20 -22
- package/src/internal/certificate_generator.js +2 -4
- package/src/internal/forge.js +1 -3
- package/src/internal/linux/chrome_linux.js +5 -6
- package/src/internal/linux/firefox_linux.js +4 -5
- package/src/internal/linux/linux_trust_store.js +5 -5
- package/src/internal/linux/nss_linux.js +2 -2
- package/src/internal/mac/chrome_mac.js +1 -1
- package/src/internal/mac/firefox_mac.js +3 -3
- package/src/internal/mac/mac_keychain.js +5 -6
- package/src/internal/mac/nss_mac.js +7 -10
- package/src/internal/nssdb_browser.js +14 -23
- package/src/internal/windows/chrome_windows.js +1 -1
- package/src/internal/windows/firefox_windows.js +1 -1
- package/src/internal/windows/windows_certutil.js +4 -5
- package/{main.js → src/main.js} +4 -4
- package/src/validity_duration.js +4 -9
package/README.md
CHANGED
|
@@ -67,13 +67,12 @@ node ./install_certificate_authority.mjs
|
|
|
67
67
|
import { createServer } from "node:https"
|
|
68
68
|
import { requestCertificateForLocalhost } from "@jsenv/https-local"
|
|
69
69
|
|
|
70
|
-
const {
|
|
71
|
-
await requestCertificateForLocalhost()
|
|
70
|
+
const { certificate, privateKey } = requestCertificateForLocalhost()
|
|
72
71
|
|
|
73
72
|
const server = createServer(
|
|
74
73
|
{
|
|
75
|
-
cert:
|
|
76
|
-
key:
|
|
74
|
+
cert: certificate,
|
|
75
|
+
key: privateKey,
|
|
77
76
|
},
|
|
78
77
|
(request, response) => {
|
|
79
78
|
const body = "Hello world"
|
|
@@ -361,10 +360,9 @@ _requestCertificateForLocalhost_ function returns a certificate and private key
|
|
|
361
360
|
import { createServer } from "node:https"
|
|
362
361
|
import { requestCertificateForLocalhost } from "@jsenv/https-local"
|
|
363
362
|
|
|
364
|
-
const {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
})
|
|
363
|
+
const { certificate, privateKey } = requestCertificateForLocalhost({
|
|
364
|
+
altNames: ["localhost", "local.example"],
|
|
365
|
+
})
|
|
368
366
|
```
|
|
369
367
|
|
|
370
368
|
[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": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A programmatic way to generate locally trusted certificates",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"url": "https://github.com/jsenv/https-local"
|
|
14
14
|
},
|
|
15
15
|
"publishConfig": {
|
|
16
|
-
"access": "public"
|
|
16
|
+
"access": "public",
|
|
17
|
+
"registry": "https://registry.npmjs.org"
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
|
19
20
|
"node": ">=16.13.0"
|
|
@@ -21,14 +22,13 @@
|
|
|
21
22
|
"type": "module",
|
|
22
23
|
"exports": {
|
|
23
24
|
".": {
|
|
24
|
-
"import": "./main.js"
|
|
25
|
+
"import": "./src/main.js"
|
|
25
26
|
},
|
|
26
27
|
"./*": "./*"
|
|
27
28
|
},
|
|
28
|
-
"main": "./main.js",
|
|
29
|
+
"main": "./src/main.js",
|
|
29
30
|
"files": [
|
|
30
|
-
"/src/"
|
|
31
|
-
"/main.js"
|
|
31
|
+
"/src/"
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
34
|
"eslint": "npx eslint . --ext=.js,.mjs,.cjs",
|
|
@@ -48,27 +48,25 @@
|
|
|
48
48
|
"playwright-install": "npx playwright install-deps && npx playwright install"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@jsenv/
|
|
52
|
-
"@jsenv/
|
|
53
|
-
"@jsenv/
|
|
54
|
-
"@jsenv/logger": "4.0.1",
|
|
51
|
+
"@jsenv/filesystem": "4.1.0",
|
|
52
|
+
"@jsenv/log": "2.0.1",
|
|
53
|
+
"@jsenv/urls": "1.2.6",
|
|
55
54
|
"command-exists": "1.2.9",
|
|
56
55
|
"node-forge": "1.3.1",
|
|
57
56
|
"sudo-prompt": "9.2.1",
|
|
58
57
|
"which": "2.0.2"
|
|
59
58
|
},
|
|
60
59
|
"devDependencies": {
|
|
61
|
-
"@jsenv/assert": "2.
|
|
62
|
-
"@jsenv/core": "27.0.0-alpha.
|
|
60
|
+
"@jsenv/assert": "2.6.0",
|
|
61
|
+
"@jsenv/core": "27.0.0-alpha.92",
|
|
63
62
|
"@jsenv/eslint-config": "16.0.9",
|
|
63
|
+
"@jsenv/eslint-import-resolver": "0.3.0",
|
|
64
64
|
"@jsenv/github-release-package": "1.4.0",
|
|
65
|
-
"@jsenv/
|
|
66
|
-
"@jsenv/
|
|
67
|
-
"
|
|
68
|
-
"@jsenv/performance-impact": "2.2.11",
|
|
69
|
-
"eslint": "8.16.0",
|
|
65
|
+
"@jsenv/package-publish": "1.7.5",
|
|
66
|
+
"@jsenv/performance-impact": "2.3.0",
|
|
67
|
+
"eslint": "8.18.0",
|
|
70
68
|
"eslint-plugin-import": "2.26.0",
|
|
71
69
|
"playwright": "1.22.2",
|
|
72
|
-
"prettier": "2.
|
|
70
|
+
"prettier": "2.7.1"
|
|
73
71
|
}
|
|
74
|
-
}
|
|
72
|
+
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { readFile, writeFile, removeEntry } from "@jsenv/filesystem"
|
|
2
|
-
import { createLogger, createDetailedMessage } from "@jsenv/
|
|
3
|
-
import { UNICODE } from "@jsenv/log"
|
|
2
|
+
import { UNICODE, createLogger, createDetailedMessage } from "@jsenv/log"
|
|
4
3
|
|
|
4
|
+
import { forge } from "./internal/forge.js"
|
|
5
5
|
import { getAuthorityFileInfos } from "./internal/authority_file_infos.js"
|
|
6
6
|
import { attributeDescriptionFromAttributeArray } from "./internal/certificate_data_converter.js"
|
|
7
7
|
import {
|
|
8
8
|
formatTimeDelta,
|
|
9
9
|
formatDuration,
|
|
10
10
|
} from "./internal/validity_formatting.js"
|
|
11
|
-
import { importNodeForge } from "./internal/forge.js"
|
|
12
11
|
import { createAuthorityRootCertificate } from "./internal/certificate_generator.js"
|
|
13
12
|
import { importPlatformMethods } from "./internal/platform.js"
|
|
14
13
|
import { jsenvParameters } from "./jsenvParameters.js"
|
|
@@ -79,7 +78,7 @@ export const installCertificateAuthority = async ({
|
|
|
79
78
|
serialNumber: 0,
|
|
80
79
|
})
|
|
81
80
|
|
|
82
|
-
const { pki } =
|
|
81
|
+
const { pki } = forge
|
|
83
82
|
const rootCertificate = pemAsFileContent(
|
|
84
83
|
pki.certificateToPem(rootCertificateForgeObject),
|
|
85
84
|
)
|
|
@@ -173,7 +172,7 @@ export const installCertificateAuthority = async ({
|
|
|
173
172
|
const rootCertificate = await readFile(rootCertificateFileInfo.path, {
|
|
174
173
|
as: "string",
|
|
175
174
|
})
|
|
176
|
-
const { pki } =
|
|
175
|
+
const { pki } = forge
|
|
177
176
|
const rootCertificateForgeObject = pki.certificateFromPem(rootCertificate)
|
|
178
177
|
|
|
179
178
|
logger.info(`Checking certificate validity...`)
|
|
@@ -323,7 +322,7 @@ export const uninstallCertificateAuthority = async ({
|
|
|
323
322
|
const rootCertificate = await readFile(rootCertificateFileInfo.url, {
|
|
324
323
|
as: "string",
|
|
325
324
|
})
|
|
326
|
-
const { pki } =
|
|
325
|
+
const { pki } = forge
|
|
327
326
|
const rootCertificateForgeObject = pki.certificateFromPem(rootCertificate)
|
|
328
327
|
const rootCertificateCommonName = attributeDescriptionFromAttributeArray(
|
|
329
328
|
rootCertificateForgeObject.subject.attributes,
|
|
@@ -1,39 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { UNICODE } from "@jsenv/log"
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { writeFileSync } from "@jsenv/filesystem"
|
|
3
|
+
import { UNICODE, createLogger, createDetailedMessage } from "@jsenv/log"
|
|
4
4
|
|
|
5
|
+
import { forge } from "./internal/forge.js"
|
|
5
6
|
import {
|
|
6
7
|
createValidityDurationOfXDays,
|
|
7
8
|
verifyServerCertificateValidityDuration,
|
|
8
9
|
} from "./validity_duration.js"
|
|
9
10
|
import { getAuthorityFileInfos } from "./internal/authority_file_infos.js"
|
|
10
|
-
import { importNodeForge } from "./internal/forge.js"
|
|
11
11
|
import { requestCertificateFromAuthority } from "./internal/certificate_generator.js"
|
|
12
12
|
import { formatDuration } from "./internal/validity_formatting.js"
|
|
13
13
|
|
|
14
|
-
export const requestCertificateForLocalhost =
|
|
14
|
+
export const requestCertificateForLocalhost = ({
|
|
15
15
|
logLevel,
|
|
16
16
|
logger = createLogger({ logLevel }), // to be able to catch logs during unit tests
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
altNames = ["localhost"],
|
|
19
|
+
commonName = "https local server certificate",
|
|
20
|
+
validityDurationInMs = createValidityDurationOfXDays(396),
|
|
21
21
|
} = {}) => {
|
|
22
|
-
if (typeof
|
|
22
|
+
if (typeof validityDurationInMs !== "number") {
|
|
23
23
|
throw new TypeError(
|
|
24
|
-
`
|
|
24
|
+
`validityDurationInMs must be a number but received ${validityDurationInMs}`,
|
|
25
25
|
)
|
|
26
26
|
}
|
|
27
|
-
if (
|
|
27
|
+
if (validityDurationInMs < 1) {
|
|
28
28
|
throw new TypeError(
|
|
29
|
-
`
|
|
29
|
+
`validityDurationInMs must be > 0 but received ${validityDurationInMs}`,
|
|
30
30
|
)
|
|
31
31
|
}
|
|
32
|
-
const validityDurationInfo =
|
|
33
|
-
|
|
34
|
-
)
|
|
32
|
+
const validityDurationInfo =
|
|
33
|
+
verifyServerCertificateValidityDuration(validityDurationInMs)
|
|
35
34
|
if (!validityDurationInfo.ok) {
|
|
36
|
-
|
|
35
|
+
validityDurationInMs = validityDurationInfo.maxAllowedValue
|
|
37
36
|
logger.warn(
|
|
38
37
|
createDetailedMessage(validityDurationInfo.message, {
|
|
39
38
|
details: validityDurationInfo.details,
|
|
@@ -59,19 +58,16 @@ export const requestCertificateForLocalhost = async ({
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
logger.debug(`Restoring certificate authority from filesystem...`)
|
|
62
|
-
const { pki } =
|
|
63
|
-
const rootCertificate =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const rootCertificatePrivateKey =
|
|
67
|
-
rootCertificatePrivateKeyFileInfo.url,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
const { pki } = forge
|
|
62
|
+
const rootCertificate = String(
|
|
63
|
+
readFileSync(new URL(rootCertificateFileInfo.url)),
|
|
64
|
+
)
|
|
65
|
+
const rootCertificatePrivateKey = String(
|
|
66
|
+
readFileSync(new URL(rootCertificatePrivateKeyFileInfo.url)),
|
|
67
|
+
)
|
|
68
|
+
const certificateAuthorityData = JSON.parse(
|
|
69
|
+
String(readFileSync(new URL(authorityJsonFileInfo.url))),
|
|
71
70
|
)
|
|
72
|
-
const certificateAuthorityData = await readFile(authorityJsonFileInfo.url, {
|
|
73
|
-
as: "json",
|
|
74
|
-
})
|
|
75
71
|
const rootCertificateForgeObject = pki.certificateFromPem(rootCertificate)
|
|
76
72
|
const rootCertificatePrivateKeyForgeObject = pki.privateKeyFromPem(
|
|
77
73
|
rootCertificatePrivateKey,
|
|
@@ -80,26 +76,26 @@ export const requestCertificateForLocalhost = async ({
|
|
|
80
76
|
|
|
81
77
|
const serverCertificateSerialNumber =
|
|
82
78
|
certificateAuthorityData.serialNumber + 1
|
|
83
|
-
|
|
79
|
+
writeFileSync(
|
|
84
80
|
authorityJsonFileInfo.url,
|
|
85
81
|
JSON.stringify({ serialNumber: serverCertificateSerialNumber }, null, " "),
|
|
86
82
|
)
|
|
87
83
|
|
|
88
|
-
if (!
|
|
89
|
-
|
|
84
|
+
if (!altNames.includes("localhost")) {
|
|
85
|
+
altNames.push("localhost")
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
logger.debug(`Generating server certificate...`)
|
|
93
89
|
const { certificateForgeObject, certificatePrivateKeyForgeObject } =
|
|
94
|
-
|
|
90
|
+
requestCertificateFromAuthority({
|
|
95
91
|
logger,
|
|
96
92
|
authorityCertificateForgeObject: rootCertificateForgeObject,
|
|
97
93
|
auhtorityCertificatePrivateKeyForgeObject:
|
|
98
94
|
rootCertificatePrivateKeyForgeObject,
|
|
99
95
|
serialNumber: serverCertificateSerialNumber,
|
|
100
|
-
altNames
|
|
101
|
-
commonName
|
|
102
|
-
validityDurationInMs
|
|
96
|
+
altNames,
|
|
97
|
+
commonName,
|
|
98
|
+
validityDurationInMs,
|
|
103
99
|
})
|
|
104
100
|
const serverCertificate = pki.certificateToPem(certificateForgeObject)
|
|
105
101
|
const serverCertificatePrivateKey = pki.privateKeyToPem(
|
|
@@ -109,13 +105,13 @@ export const requestCertificateForLocalhost = async ({
|
|
|
109
105
|
`${
|
|
110
106
|
UNICODE.OK
|
|
111
107
|
} server certificate generated, it will be valid for ${formatDuration(
|
|
112
|
-
|
|
108
|
+
validityDurationInMs,
|
|
113
109
|
)}`,
|
|
114
110
|
)
|
|
115
111
|
|
|
116
112
|
return {
|
|
117
|
-
serverCertificate,
|
|
118
|
-
serverCertificatePrivateKey,
|
|
113
|
+
certificate: serverCertificate,
|
|
114
|
+
privateKey: serverCertificatePrivateKey,
|
|
119
115
|
rootCertificateFilePath: rootCertificateFileInfo.path,
|
|
120
116
|
}
|
|
121
117
|
}
|
package/src/hosts_file_verif.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
|
-
import {
|
|
2
|
+
import { fileURLToPath } from "node:url"
|
|
3
3
|
|
|
4
4
|
import { getCertificateAuthorityFileUrls } from "./certificate_authority_file_urls.js"
|
|
5
5
|
|
|
@@ -10,15 +10,13 @@ export const getAuthorityFileInfos = () => {
|
|
|
10
10
|
rootCertificatePrivateKeyFileUrl,
|
|
11
11
|
} = getCertificateAuthorityFileUrls()
|
|
12
12
|
|
|
13
|
-
const authorityJsonFilePath =
|
|
14
|
-
certificateAuthorityJsonFileUrl,
|
|
15
|
-
)
|
|
13
|
+
const authorityJsonFilePath = fileURLToPath(certificateAuthorityJsonFileUrl)
|
|
16
14
|
const authorityJsonFileDetected = existsSync(authorityJsonFilePath)
|
|
17
15
|
|
|
18
|
-
const rootCertificateFilePath =
|
|
16
|
+
const rootCertificateFilePath = fileURLToPath(rootCertificateFileUrl)
|
|
19
17
|
const rootCertificateFileDetected = existsSync(rootCertificateFilePath)
|
|
20
18
|
|
|
21
|
-
const rootCertificatePrivateKeyFilePath =
|
|
19
|
+
const rootCertificatePrivateKeyFilePath = fileURLToPath(
|
|
22
20
|
rootCertificatePrivateKeyFileUrl,
|
|
23
21
|
)
|
|
24
22
|
const rootCertificatePrivateKeyFileDetected = existsSync(
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
resolveUrl,
|
|
4
|
-
urlToFilename,
|
|
5
|
-
} from "@jsenv/filesystem"
|
|
1
|
+
import { urlToFilename } from "@jsenv/urls"
|
|
2
|
+
import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
6
3
|
|
|
7
4
|
export const getCertificateAuthorityFileUrls = () => {
|
|
8
5
|
// we need a directory common to every instance of @jsenv/https-local
|
|
@@ -20,10 +17,10 @@ export const getCertificateAuthorityFileUrls = () => {
|
|
|
20
17
|
applicationDirectoryUrl,
|
|
21
18
|
)
|
|
22
19
|
|
|
23
|
-
const rootCertificatePrivateKeyFileUrl =
|
|
20
|
+
const rootCertificatePrivateKeyFileUrl = new URL(
|
|
24
21
|
"./https_local_root_certificate.key",
|
|
25
22
|
applicationDirectoryUrl,
|
|
26
|
-
)
|
|
23
|
+
).href
|
|
27
24
|
|
|
28
25
|
return {
|
|
29
26
|
certificateAuthorityJsonFileUrl,
|
|
@@ -37,18 +34,19 @@ export const getRootCertificateSymlinkUrls = ({
|
|
|
37
34
|
rootPrivateKeyFileUrl,
|
|
38
35
|
serverCertificateFileUrl,
|
|
39
36
|
}) => {
|
|
40
|
-
const serverCertificateDirectory =
|
|
37
|
+
const serverCertificateDirectory = new URL("./", serverCertificateFileUrl)
|
|
38
|
+
.href
|
|
41
39
|
|
|
42
40
|
const rootCertificateFilename = urlToFilename(rootCertificateFileUrl)
|
|
43
|
-
const rootCertificateSymlinkUrl =
|
|
41
|
+
const rootCertificateSymlinkUrl = new URL(
|
|
44
42
|
rootCertificateFilename,
|
|
45
43
|
serverCertificateDirectory,
|
|
46
|
-
)
|
|
44
|
+
).href
|
|
47
45
|
const rootPrivateKeyFilename = urlToFilename(rootPrivateKeyFileUrl)
|
|
48
|
-
const rootPrivateKeySymlinkUrl =
|
|
46
|
+
const rootPrivateKeySymlinkUrl = new URL(
|
|
49
47
|
rootPrivateKeyFilename,
|
|
50
48
|
serverCertificateDirectory,
|
|
51
|
-
)
|
|
49
|
+
).href
|
|
52
50
|
|
|
53
51
|
return {
|
|
54
52
|
rootCertificateSymlinkUrl,
|
|
@@ -61,37 +59,37 @@ const getJsenvApplicationDirectoryUrl = () => {
|
|
|
61
59
|
const { platform } = process
|
|
62
60
|
|
|
63
61
|
if (platform === "darwin") {
|
|
64
|
-
return
|
|
62
|
+
return new URL(
|
|
65
63
|
`./Library/Application Support/https_local/`,
|
|
66
64
|
assertAndNormalizeDirectoryUrl(process.env.HOME),
|
|
67
|
-
)
|
|
65
|
+
).href
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
if (platform === "linux") {
|
|
71
69
|
if (process.env.XDG_CONFIG_HOME) {
|
|
72
|
-
return
|
|
70
|
+
return new URL(
|
|
73
71
|
`./https_local/`,
|
|
74
72
|
assertAndNormalizeDirectoryUrl(process.env.XDG_CONFIG_HOME),
|
|
75
|
-
)
|
|
73
|
+
).href
|
|
76
74
|
}
|
|
77
|
-
return
|
|
75
|
+
return new URL(
|
|
78
76
|
`./.config/https_local/`,
|
|
79
77
|
assertAndNormalizeDirectoryUrl(process.env.HOME),
|
|
80
|
-
)
|
|
78
|
+
).href
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
if (platform === "win32") {
|
|
84
82
|
if (process.env.LOCALAPPDATA) {
|
|
85
|
-
return
|
|
83
|
+
return new URL(
|
|
86
84
|
`./https_local/`,
|
|
87
85
|
assertAndNormalizeDirectoryUrl(process.env.LOCALAPPDATA),
|
|
88
|
-
)
|
|
86
|
+
).href
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
return
|
|
89
|
+
return new URL(
|
|
92
90
|
`./Local Settings/Application Data/https_local/`,
|
|
93
91
|
assertAndNormalizeDirectoryUrl(process.env.USERPROFILE),
|
|
94
|
-
)
|
|
92
|
+
).href
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
throw new Error(`platform not supported`)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// https://github.com/digitalbazaar/forge/blob/master/examples/create-cert.js
|
|
2
2
|
// https://github.com/digitalbazaar/forge/issues/660#issuecomment-467145103
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { forge } from "./forge.js"
|
|
5
5
|
import {
|
|
6
6
|
attributeArrayFromAttributeDescription,
|
|
7
7
|
attributeDescriptionFromAttributeArray,
|
|
@@ -23,7 +23,6 @@ export const createAuthorityRootCertificate = async ({
|
|
|
23
23
|
throw new TypeError(`serial must be a number but received ${serialNumber}`)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const forge = await importNodeForge()
|
|
27
26
|
const { pki } = forge
|
|
28
27
|
const rootCertificateForgeObject = pki.createCertificate()
|
|
29
28
|
const keyPair = pki.rsa.generateKeyPair(2048) // TODO: use async version https://github.com/digitalbazaar/forge#rsa
|
|
@@ -86,7 +85,7 @@ export const createAuthorityRootCertificate = async ({
|
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
export const requestCertificateFromAuthority =
|
|
88
|
+
export const requestCertificateFromAuthority = ({
|
|
90
89
|
authorityCertificateForgeObject, // could be intermediate or root certificate authority
|
|
91
90
|
auhtorityCertificatePrivateKeyForgeObject,
|
|
92
91
|
serialNumber,
|
|
@@ -116,7 +115,6 @@ export const requestCertificateFromAuthority = async ({
|
|
|
116
115
|
)
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
const forge = await importNodeForge()
|
|
120
118
|
const { pki } = forge
|
|
121
119
|
const certificateForgeObject = pki.createCertificate()
|
|
122
120
|
const keyPair = pki.rsa.generateKeyPair(2048) // TODO: use async version https://github.com/digitalbazaar/forge#rsa
|
package/src/internal/forge.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
2
|
import { execSync } from "node:child_process"
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
5
4
|
import { UNICODE } from "@jsenv/log"
|
|
5
|
+
|
|
6
|
+
import { executeTrustQueryOnBrowserNSSDB } from "../nssdb_browser.js"
|
|
6
7
|
import {
|
|
7
8
|
nssCommandName,
|
|
8
9
|
detectIfNSSIsInstalled,
|
|
@@ -10,8 +11,6 @@ import {
|
|
|
10
11
|
getCertutilBinPath,
|
|
11
12
|
} from "./nss_linux.js"
|
|
12
13
|
|
|
13
|
-
import { executeTrustQueryOnBrowserNSSDB } from "../nssdb_browser.js"
|
|
14
|
-
|
|
15
14
|
export const executeTrustQueryOnChrome = ({
|
|
16
15
|
logger,
|
|
17
16
|
certificateCommonName,
|
|
@@ -48,10 +47,10 @@ export const executeTrustQueryOnChrome = ({
|
|
|
48
47
|
logger.debug(`${UNICODE.INFO} Chrome not detected`)
|
|
49
48
|
return false
|
|
50
49
|
},
|
|
51
|
-
browserNSSDBDirectoryUrl:
|
|
50
|
+
browserNSSDBDirectoryUrl: new URL(
|
|
52
51
|
".pki/nssdb",
|
|
53
52
|
assertAndNormalizeDirectoryUrl(process.env.HOME),
|
|
54
|
-
),
|
|
53
|
+
).href,
|
|
55
54
|
getBrowserClosedPromise: async () => {
|
|
56
55
|
if (!isChromeOpen()) {
|
|
57
56
|
return
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
2
|
import { execSync } from "node:child_process"
|
|
3
|
-
import {
|
|
3
|
+
import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
4
4
|
import { UNICODE } from "@jsenv/log"
|
|
5
5
|
|
|
6
|
+
import { executeTrustQueryOnBrowserNSSDB } from "../nssdb_browser.js"
|
|
6
7
|
import {
|
|
7
8
|
nssCommandName,
|
|
8
9
|
detectIfNSSIsInstalled,
|
|
@@ -10,8 +11,6 @@ import {
|
|
|
10
11
|
getCertutilBinPath,
|
|
11
12
|
} from "./nss_linux.js"
|
|
12
13
|
|
|
13
|
-
import { executeTrustQueryOnBrowserNSSDB } from "../nssdb_browser.js"
|
|
14
|
-
|
|
15
14
|
export const executeTrustQueryOnFirefox = ({
|
|
16
15
|
logger,
|
|
17
16
|
certificateCommonName,
|
|
@@ -48,10 +47,10 @@ export const executeTrustQueryOnFirefox = ({
|
|
|
48
47
|
logger.debug(`${UNICODE.INFO} Firefox not detected`)
|
|
49
48
|
return false
|
|
50
49
|
},
|
|
51
|
-
browserNSSDBDirectoryUrl:
|
|
50
|
+
browserNSSDBDirectoryUrl: new URL(
|
|
52
51
|
".mozilla/firefox/",
|
|
53
52
|
assertAndNormalizeDirectoryUrl(process.env.HOME),
|
|
54
|
-
),
|
|
53
|
+
).href,
|
|
55
54
|
getBrowserClosedPromise: async () => {
|
|
56
55
|
if (!isFirefoxOpen()) {
|
|
57
56
|
return
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync } from "node:fs"
|
|
6
|
-
import {
|
|
7
|
-
import { readFile
|
|
8
|
-
import { UNICODE } from "@jsenv/log"
|
|
6
|
+
import { fileURLToPath } from "node:url"
|
|
7
|
+
import { readFile } from "@jsenv/filesystem"
|
|
8
|
+
import { createDetailedMessage, UNICODE } from "@jsenv/log"
|
|
9
9
|
|
|
10
|
-
import { exec } from "@jsenv/https-local/src/internal/exec.js"
|
|
11
10
|
import {
|
|
12
11
|
VERB_CHECK_TRUST,
|
|
13
12
|
VERB_ADD_TRUST,
|
|
14
13
|
VERB_REMOVE_TRUST,
|
|
15
14
|
} from "../trust_query.js"
|
|
15
|
+
import { exec } from "../exec.js"
|
|
16
16
|
|
|
17
17
|
const REASON_NEW_AND_TRY_TO_TRUST_DISABLED =
|
|
18
18
|
"certificate is new and tryToTrust is disabled"
|
|
@@ -50,7 +50,7 @@ export const executeTrustQueryOnLinux = async ({
|
|
|
50
50
|
logger.debug(
|
|
51
51
|
`Searching certificate file at ${JSENV_AUTHORITY_ROOT_CERTIFICATE_PATH}...`,
|
|
52
52
|
)
|
|
53
|
-
const certificateFilePath =
|
|
53
|
+
const certificateFilePath = fileURLToPath(certificateFileUrl)
|
|
54
54
|
const certificateStatus = await getCertificateStatus({ certificate })
|
|
55
55
|
|
|
56
56
|
if (certificateStatus === "missing" || certificateStatus === "outdated") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UNICODE } from "@jsenv/log"
|
|
2
2
|
|
|
3
|
-
import { memoize } from "
|
|
4
|
-
import { exec } from "
|
|
3
|
+
import { memoize } from "../memoize.js"
|
|
4
|
+
import { exec } from "../exec.js"
|
|
5
5
|
|
|
6
6
|
export const nssCommandName = "libnss3-tools"
|
|
7
7
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
2
|
import { execSync } from "node:child_process"
|
|
3
|
-
import {
|
|
3
|
+
import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
4
4
|
import { UNICODE, createTaskLog } from "@jsenv/log"
|
|
5
5
|
|
|
6
6
|
import { executeTrustQueryOnBrowserNSSDB } from "../nssdb_browser.js"
|
|
@@ -47,10 +47,10 @@ export const executeTrustQueryOnFirefox = ({
|
|
|
47
47
|
logger.debug(`${UNICODE.INFO} firefox not detected`)
|
|
48
48
|
return false
|
|
49
49
|
},
|
|
50
|
-
browserNSSDBDirectoryUrl:
|
|
50
|
+
browserNSSDBDirectoryUrl: new URL(
|
|
51
51
|
`./Library/Application Support/Firefox/Profiles/`,
|
|
52
52
|
assertAndNormalizeDirectoryUrl(process.env.HOME),
|
|
53
|
-
),
|
|
53
|
+
).href,
|
|
54
54
|
getBrowserClosedPromise: async () => {
|
|
55
55
|
if (!isFirefoxOpen()) {
|
|
56
56
|
return
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
// https://ss64.com/osx/security.html
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { createDetailedMessage } from "@jsenv/
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
import { createDetailedMessage, UNICODE } from "@jsenv/log"
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { searchCertificateInCommandOutput } from "@jsenv/https-local/src/internal/search_certificate_in_command_output.js"
|
|
6
|
+
import { exec } from "../exec.js"
|
|
7
|
+
import { searchCertificateInCommandOutput } from "../search_certificate_in_command_output.js"
|
|
9
8
|
import {
|
|
10
9
|
VERB_CHECK_TRUST,
|
|
11
10
|
VERB_ADD_TRUST,
|
|
@@ -62,7 +61,7 @@ export const executeTrustQueryOnMacKeychain = async ({
|
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
const certificateFilePath =
|
|
64
|
+
const certificateFilePath = fileURLToPath(certificateFileUrl)
|
|
66
65
|
// https://ss64.com/osx/security-cert.html
|
|
67
66
|
const addTrustedCertCommand = `sudo security add-trusted-cert -d -r trustRoot -k ${systemKeychainPath} "${certificateFilePath}"`
|
|
68
67
|
logger.info(`Adding certificate to mac keychain...`)
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
resolveUrl,
|
|
4
|
-
urlToFileSystemPath,
|
|
5
|
-
} from "@jsenv/filesystem"
|
|
1
|
+
import { fileURLToPath } from "node:url"
|
|
2
|
+
import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
6
3
|
import { UNICODE } from "@jsenv/log"
|
|
7
4
|
|
|
8
|
-
import { memoize } from "
|
|
9
|
-
import { exec } from "
|
|
10
|
-
import { commandExists } from "
|
|
5
|
+
import { memoize } from "../memoize.js"
|
|
6
|
+
import { exec } from "../exec.js"
|
|
7
|
+
import { commandExists } from "../command.js"
|
|
11
8
|
|
|
12
9
|
export const nssCommandName = "nss"
|
|
13
10
|
|
|
@@ -31,8 +28,8 @@ export const getCertutilBinPath = memoize(async () => {
|
|
|
31
28
|
const nssCommandDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
32
29
|
brewCommandOutput.trim(),
|
|
33
30
|
)
|
|
34
|
-
const certutilBinUrl =
|
|
35
|
-
const certutilBinPath =
|
|
31
|
+
const certutilBinUrl = new URL(`./bin/certutil`, nssCommandDirectoryUrl).href
|
|
32
|
+
const certutilBinPath = fileURLToPath(certutilBinUrl)
|
|
36
33
|
return certutilBinPath
|
|
37
34
|
})
|
|
38
35
|
|
|
@@ -4,18 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { existsSync } from "node:fs"
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
resolveUrl,
|
|
12
|
-
urlToFilename,
|
|
13
|
-
urlToFileSystemPath,
|
|
14
|
-
} from "@jsenv/filesystem"
|
|
15
|
-
import { UNICODE } from "@jsenv/log"
|
|
7
|
+
import { fileURLToPath } from "node:url"
|
|
8
|
+
import { urlToFilename } from "@jsenv/urls"
|
|
9
|
+
import { createDetailedMessage, UNICODE } from "@jsenv/log"
|
|
10
|
+
import { assertAndNormalizeDirectoryUrl, collectFiles } from "@jsenv/filesystem"
|
|
16
11
|
|
|
17
|
-
import { exec } from "
|
|
18
|
-
import { searchCertificateInCommandOutput } from "
|
|
12
|
+
import { exec } from "./exec.js"
|
|
13
|
+
import { searchCertificateInCommandOutput } from "./search_certificate_in_command_output.js"
|
|
19
14
|
import { VERB_CHECK_TRUST, VERB_ADD_TRUST } from "./trust_query.js"
|
|
20
15
|
|
|
21
16
|
export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
@@ -131,7 +126,7 @@ export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
|
131
126
|
}
|
|
132
127
|
}
|
|
133
128
|
|
|
134
|
-
const certificateFilePath =
|
|
129
|
+
const certificateFilePath = fileURLToPath(certificateFileUrl)
|
|
135
130
|
const certutilBinPath = await getCertutilBinPath()
|
|
136
131
|
|
|
137
132
|
const checkNSSDB = async ({ NSSDBFileUrl }) => {
|
|
@@ -288,7 +283,7 @@ const findNSSDBFiles = async ({ logger, NSSDBDirectoryUrl }) => {
|
|
|
288
283
|
}
|
|
289
284
|
|
|
290
285
|
logger.debug(`Searching nss database files in directory...`)
|
|
291
|
-
const NSSDBDirectoryPath =
|
|
286
|
+
const NSSDBDirectoryPath = fileURLToPath(NSSDBDirectoryUrl)
|
|
292
287
|
const NSSDBDirectoryExists = existsSync(NSSDBDirectoryPath)
|
|
293
288
|
if (!NSSDBDirectoryExists) {
|
|
294
289
|
logger.info(
|
|
@@ -300,13 +295,9 @@ const findNSSDBFiles = async ({ logger, NSSDBDirectoryUrl }) => {
|
|
|
300
295
|
NSSDBDirectoryUrl = assertAndNormalizeDirectoryUrl(NSSDBDirectoryUrl)
|
|
301
296
|
const NSSDBFiles = await collectFiles({
|
|
302
297
|
directoryUrl: NSSDBDirectoryUrl,
|
|
303
|
-
|
|
304
|
-
isLegacyNSSDB: {
|
|
305
|
-
|
|
306
|
-
},
|
|
307
|
-
isModernNSSDB: {
|
|
308
|
-
"./**/cert9.db": true,
|
|
309
|
-
},
|
|
298
|
+
associations: {
|
|
299
|
+
isLegacyNSSDB: { "./**/cert8.db": true },
|
|
300
|
+
isModernNSSDB: { "./**/cert9.db": true },
|
|
310
301
|
},
|
|
311
302
|
predicate: ({ isLegacyNSSDB, isModernNSSDB }) =>
|
|
312
303
|
isLegacyNSSDB || isModernNSSDB,
|
|
@@ -324,7 +315,7 @@ const findNSSDBFiles = async ({ logger, NSSDBDirectoryUrl }) => {
|
|
|
324
315
|
`${UNICODE.OK} found ${fileCount} nss database file in ${NSSDBDirectoryUrl}`,
|
|
325
316
|
)
|
|
326
317
|
const files = NSSDBFiles.map((file) => {
|
|
327
|
-
return
|
|
318
|
+
return new URL(file.relativeUrl, NSSDBDirectoryUrl).href
|
|
328
319
|
})
|
|
329
320
|
NSSDirectoryCache[NSSDBDirectoryUrl] = files
|
|
330
321
|
return files
|
|
@@ -332,8 +323,8 @@ const findNSSDBFiles = async ({ logger, NSSDBDirectoryUrl }) => {
|
|
|
332
323
|
|
|
333
324
|
const getDirectoryArgFromNSSDBFileUrl = (NSSDBFileUrl) => {
|
|
334
325
|
const nssDBFilename = urlToFilename(NSSDBFileUrl)
|
|
335
|
-
const nssDBDirectoryUrl =
|
|
336
|
-
const nssDBDirectoryPath =
|
|
326
|
+
const nssDBDirectoryUrl = new URL("./", NSSDBFileUrl).href
|
|
327
|
+
const nssDBDirectoryPath = fileURLToPath(nssDBDirectoryUrl)
|
|
337
328
|
return nssDBFilename === "cert8.db"
|
|
338
329
|
? `"${nssDBDirectoryPath}"`
|
|
339
330
|
: `sql:"${nssDBDirectoryPath}"`
|
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module"
|
|
|
2
2
|
import { existsSync } from "node:fs"
|
|
3
3
|
import { UNICODE } from "@jsenv/log"
|
|
4
4
|
|
|
5
|
-
import { memoize } from "
|
|
5
|
+
import { memoize } from "../memoize.js"
|
|
6
6
|
|
|
7
7
|
const require = createRequire(import.meta.url)
|
|
8
8
|
|
|
@@ -7,7 +7,7 @@ import { createRequire } from "node:module"
|
|
|
7
7
|
import { existsSync } from "node:fs"
|
|
8
8
|
import { UNICODE } from "@jsenv/log"
|
|
9
9
|
|
|
10
|
-
import { memoize } from "
|
|
10
|
+
import { memoize } from "../memoize.js"
|
|
11
11
|
|
|
12
12
|
const require = createRequire(import.meta.url)
|
|
13
13
|
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/certutil
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { UNICODE } from "@jsenv/log"
|
|
6
|
+
import { fileURLToPath } from "node:url"
|
|
7
|
+
import { createDetailedMessage, UNICODE } from "@jsenv/log"
|
|
9
8
|
|
|
10
|
-
import { exec } from "
|
|
9
|
+
import { exec } from "../exec.js"
|
|
11
10
|
import {
|
|
12
11
|
VERB_CHECK_TRUST,
|
|
13
12
|
VERB_ADD_TRUST,
|
|
@@ -49,7 +48,7 @@ export const executeTrustQueryOnWindows = async ({
|
|
|
49
48
|
const certutilListCommand = `certutil -store -user root`
|
|
50
49
|
logger.debug(`${UNICODE.COMMAND} ${certutilListCommand}`)
|
|
51
50
|
const certutilListCommandOutput = await exec(certutilListCommand)
|
|
52
|
-
const certificateFilePath =
|
|
51
|
+
const certificateFilePath = fileURLToPath(certificateFileUrl)
|
|
53
52
|
|
|
54
53
|
// it's not super accurate and do not take into account if the cert is different
|
|
55
54
|
// but it's the best I could do with certutil command on windows
|
package/{main.js → src/main.js}
RENAMED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
export {
|
|
8
8
|
installCertificateAuthority,
|
|
9
9
|
uninstallCertificateAuthority,
|
|
10
|
-
} from "./
|
|
10
|
+
} from "./certificate_authority.js"
|
|
11
11
|
|
|
12
12
|
export {
|
|
13
13
|
createValidityDurationOfXYears,
|
|
14
14
|
createValidityDurationOfXDays,
|
|
15
|
-
} from "./
|
|
15
|
+
} from "./validity_duration.js"
|
|
16
16
|
|
|
17
|
-
export { verifyHostsFile } from "./
|
|
17
|
+
export { verifyHostsFile } from "./hosts_file_verif.js"
|
|
18
18
|
|
|
19
|
-
export { requestCertificateForLocalhost } from "./
|
|
19
|
+
export { requestCertificateForLocalhost } from "./certificate_for_localhost.js"
|
package/src/validity_duration.js
CHANGED
|
@@ -3,7 +3,6 @@ const MILLISECONDS_PER_YEAR = MILLISECONDS_PER_DAY * 365
|
|
|
3
3
|
|
|
4
4
|
export const verifyRootCertificateValidityDuration = (validityDurationInMs) => {
|
|
5
5
|
const durationInYears = validityDurationInMs / MILLISECONDS_PER_YEAR
|
|
6
|
-
|
|
7
6
|
if (durationInYears > 25) {
|
|
8
7
|
return {
|
|
9
8
|
ok: false,
|
|
@@ -13,26 +12,22 @@ export const verifyRootCertificateValidityDuration = (validityDurationInMs) => {
|
|
|
13
12
|
"https://serverfault.com/questions/847190/in-theory-could-a-ca-make-a-certificate-that-is-valid-for-arbitrarily-long",
|
|
14
13
|
}
|
|
15
14
|
}
|
|
16
|
-
|
|
17
15
|
return { ok: true }
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
export const verifyServerCertificateValidityDuration = (
|
|
21
|
-
|
|
19
|
+
validityDurationInMs,
|
|
22
20
|
) => {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (serverCertificateValidityDurationInDays > 397) {
|
|
21
|
+
const validityDurationInDays = validityDurationInMs / MILLISECONDS_PER_DAY
|
|
22
|
+
if (validityDurationInDays > 397) {
|
|
27
23
|
return {
|
|
28
24
|
ok: false,
|
|
29
25
|
maxAllowedValue: MILLISECONDS_PER_DAY * 397,
|
|
30
|
-
message: `certificate validity duration of ${
|
|
26
|
+
message: `certificate validity duration of ${validityDurationInMs} days is too much, using the max recommended duration: 397 days`,
|
|
31
27
|
details:
|
|
32
28
|
"https://www.globalsign.com/en/blog/maximum-ssltls-certificate-validity-now-one-year",
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
|
-
|
|
36
31
|
return { ok: true }
|
|
37
32
|
}
|
|
38
33
|
|