@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
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const IS_WINDOWS = process.platform === "win32"
|
|
2
2
|
|
|
3
3
|
// https://github.com/feross/hostile/blob/master/index.js
|
|
4
|
-
export const parseHosts = (
|
|
4
|
+
export const parseHosts = (
|
|
5
|
+
hosts,
|
|
6
|
+
{ EOL = IS_WINDOWS ? "\r\n" : "\n" } = {},
|
|
7
|
+
) => {
|
|
5
8
|
const lines = []
|
|
6
9
|
hosts.split(/\r?\n/).forEach((line) => {
|
|
7
10
|
const lineWithoutComments = line.replace(/#.*/, "")
|
|
@@ -22,7 +25,9 @@ export const parseHosts = (hosts, { EOL = IS_WINDOWS ? "\r\n" : "\n" } = {}) =>
|
|
|
22
25
|
if (line.type === "rule") {
|
|
23
26
|
const { ip, hostnames } = line
|
|
24
27
|
const existingHostnames = ipHostnames[ip]
|
|
25
|
-
ipHostnames[ip] = existingHostnames
|
|
28
|
+
ipHostnames[ip] = existingHostnames
|
|
29
|
+
? [...existingHostnames, ...hostnames]
|
|
30
|
+
: hostnames
|
|
26
31
|
}
|
|
27
32
|
})
|
|
28
33
|
return ipHostnames
|
|
@@ -40,7 +45,8 @@ export const parseHosts = (hosts, { EOL = IS_WINDOWS ? "\r\n" : "\n" } = {}) =>
|
|
|
40
45
|
|
|
41
46
|
const addIpHostname = (ip, host) => {
|
|
42
47
|
const alreadyThere = lines.some(
|
|
43
|
-
(line) =>
|
|
48
|
+
(line) =>
|
|
49
|
+
line.type === "rule" && line.ip === ip && line.hostnames.includes(host),
|
|
44
50
|
)
|
|
45
51
|
if (alreadyThere) {
|
|
46
52
|
return false
|
|
@@ -96,7 +102,9 @@ export const parseHosts = (hosts, { EOL = IS_WINDOWS ? "\r\n" : "\n" } = {}) =>
|
|
|
96
102
|
|
|
97
103
|
const asFileContent = () => {
|
|
98
104
|
let hostsFileContent = ""
|
|
99
|
-
const ips = lines
|
|
105
|
+
const ips = lines
|
|
106
|
+
.filter((line) => line.type === "rule")
|
|
107
|
+
.map((line) => line.ip)
|
|
100
108
|
const longestIp = ips.reduce((previous, ip) => {
|
|
101
109
|
const length = ip.length
|
|
102
110
|
return length > previous ? length : previous
|
|
@@ -107,7 +115,9 @@ export const parseHosts = (hosts, { EOL = IS_WINDOWS ? "\r\n" : "\n" } = {}) =>
|
|
|
107
115
|
const { ip, hostnames } = line
|
|
108
116
|
const ipLength = ip.length
|
|
109
117
|
const lengthDelta = longestIp - ipLength
|
|
110
|
-
hostsFileContent += `${ip}${" ".repeat(lengthDelta)} ${hostnames.join(
|
|
118
|
+
hostsFileContent += `${ip}${" ".repeat(lengthDelta)} ${hostnames.join(
|
|
119
|
+
" ",
|
|
120
|
+
)}`
|
|
111
121
|
} else {
|
|
112
122
|
hostsFileContent += line.value
|
|
113
123
|
}
|
|
@@ -9,9 +9,17 @@ export const writeHostsFile = async (
|
|
|
9
9
|
{ hostsFilePath = HOSTS_FILE_PATH, onBeforeExecCommand = () => {} } = {},
|
|
10
10
|
) => {
|
|
11
11
|
if (process.platform === "win32") {
|
|
12
|
-
return writeHostsFileOnWindows({
|
|
12
|
+
return writeHostsFileOnWindows({
|
|
13
|
+
hostsFileContent,
|
|
14
|
+
hostsFilePath,
|
|
15
|
+
onBeforeExecCommand,
|
|
16
|
+
})
|
|
13
17
|
}
|
|
14
|
-
return writeHostsFileOnLinuxOrMac({
|
|
18
|
+
return writeHostsFileOnLinuxOrMac({
|
|
19
|
+
hostsFileContent,
|
|
20
|
+
hostsFilePath,
|
|
21
|
+
onBeforeExecCommand,
|
|
22
|
+
})
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
const writeHostsFileOnLinuxOrMac = async ({
|
|
@@ -42,15 +50,19 @@ const writeHostsFileOnWindows = async ({
|
|
|
42
50
|
const sudoPrompt = require("sudo-prompt")
|
|
43
51
|
onBeforeExecCommand(updateHostsFileCommand)
|
|
44
52
|
await new Promise((resolve, reject) => {
|
|
45
|
-
sudoPrompt.exec(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
sudoPrompt.exec(
|
|
54
|
+
updateHostsFileCommand,
|
|
55
|
+
{ name: "write hosts" },
|
|
56
|
+
(error, stdout, stderr) => {
|
|
57
|
+
if (error) {
|
|
58
|
+
reject(error)
|
|
59
|
+
} else if (typeof stderr === "string" && stderr.trim().length > 0) {
|
|
60
|
+
reject(stderr)
|
|
61
|
+
} else {
|
|
62
|
+
resolve(stdout)
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
)
|
|
54
66
|
})
|
|
55
67
|
return
|
|
56
68
|
}
|
|
@@ -10,13 +10,25 @@ export const writeLineInHostsFile = async (
|
|
|
10
10
|
{ hostsFilePath = HOSTS_FILE_PATH, onBeforeExecCommand = () => {} } = {},
|
|
11
11
|
) => {
|
|
12
12
|
if (process.platform === "win32") {
|
|
13
|
-
return appendToHostsFileOnWindows({
|
|
13
|
+
return appendToHostsFileOnWindows({
|
|
14
|
+
lineToAppend,
|
|
15
|
+
hostsFilePath,
|
|
16
|
+
onBeforeExecCommand,
|
|
17
|
+
})
|
|
14
18
|
}
|
|
15
|
-
return appendToHostsFileOnLinuxOrMac({
|
|
19
|
+
return appendToHostsFileOnLinuxOrMac({
|
|
20
|
+
lineToAppend,
|
|
21
|
+
hostsFilePath,
|
|
22
|
+
onBeforeExecCommand,
|
|
23
|
+
})
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
// https://renenyffenegger.ch/notes/Windows/dirs/Windows/System32/cmd_exe/commands/echo/index
|
|
19
|
-
const appendToHostsFileOnWindows = async ({
|
|
27
|
+
const appendToHostsFileOnWindows = async ({
|
|
28
|
+
lineToAppend,
|
|
29
|
+
hostsFilePath,
|
|
30
|
+
onBeforeExecCommand,
|
|
31
|
+
}) => {
|
|
20
32
|
const hostsFileContent = await readFile(hostsFilePath)
|
|
21
33
|
const echoCommand =
|
|
22
34
|
hostsFileContent.length > 0 && !hostsFileContent.endsWith("\r\n")
|
|
@@ -30,15 +42,19 @@ const appendToHostsFileOnWindows = async ({ lineToAppend, hostsFilePath, onBefor
|
|
|
30
42
|
const sudoPrompt = require("sudo-prompt")
|
|
31
43
|
onBeforeExecCommand(updateHostsFileCommand)
|
|
32
44
|
await new Promise((resolve, reject) => {
|
|
33
|
-
sudoPrompt.exec(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
sudoPrompt.exec(
|
|
46
|
+
updateHostsFileCommand,
|
|
47
|
+
{ name: "append hosts" },
|
|
48
|
+
(error, stdout, stderr) => {
|
|
49
|
+
if (error) {
|
|
50
|
+
reject(error)
|
|
51
|
+
} else if (typeof stderr === "string" && stderr.trim().length > 0) {
|
|
52
|
+
reject(stderr)
|
|
53
|
+
} else {
|
|
54
|
+
resolve(stdout)
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
)
|
|
42
58
|
})
|
|
43
59
|
return
|
|
44
60
|
}
|
|
@@ -2,7 +2,11 @@ import { existsSync } from "node:fs"
|
|
|
2
2
|
import { execSync } from "node:child_process"
|
|
3
3
|
import { resolveUrl, assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
okSign,
|
|
7
|
+
infoSign,
|
|
8
|
+
warningSign,
|
|
9
|
+
} from "@jsenv/https-local/src/internal/logs.js"
|
|
6
10
|
import {
|
|
7
11
|
nssCommandName,
|
|
8
12
|
detectIfNSSIsInstalled,
|
|
@@ -57,7 +61,9 @@ export const executeTrustQueryOnChrome = ({
|
|
|
57
61
|
return
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
logger.warn(
|
|
64
|
+
logger.warn(
|
|
65
|
+
`${warningSign} waiting for you to close Chrome before resuming...`,
|
|
66
|
+
)
|
|
61
67
|
const next = async () => {
|
|
62
68
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
63
69
|
if (isChromeOpen()) {
|
|
@@ -2,7 +2,11 @@ import { existsSync } from "node:fs"
|
|
|
2
2
|
import { execSync } from "node:child_process"
|
|
3
3
|
import { resolveUrl, assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
okSign,
|
|
7
|
+
infoSign,
|
|
8
|
+
warningSign,
|
|
9
|
+
} from "@jsenv/https-local/src/internal/logs.js"
|
|
6
10
|
import {
|
|
7
11
|
nssCommandName,
|
|
8
12
|
detectIfNSSIsInstalled,
|
|
@@ -57,7 +61,9 @@ export const executeTrustQueryOnFirefox = ({
|
|
|
57
61
|
return
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
logger.warn(
|
|
64
|
+
logger.warn(
|
|
65
|
+
`${warningSign} waiting for you to close Firefox before resuming...`,
|
|
66
|
+
)
|
|
61
67
|
const next = async () => {
|
|
62
68
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
63
69
|
if (isFirefoxOpen()) {
|
|
@@ -6,18 +6,31 @@ import { existsSync } from "node:fs"
|
|
|
6
6
|
import { createDetailedMessage } from "@jsenv/logger"
|
|
7
7
|
import { readFile, urlToFileSystemPath } from "@jsenv/filesystem"
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
commandSign,
|
|
11
|
+
okSign,
|
|
12
|
+
infoSign,
|
|
13
|
+
failureSign,
|
|
14
|
+
} from "@jsenv/https-local/src/internal/logs.js"
|
|
10
15
|
import { exec } from "@jsenv/https-local/src/internal/exec.js"
|
|
11
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
VERB_CHECK_TRUST,
|
|
18
|
+
VERB_ADD_TRUST,
|
|
19
|
+
VERB_REMOVE_TRUST,
|
|
20
|
+
} from "../trust_query.js"
|
|
12
21
|
|
|
13
|
-
const REASON_NEW_AND_TRY_TO_TRUST_DISABLED =
|
|
22
|
+
const REASON_NEW_AND_TRY_TO_TRUST_DISABLED =
|
|
23
|
+
"certificate is new and tryToTrust is disabled"
|
|
14
24
|
const REASON_NOT_FOUND_IN_LINUX = `not found in linux store`
|
|
15
25
|
const REASON_OUTDATED_IN_LINUX = "certificate in linux store is outdated"
|
|
16
26
|
const REASON_FOUND_IN_LINUX = "found in linux store"
|
|
17
27
|
const REASON_ADD_COMMAND_FAILED = "command to add certificate to linux failed"
|
|
18
|
-
const REASON_ADD_COMMAND_COMPLETED =
|
|
19
|
-
|
|
20
|
-
const
|
|
28
|
+
const REASON_ADD_COMMAND_COMPLETED =
|
|
29
|
+
"command to add certificate to linux completed"
|
|
30
|
+
const REASON_REMOVE_COMMAND_FAILED =
|
|
31
|
+
"command to remove certificate from linux failed"
|
|
32
|
+
const REASON_REMOVE_COMMAND_COMPLETED =
|
|
33
|
+
"command to remove certificate from linux completed"
|
|
21
34
|
|
|
22
35
|
const LINUX_CERTIFICATE_AUTHORITIES_DIRECTORY_PATH = `/usr/local/share/ca-certificates/`
|
|
23
36
|
const JSENV_AUTHORITY_ROOT_CERTIFICATE_PATH = `${LINUX_CERTIFICATE_AUTHORITIES_DIRECTORY_PATH}https_local_root_certificate.crt`
|
|
@@ -39,7 +52,9 @@ export const executeTrustQueryOnLinux = async ({
|
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
logger.info(`Check if certificate is in linux...`)
|
|
42
|
-
logger.debug(
|
|
55
|
+
logger.debug(
|
|
56
|
+
`Searching certificate file at ${JSENV_AUTHORITY_ROOT_CERTIFICATE_PATH}...`,
|
|
57
|
+
)
|
|
43
58
|
const certificateFilePath = urlToFileSystemPath(certificateFileUrl)
|
|
44
59
|
const certificateStatus = await getCertificateStatus({ certificate })
|
|
45
60
|
|
|
@@ -53,7 +68,9 @@ export const executeTrustQueryOnLinux = async ({
|
|
|
53
68
|
return {
|
|
54
69
|
status: "not_trusted",
|
|
55
70
|
reason:
|
|
56
|
-
certificateStatus === "missing"
|
|
71
|
+
certificateStatus === "missing"
|
|
72
|
+
? REASON_NOT_FOUND_IN_LINUX
|
|
73
|
+
: REASON_OUTDATED_IN_LINUX,
|
|
57
74
|
}
|
|
58
75
|
}
|
|
59
76
|
|
|
@@ -73,9 +90,12 @@ export const executeTrustQueryOnLinux = async ({
|
|
|
73
90
|
} catch (e) {
|
|
74
91
|
console.error(e)
|
|
75
92
|
logger.error(
|
|
76
|
-
createDetailedMessage(
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
createDetailedMessage(
|
|
94
|
+
`${failureSign} failed to add certificate to linux`,
|
|
95
|
+
{
|
|
96
|
+
"certificate file": certificateFilePath,
|
|
97
|
+
},
|
|
98
|
+
),
|
|
79
99
|
)
|
|
80
100
|
return {
|
|
81
101
|
status: "not_trusted",
|
|
@@ -107,10 +127,13 @@ export const executeTrustQueryOnLinux = async ({
|
|
|
107
127
|
}
|
|
108
128
|
} catch (e) {
|
|
109
129
|
logger.error(
|
|
110
|
-
createDetailedMessage(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
130
|
+
createDetailedMessage(
|
|
131
|
+
`${failureSign} failed to remove certificate from linux`,
|
|
132
|
+
{
|
|
133
|
+
"error stack": e.stack,
|
|
134
|
+
"certificate file": JSENV_AUTHORITY_ROOT_CERTIFICATE_PATH,
|
|
135
|
+
},
|
|
136
|
+
),
|
|
114
137
|
)
|
|
115
138
|
return {
|
|
116
139
|
status: "unknown",
|
|
@@ -124,7 +147,9 @@ const getCertificateStatus = async ({ certificate }) => {
|
|
|
124
147
|
if (!certificateInStore) {
|
|
125
148
|
return "missing"
|
|
126
149
|
}
|
|
127
|
-
const certificateInLinuxStore = await readFile(
|
|
150
|
+
const certificateInLinuxStore = await readFile(
|
|
151
|
+
JSENV_AUTHORITY_ROOT_CERTIFICATE_PATH,
|
|
152
|
+
)
|
|
128
153
|
if (certificateInLinuxStore !== certificate) {
|
|
129
154
|
return "outdated"
|
|
130
155
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { memoize } from "@jsenv/https-local/src/internal/memoize.js"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
commandSign,
|
|
4
|
+
infoSign,
|
|
5
|
+
okSign,
|
|
6
|
+
} from "@jsenv/https-local/src/internal/logs.js"
|
|
3
7
|
import { exec } from "@jsenv/https-local/src/internal/exec.js"
|
|
4
8
|
|
|
5
9
|
export const nssCommandName = "libnss3-tools"
|
|
@@ -24,10 +28,12 @@ export const getCertutilBinPath = () => "certutil"
|
|
|
24
28
|
|
|
25
29
|
export const getNSSDynamicInstallInfo = ({ logger }) => {
|
|
26
30
|
return {
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
isInstallable: true,
|
|
32
|
+
install: async () => {
|
|
29
33
|
const aptInstallCommand = `sudo apt install libnss3-tools`
|
|
30
|
-
logger.info(
|
|
34
|
+
logger.info(
|
|
35
|
+
`"libnss3-tools" is not installed, trying to install "libnss3-tools"`,
|
|
36
|
+
)
|
|
31
37
|
logger.info(`${commandSign} ${aptInstallCommand}`)
|
|
32
38
|
await exec(aptInstallCommand)
|
|
33
39
|
},
|
|
@@ -2,7 +2,11 @@ import { existsSync } from "node:fs"
|
|
|
2
2
|
import { execSync } from "node:child_process"
|
|
3
3
|
import { resolveUrl, assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
okSign,
|
|
7
|
+
infoSign,
|
|
8
|
+
warningSign,
|
|
9
|
+
} from "@jsenv/https-local/src/internal/logs.js"
|
|
6
10
|
|
|
7
11
|
import { executeTrustQueryOnBrowserNSSDB } from "../nssdb_browser.js"
|
|
8
12
|
import {
|
|
@@ -57,7 +61,9 @@ export const executeTrustQueryOnFirefox = ({
|
|
|
57
61
|
return
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
logger.warn(
|
|
64
|
+
logger.warn(
|
|
65
|
+
`${warningSign} waiting for you to close firefox before resuming...`,
|
|
66
|
+
)
|
|
61
67
|
const next = async () => {
|
|
62
68
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
63
69
|
if (isFirefoxOpen()) {
|
|
@@ -3,15 +3,26 @@
|
|
|
3
3
|
import { urlToFileSystemPath } from "@jsenv/filesystem"
|
|
4
4
|
import { createDetailedMessage } from "@jsenv/logger"
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
commandSign,
|
|
8
|
+
okSign,
|
|
9
|
+
infoSign,
|
|
10
|
+
failureSign,
|
|
11
|
+
} from "@jsenv/https-local/src/internal/logs.js"
|
|
7
12
|
import { exec } from "@jsenv/https-local/src/internal/exec.js"
|
|
8
13
|
import { searchCertificateInCommandOutput } from "@jsenv/https-local/src/internal/search_certificate_in_command_output.js"
|
|
9
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
VERB_CHECK_TRUST,
|
|
16
|
+
VERB_ADD_TRUST,
|
|
17
|
+
VERB_REMOVE_TRUST,
|
|
18
|
+
} from "../trust_query.js"
|
|
10
19
|
|
|
11
|
-
const REASON_NEW_AND_TRY_TO_TRUST_DISABLED =
|
|
20
|
+
const REASON_NEW_AND_TRY_TO_TRUST_DISABLED =
|
|
21
|
+
"certificate is new and tryToTrust is disabled"
|
|
12
22
|
const REASON_NOT_IN_KEYCHAIN = "certificate not found in mac keychain"
|
|
13
23
|
const REASON_IN_KEYCHAIN = "certificate found in mac keychain"
|
|
14
|
-
const REASON_ADD_TO_KEYCHAIN_COMMAND_FAILED =
|
|
24
|
+
const REASON_ADD_TO_KEYCHAIN_COMMAND_FAILED =
|
|
25
|
+
"command to add certificate in mac keychain failed"
|
|
15
26
|
const REASON_ADD_TO_KEYCHAIN_COMMAND_COMPLETED =
|
|
16
27
|
"command to add certificate in mac keychain completed"
|
|
17
28
|
const REASON_REMOVE_FROM_KEYCHAIN_COMMAND_FAILED =
|
|
@@ -70,10 +81,13 @@ export const executeTrustQueryOnMacKeychain = async ({
|
|
|
70
81
|
}
|
|
71
82
|
} catch (e) {
|
|
72
83
|
logger.error(
|
|
73
|
-
createDetailedMessage(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
createDetailedMessage(
|
|
85
|
+
`${failureSign} failed to add certificate to mac keychain`,
|
|
86
|
+
{
|
|
87
|
+
"error stack": e.stack,
|
|
88
|
+
"certificate file": certificateFilePath,
|
|
89
|
+
},
|
|
90
|
+
),
|
|
77
91
|
)
|
|
78
92
|
return {
|
|
79
93
|
status: "not_trusted",
|
|
@@ -107,10 +121,13 @@ export const executeTrustQueryOnMacKeychain = async ({
|
|
|
107
121
|
}
|
|
108
122
|
} catch (e) {
|
|
109
123
|
logger.error(
|
|
110
|
-
createDetailedMessage(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
createDetailedMessage(
|
|
125
|
+
`${failureSign} failed to remove certificate from mac keychain`,
|
|
126
|
+
{
|
|
127
|
+
"error stack": e.stack,
|
|
128
|
+
"certificate file url": certificateFileUrl,
|
|
129
|
+
},
|
|
130
|
+
),
|
|
114
131
|
)
|
|
115
132
|
return {
|
|
116
133
|
status: "not_trusted",
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
assertAndNormalizeDirectoryUrl,
|
|
3
|
+
resolveUrl,
|
|
4
|
+
urlToFileSystemPath,
|
|
5
|
+
} from "@jsenv/filesystem"
|
|
2
6
|
|
|
3
7
|
import { memoize } from "@jsenv/https-local/src/internal/memoize.js"
|
|
4
8
|
import { exec } from "@jsenv/https-local/src/internal/exec.js"
|
|
@@ -24,7 +28,9 @@ export const detectIfNSSIsInstalled = async ({ logger }) => {
|
|
|
24
28
|
export const getCertutilBinPath = memoize(async () => {
|
|
25
29
|
const brewCommand = `brew --prefix nss`
|
|
26
30
|
const brewCommandOutput = await exec(brewCommand)
|
|
27
|
-
const nssCommandDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
31
|
+
const nssCommandDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
32
|
+
brewCommandOutput.trim(),
|
|
33
|
+
)
|
|
28
34
|
const certutilBinUrl = resolveUrl(`./bin/certutil`, nssCommandDirectoryUrl)
|
|
29
35
|
const certutilBinPath = urlToFileSystemPath(certutilBinUrl)
|
|
30
36
|
return certutilBinPath
|
|
@@ -33,11 +39,13 @@ export const getCertutilBinPath = memoize(async () => {
|
|
|
33
39
|
export const getNSSDynamicInstallInfo = () => {
|
|
34
40
|
return {
|
|
35
41
|
isInstallable: commandExists("brew"),
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
notInstallableReason: `"brew" is not available`,
|
|
43
|
+
suggestion: `install "brew" on this mac`,
|
|
44
|
+
install: async ({ logger }) => {
|
|
39
45
|
const brewInstallCommand = `brew install nss`
|
|
40
|
-
logger.info(
|
|
46
|
+
logger.info(
|
|
47
|
+
`"nss" is not installed, trying to install "nss" via Homebrew`,
|
|
48
|
+
)
|
|
41
49
|
logger.info(`> ${brewInstallCommand}`)
|
|
42
50
|
await exec(brewInstallCommand)
|
|
43
51
|
},
|
|
@@ -64,15 +64,15 @@ export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
|
64
64
|
const cannotCheckMessage = `${failureSign} cannot check if certificate is in ${browserName}`
|
|
65
65
|
if (!nssIsInstalled) {
|
|
66
66
|
if (verb === VERB_ADD_TRUST) {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
if (!nssIsInstallable) {
|
|
67
|
+
const nssDynamicInstallInfo = await getNSSDynamicInstallInfo({ logger })
|
|
68
|
+
if (!nssDynamicInstallInfo.isInstallable) {
|
|
70
69
|
const reason = `"${nssCommandName}" is not installed and not cannot be installed`
|
|
71
70
|
logger.warn(
|
|
72
71
|
createDetailedMessage(cannotCheckMessage, {
|
|
73
72
|
reason,
|
|
74
|
-
"reason it cannot be installed":
|
|
75
|
-
|
|
73
|
+
"reason it cannot be installed":
|
|
74
|
+
nssDynamicInstallInfo.notInstallableReason,
|
|
75
|
+
"suggested solution": nssDynamicInstallInfo.suggestion,
|
|
76
76
|
}),
|
|
77
77
|
)
|
|
78
78
|
return {
|
|
@@ -96,7 +96,7 @@ export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
try {
|
|
99
|
-
await
|
|
99
|
+
await nssDynamicInstallInfo.install({ logger })
|
|
100
100
|
} catch (e) {
|
|
101
101
|
logger.error(
|
|
102
102
|
createDetailedMessage(cannotCheckMessage, {
|
|
@@ -314,16 +314,21 @@ const findNSSDBFiles = async ({ logger, NSSDBDirectoryUrl }) => {
|
|
|
314
314
|
"./**/cert9.db": true,
|
|
315
315
|
},
|
|
316
316
|
},
|
|
317
|
-
predicate: ({ isLegacyNSSDB, isModernNSSDB }) =>
|
|
317
|
+
predicate: ({ isLegacyNSSDB, isModernNSSDB }) =>
|
|
318
|
+
isLegacyNSSDB || isModernNSSDB,
|
|
318
319
|
})
|
|
319
320
|
const fileCount = NSSDBFiles.length
|
|
320
321
|
if (fileCount === 0) {
|
|
321
|
-
logger.warn(
|
|
322
|
+
logger.warn(
|
|
323
|
+
`${warningSign} could not find nss database file in ${NSSDBDirectoryUrl}`,
|
|
324
|
+
)
|
|
322
325
|
NSSDirectoryCache[NSSDBDirectoryUrl] = []
|
|
323
326
|
return []
|
|
324
327
|
}
|
|
325
328
|
|
|
326
|
-
logger.debug(
|
|
329
|
+
logger.debug(
|
|
330
|
+
`${okSign} found ${fileCount} nss database file in ${NSSDBDirectoryUrl}`,
|
|
331
|
+
)
|
|
327
332
|
const files = NSSDBFiles.map((file) => {
|
|
328
333
|
return resolveUrl(file.relativeUrl, NSSDBDirectoryUrl)
|
|
329
334
|
})
|
|
@@ -335,7 +340,9 @@ const getDirectoryArgFromNSSDBFileUrl = (NSSDBFileUrl) => {
|
|
|
335
340
|
const nssDBFilename = urlToFilename(NSSDBFileUrl)
|
|
336
341
|
const nssDBDirectoryUrl = resolveUrl("./", NSSDBFileUrl)
|
|
337
342
|
const nssDBDirectoryPath = urlToFileSystemPath(nssDBDirectoryUrl)
|
|
338
|
-
return nssDBFilename === "cert8.db"
|
|
343
|
+
return nssDBFilename === "cert8.db"
|
|
344
|
+
? `"${nssDBDirectoryPath}"`
|
|
345
|
+
: `sql:"${nssDBDirectoryPath}"`
|
|
339
346
|
}
|
|
340
347
|
|
|
341
348
|
const execCertutilCommmand = async (command) => {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export const searchCertificateInCommandOutput = (
|
|
1
|
+
export const searchCertificateInCommandOutput = (
|
|
2
|
+
commandOutput,
|
|
3
|
+
certificateAsPEM,
|
|
4
|
+
) => {
|
|
2
5
|
commandOutput = commandOutput.replace(/\r\n/g, "\n").trim()
|
|
3
6
|
certificateAsPEM = certificateAsPEM.replace(/\r\n/g, "\n").trim()
|
|
4
7
|
return commandOutput.includes(certificateAsPEM)
|
|
@@ -6,7 +6,9 @@ const platformTrustInfo = {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export const executeTrustQuery = ({ logger }) => {
|
|
9
|
-
logger.warn(
|
|
9
|
+
logger.warn(
|
|
10
|
+
`${warningSign} platform not supported, cannot execute trust query`,
|
|
11
|
+
)
|
|
10
12
|
return {
|
|
11
13
|
platform: platformTrustInfo,
|
|
12
14
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export const formatStillValid = ({ certificateName, validityRemainingMs }) => {
|
|
2
|
-
return `${certificateName} still valid for ${formatDuration(
|
|
2
|
+
return `${certificateName} still valid for ${formatDuration(
|
|
3
|
+
validityRemainingMs,
|
|
4
|
+
)}`
|
|
3
5
|
}
|
|
4
6
|
|
|
5
7
|
export const formatAboutToExpire = ({
|
|
@@ -40,16 +40,18 @@ const detectChrome = memoize(({ logger }) => {
|
|
|
40
40
|
`${process.env.ProgramFiles}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
41
41
|
`${process.env["ProgramFiles(x86)"]}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
42
42
|
]
|
|
43
|
-
const someExecutableFound = executableCandidates.some(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
const someExecutableFound = executableCandidates.some(
|
|
44
|
+
(chromeExecutablePathCandidate) => {
|
|
45
|
+
if (existsSync(chromeExecutablePathCandidate)) {
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
which.sync(chromeExecutablePathCandidate)
|
|
50
|
+
return true
|
|
51
|
+
} catch (e) {}
|
|
52
|
+
return false
|
|
53
|
+
},
|
|
54
|
+
)
|
|
53
55
|
if (someExecutableFound) {
|
|
54
56
|
logger.debug(`${okSign} Chrome detected`)
|
|
55
57
|
return true
|
|
@@ -58,16 +58,18 @@ const detectFirefox = memoize(({ logger }) => {
|
|
|
58
58
|
`${process.env.ProgramFiles}\\Mozilla Firefox\\firefox.exe`,
|
|
59
59
|
`${process.env["ProgramFiles(x86)"]}\\Mozilla Firefox\\firefox.exe`,
|
|
60
60
|
]
|
|
61
|
-
const someExecutableFound = executableCandidates.some(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
const someExecutableFound = executableCandidates.some(
|
|
62
|
+
(firefoxExecutablePathCandidate) => {
|
|
63
|
+
if (existsSync(firefoxExecutablePathCandidate)) {
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
which.sync(firefoxExecutablePathCandidate)
|
|
68
|
+
return true
|
|
69
|
+
} catch (e) {}
|
|
70
|
+
return false
|
|
71
|
+
},
|
|
72
|
+
)
|
|
71
73
|
if (someExecutableFound) {
|
|
72
74
|
logger.debug(`${okSign} Firefox detected`)
|
|
73
75
|
return true
|