@jsenv/https-local 1.0.0 → 1.0.5
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 +56 -65
- package/package.json +27 -33
- package/src/certificate_authority.js +67 -26
- package/src/certificate_for_localhost.js +27 -11
- 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 -11
- 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 +45 -20
- package/src/internal/linux/nss_linux.js +8 -2
- 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 +12 -4
- package/src/internal/nssdb_browser.js +17 -6
- 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/jsenvParameters.js +1 -1
- package/src/validity_duration.js +7 -3
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
|
|
@@ -7,17 +11,17 @@ export const getCertificateAuthorityFileUrls = () => {
|
|
|
7
11
|
const applicationDirectoryUrl = getJsenvApplicationDirectoryUrl()
|
|
8
12
|
|
|
9
13
|
const certificateAuthorityJsonFileUrl = new URL(
|
|
10
|
-
"./
|
|
14
|
+
"./https_local_certificate_authority.json",
|
|
11
15
|
applicationDirectoryUrl,
|
|
12
16
|
)
|
|
13
17
|
|
|
14
18
|
const rootCertificateFileUrl = new URL(
|
|
15
|
-
"./
|
|
19
|
+
"./https_local_root_certificate.crt",
|
|
16
20
|
applicationDirectoryUrl,
|
|
17
21
|
)
|
|
18
22
|
|
|
19
23
|
const rootCertificatePrivateKeyFileUrl = resolveUrl(
|
|
20
|
-
"./
|
|
24
|
+
"./https_local_root_certificate.key",
|
|
21
25
|
applicationDirectoryUrl,
|
|
22
26
|
)
|
|
23
27
|
|
|
@@ -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,
|
|
@@ -52,7 +62,7 @@ const getJsenvApplicationDirectoryUrl = () => {
|
|
|
52
62
|
|
|
53
63
|
if (platform === "darwin") {
|
|
54
64
|
return resolveUrl(
|
|
55
|
-
`./Library/Application Support/
|
|
65
|
+
`./Library/Application Support/https_local/`,
|
|
56
66
|
assertAndNormalizeDirectoryUrl(process.env.HOME),
|
|
57
67
|
)
|
|
58
68
|
}
|
|
@@ -60,12 +70,12 @@ const getJsenvApplicationDirectoryUrl = () => {
|
|
|
60
70
|
if (platform === "linux") {
|
|
61
71
|
if (process.env.XDG_CONFIG_HOME) {
|
|
62
72
|
return resolveUrl(
|
|
63
|
-
`./
|
|
73
|
+
`./https_local/`,
|
|
64
74
|
assertAndNormalizeDirectoryUrl(process.env.XDG_CONFIG_HOME),
|
|
65
75
|
)
|
|
66
76
|
}
|
|
67
77
|
return resolveUrl(
|
|
68
|
-
`./.config/
|
|
78
|
+
`./.config/https_local/`,
|
|
69
79
|
assertAndNormalizeDirectoryUrl(process.env.HOME),
|
|
70
80
|
)
|
|
71
81
|
}
|
|
@@ -73,13 +83,13 @@ const getJsenvApplicationDirectoryUrl = () => {
|
|
|
73
83
|
if (platform === "win32") {
|
|
74
84
|
if (process.env.LOCALAPPDATA) {
|
|
75
85
|
return resolveUrl(
|
|
76
|
-
`./
|
|
86
|
+
`./https_local/`,
|
|
77
87
|
assertAndNormalizeDirectoryUrl(process.env.LOCALAPPDATA),
|
|
78
88
|
)
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
return resolveUrl(
|
|
82
|
-
`./Local Settings/Application Data/
|
|
92
|
+
`./Local Settings/Application Data/https_local/`,
|
|
83
93
|
assertAndNormalizeDirectoryUrl(process.env.USERPROFILE),
|
|
84
94
|
)
|
|
85
95
|
}
|
|
@@ -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,
|
|
@@ -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,21 +6,34 @@ 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
|
-
const
|
|
36
|
+
const JSENV_AUTHORITY_ROOT_CERTIFICATE_PATH = `${LINUX_CERTIFICATE_AUTHORITIES_DIRECTORY_PATH}https_local_root_certificate.crt`
|
|
24
37
|
|
|
25
38
|
export const executeTrustQueryOnLinux = async ({
|
|
26
39
|
logger,
|
|
@@ -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,11 +68,13 @@ 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
|
|
|
60
|
-
const copyCertificateCommand = `sudo /bin/cp -f "${certificateFilePath}" ${
|
|
77
|
+
const copyCertificateCommand = `sudo /bin/cp -f "${certificateFilePath}" ${JSENV_AUTHORITY_ROOT_CERTIFICATE_PATH}`
|
|
61
78
|
const updateCertificateCommand = `sudo update-ca-certificates`
|
|
62
79
|
logger.info(`Adding certificate to linux...`)
|
|
63
80
|
try {
|
|
@@ -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",
|
|
@@ -93,7 +113,7 @@ export const executeTrustQueryOnLinux = async ({
|
|
|
93
113
|
}
|
|
94
114
|
|
|
95
115
|
logger.info(`Removing certificate from linux...`)
|
|
96
|
-
const removeCertificateCommand = `sudo rm ${
|
|
116
|
+
const removeCertificateCommand = `sudo rm ${JSENV_AUTHORITY_ROOT_CERTIFICATE_PATH}`
|
|
97
117
|
const updateCertificateCommand = `sudo update-ca-certificates`
|
|
98
118
|
try {
|
|
99
119
|
logger.info(`${commandSign} ${removeCertificateCommand}`)
|
|
@@ -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",
|
|
@@ -120,11 +143,13 @@ export const executeTrustQueryOnLinux = async ({
|
|
|
120
143
|
}
|
|
121
144
|
|
|
122
145
|
const getCertificateStatus = async ({ certificate }) => {
|
|
123
|
-
const certificateInStore = existsSync(
|
|
146
|
+
const certificateInStore = existsSync(JSENV_AUTHORITY_ROOT_CERTIFICATE_PATH)
|
|
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"
|
|
@@ -27,7 +31,9 @@ export const getNSSDynamicInstallInfo = ({ logger }) => {
|
|
|
27
31
|
nssIsInstallable: true,
|
|
28
32
|
nssInstall: 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()) {
|