@jsenv/https-local 1.1.0 → 3.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 +15 -17
- package/package.json +17 -21
- package/src/certificate_authority.js +6 -8
- package/src/{certificate_for_localhost.js → certificate_request.js} +33 -41
- package/src/hosts_file_verif.js +1 -2
- package/src/internal/certificate_generator.js +2 -4
- package/src/internal/forge.js +1 -3
- package/src/internal/linux/chrome_linux.js +2 -3
- package/src/internal/linux/firefox_linux.js +1 -2
- package/src/internal/linux/linux_trust_store.js +8 -4
- package/src/internal/linux/nss_linux.js +2 -2
- package/src/internal/mac/chrome_mac.js +1 -1
- package/src/internal/mac/mac_keychain.js +46 -32
- package/src/internal/mac/nss_mac.js +3 -3
- package/src/internal/nssdb_browser.js +10 -7
- package/src/internal/trust_query.js +1 -2
- 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 +8 -4
- package/{main.js → src/main.js} +4 -4
- package/src/validity_duration.js +4 -9
package/README.md
CHANGED
|
@@ -61,19 +61,18 @@ node ./install_certificate_authority.mjs
|
|
|
61
61
|
*
|
|
62
62
|
* > node ./install_certificate_authority.mjs
|
|
63
63
|
*
|
|
64
|
-
* Read more in https://github.com/jsenv/https-local#
|
|
64
|
+
* Read more in https://github.com/jsenv/https-local#requestCertificate
|
|
65
65
|
*/
|
|
66
66
|
|
|
67
67
|
import { createServer } from "node:https"
|
|
68
|
-
import {
|
|
68
|
+
import { requestCertificate } from "@jsenv/https-local"
|
|
69
69
|
|
|
70
|
-
const {
|
|
71
|
-
await requestCertificateForLocalhost()
|
|
70
|
+
const { certificate, privateKey } = requestCertificate()
|
|
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"
|
|
@@ -100,10 +99,10 @@ The rest of the documentation goes into details.
|
|
|
100
99
|
|
|
101
100
|
# Certificate expiration
|
|
102
101
|
|
|
103
|
-
| Certificate | Expires after | How to renew?
|
|
104
|
-
| ----------- | ------------- |
|
|
105
|
-
| server | 1 year | Re-run
|
|
106
|
-
| authority | 20 year | Re-run _installCertificateAuthority_
|
|
102
|
+
| Certificate | Expires after | How to renew? |
|
|
103
|
+
| ----------- | ------------- | ------------------------------------ |
|
|
104
|
+
| server | 1 year | Re-run _requestCertificate_ |
|
|
105
|
+
| authority | 20 year | Re-run _installCertificateAuthority_ |
|
|
107
106
|
|
|
108
107
|
The **server** certificate expires after one year which is the maximum duration allowed by web browsers. In the unlikely scenario where your local server is running for more than a year without interruption, restart it and you're good for one more year.
|
|
109
108
|
|
|
@@ -353,18 +352,17 @@ Check if certificate is trusted by firefox...
|
|
|
353
352
|
|
|
354
353
|
</details>
|
|
355
354
|
|
|
356
|
-
#
|
|
355
|
+
# requestCertificate
|
|
357
356
|
|
|
358
|
-
|
|
357
|
+
_requestCertificate_ function returns a certificate and private key that can be used to start a server in HTTPS.
|
|
359
358
|
|
|
360
359
|
```js
|
|
361
360
|
import { createServer } from "node:https"
|
|
362
|
-
import {
|
|
361
|
+
import { requestCertificate } from "@jsenv/https-local"
|
|
363
362
|
|
|
364
|
-
const {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
})
|
|
363
|
+
const { certificate, privateKey } = requestCertificate({
|
|
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": "3.0.0",
|
|
4
4
|
"description": "A programmatic way to generate locally trusted certificates",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -21,14 +21,13 @@
|
|
|
21
21
|
"type": "module",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
24
|
-
"import": "./main.js"
|
|
24
|
+
"import": "./src/main.js"
|
|
25
25
|
},
|
|
26
26
|
"./*": "./*"
|
|
27
27
|
},
|
|
28
|
-
"main": "./main.js",
|
|
28
|
+
"main": "./src/main.js",
|
|
29
29
|
"files": [
|
|
30
|
-
"/src/"
|
|
31
|
-
"/main.js"
|
|
30
|
+
"/src/"
|
|
32
31
|
],
|
|
33
32
|
"scripts": {
|
|
34
33
|
"eslint": "npx eslint . --ext=.js,.mjs,.cjs",
|
|
@@ -48,28 +47,25 @@
|
|
|
48
47
|
"playwright-install": "npx playwright install-deps && npx playwright install"
|
|
49
48
|
},
|
|
50
49
|
"dependencies": {
|
|
51
|
-
"@jsenv/filesystem": "4.
|
|
52
|
-
"@jsenv/log": "1.
|
|
53
|
-
"@jsenv/
|
|
54
|
-
"@jsenv/urls": "1.1.2",
|
|
50
|
+
"@jsenv/filesystem": "4.1.2",
|
|
51
|
+
"@jsenv/log": "3.1.0",
|
|
52
|
+
"@jsenv/urls": "1.2.7",
|
|
55
53
|
"command-exists": "1.2.9",
|
|
56
54
|
"node-forge": "1.3.1",
|
|
57
55
|
"sudo-prompt": "9.2.1",
|
|
58
56
|
"which": "2.0.2"
|
|
59
57
|
},
|
|
60
58
|
"devDependencies": {
|
|
61
|
-
"@jsenv/assert": "2.
|
|
62
|
-
"@jsenv/core": "
|
|
63
|
-
"@jsenv/eslint-config": "16.
|
|
64
|
-
"@jsenv/eslint-import-resolver": "0.
|
|
65
|
-
"@jsenv/github-release-package": "1.
|
|
66
|
-
"@jsenv/
|
|
67
|
-
"@jsenv/
|
|
68
|
-
"
|
|
69
|
-
"@jsenv/performance-impact": "2.2.11",
|
|
70
|
-
"eslint": "8.17.0",
|
|
59
|
+
"@jsenv/assert": "2.6.0",
|
|
60
|
+
"@jsenv/core": "28.0.2",
|
|
61
|
+
"@jsenv/eslint-config": "16.2.1",
|
|
62
|
+
"@jsenv/eslint-import-resolver": "0.3.0",
|
|
63
|
+
"@jsenv/github-release-package": "1.5.0",
|
|
64
|
+
"@jsenv/package-publish": "1.10.0",
|
|
65
|
+
"@jsenv/performance-impact": "2.3.0",
|
|
66
|
+
"eslint": "8.21.0",
|
|
71
67
|
"eslint-plugin-import": "2.26.0",
|
|
72
|
-
"playwright": "1.
|
|
73
|
-
"prettier": "2.7.
|
|
68
|
+
"playwright": "1.24.2",
|
|
69
|
+
"prettier": "2.7.1"
|
|
74
70
|
}
|
|
75
71
|
}
|
|
@@ -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...`)
|
|
@@ -232,13 +231,12 @@ export const installCertificateAuthority = async ({
|
|
|
232
231
|
const rootCertificatePrivateKeyForgeObject = pki.privateKeyFromPem(
|
|
233
232
|
rootCertificatePrivateKey,
|
|
234
233
|
)
|
|
235
|
-
|
|
236
234
|
const trustInfo = await platformMethods.executeTrustQuery({
|
|
237
235
|
logger,
|
|
238
236
|
certificateCommonName,
|
|
239
237
|
certificateFileUrl: rootCertificateFileInfo.url,
|
|
240
238
|
certificate: rootCertificate,
|
|
241
|
-
verb: tryToTrust ? "
|
|
239
|
+
verb: tryToTrust ? "ENSURE_TRUST" : "CHECK_TRUST",
|
|
242
240
|
NSSDynamicInstall,
|
|
243
241
|
})
|
|
244
242
|
|
|
@@ -323,7 +321,7 @@ export const uninstallCertificateAuthority = async ({
|
|
|
323
321
|
const rootCertificate = await readFile(rootCertificateFileInfo.url, {
|
|
324
322
|
as: "string",
|
|
325
323
|
})
|
|
326
|
-
const { pki } =
|
|
324
|
+
const { pki } = forge
|
|
327
325
|
const rootCertificateForgeObject = pki.certificateFromPem(rootCertificate)
|
|
328
326
|
const rootCertificateCommonName = attributeDescriptionFromAttributeArray(
|
|
329
327
|
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
|
|
14
|
+
export const requestCertificate = ({
|
|
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,
|
|
@@ -48,7 +47,7 @@ export const requestCertificateForLocalhost = async ({
|
|
|
48
47
|
} = getAuthorityFileInfos()
|
|
49
48
|
if (!rootCertificateFileInfo.exists) {
|
|
50
49
|
throw new Error(
|
|
51
|
-
`Certificate authority not found, "installCertificateAuthority" must be called before "
|
|
50
|
+
`Certificate authority not found, "installCertificateAuthority" must be called before "requestServerCertificate"`,
|
|
52
51
|
)
|
|
53
52
|
}
|
|
54
53
|
if (!rootCertificatePrivateKeyFileInfo.exists) {
|
|
@@ -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,22 @@ 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 (!serverCertificateAltNames.includes("localhost")) {
|
|
89
|
-
serverCertificateAltNames.push("localhost")
|
|
90
|
-
}
|
|
91
|
-
|
|
92
84
|
logger.debug(`Generating server certificate...`)
|
|
93
85
|
const { certificateForgeObject, certificatePrivateKeyForgeObject } =
|
|
94
|
-
|
|
86
|
+
requestCertificateFromAuthority({
|
|
95
87
|
logger,
|
|
96
88
|
authorityCertificateForgeObject: rootCertificateForgeObject,
|
|
97
89
|
auhtorityCertificatePrivateKeyForgeObject:
|
|
98
90
|
rootCertificatePrivateKeyForgeObject,
|
|
99
91
|
serialNumber: serverCertificateSerialNumber,
|
|
100
|
-
altNames
|
|
101
|
-
commonName
|
|
102
|
-
validityDurationInMs
|
|
92
|
+
altNames,
|
|
93
|
+
commonName,
|
|
94
|
+
validityDurationInMs,
|
|
103
95
|
})
|
|
104
96
|
const serverCertificate = pki.certificateToPem(certificateForgeObject)
|
|
105
97
|
const serverCertificatePrivateKey = pki.privateKeyToPem(
|
|
@@ -109,13 +101,13 @@ export const requestCertificateForLocalhost = async ({
|
|
|
109
101
|
`${
|
|
110
102
|
UNICODE.OK
|
|
111
103
|
} server certificate generated, it will be valid for ${formatDuration(
|
|
112
|
-
|
|
104
|
+
validityDurationInMs,
|
|
113
105
|
)}`,
|
|
114
106
|
)
|
|
115
107
|
|
|
116
108
|
return {
|
|
117
|
-
serverCertificate,
|
|
118
|
-
serverCertificatePrivateKey,
|
|
109
|
+
certificate: serverCertificate,
|
|
110
|
+
privateKey: serverCertificatePrivateKey,
|
|
119
111
|
rootCertificateFilePath: rootCertificateFileInfo.path,
|
|
120
112
|
}
|
|
121
113
|
}
|
package/src/hosts_file_verif.js
CHANGED
|
@@ -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
3
|
import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
4
|
-
|
|
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,
|
|
@@ -3,6 +3,7 @@ import { execSync } from "node:child_process"
|
|
|
3
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,
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import { existsSync } from "node:fs"
|
|
6
6
|
import { fileURLToPath } from "node:url"
|
|
7
|
-
import { createDetailedMessage } from "@jsenv/logger"
|
|
8
7
|
import { readFile } from "@jsenv/filesystem"
|
|
9
|
-
import { UNICODE } from "@jsenv/log"
|
|
8
|
+
import { createDetailedMessage, UNICODE } from "@jsenv/log"
|
|
10
9
|
|
|
11
|
-
import { exec } from "@jsenv/https-local/src/internal/exec.js"
|
|
12
10
|
import {
|
|
13
11
|
VERB_CHECK_TRUST,
|
|
14
12
|
VERB_ADD_TRUST,
|
|
13
|
+
VERB_ENSURE_TRUST,
|
|
15
14
|
VERB_REMOVE_TRUST,
|
|
16
15
|
} from "../trust_query.js"
|
|
16
|
+
import { exec } from "../exec.js"
|
|
17
17
|
|
|
18
18
|
const REASON_NEW_AND_TRY_TO_TRUST_DISABLED =
|
|
19
19
|
"certificate is new and tryToTrust is disabled"
|
|
@@ -101,7 +101,11 @@ export const executeTrustQueryOnLinux = async ({
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
logger.info(`${UNICODE.OK} certificate found in linux`)
|
|
104
|
-
if (
|
|
104
|
+
if (
|
|
105
|
+
verb === VERB_CHECK_TRUST ||
|
|
106
|
+
verb === VERB_ADD_TRUST ||
|
|
107
|
+
verb === VERB_ENSURE_TRUST
|
|
108
|
+
) {
|
|
105
109
|
return {
|
|
106
110
|
status: "trusted",
|
|
107
111
|
reason: REASON_FOUND_IN_LINUX,
|
|
@@ -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,14 +1,14 @@
|
|
|
1
1
|
// https://ss64.com/osx/security.html
|
|
2
2
|
|
|
3
3
|
import { fileURLToPath } from "node:url"
|
|
4
|
-
import { createDetailedMessage } from "@jsenv/
|
|
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,
|
|
11
|
+
VERB_ENSURE_TRUST,
|
|
12
12
|
VERB_REMOVE_TRUST,
|
|
13
13
|
} from "../trust_query.js"
|
|
14
14
|
|
|
@@ -53,6 +53,35 @@ export const executeTrustQueryOnMacKeychain = async ({
|
|
|
53
53
|
certificate,
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
+
const removeCert = async () => {
|
|
57
|
+
// https://ss64.com/osx/security-delete-cert.html
|
|
58
|
+
const removeTrustedCertCommand = `sudo security delete-certificate -c "${certificateCommonName}"`
|
|
59
|
+
logger.info(`Removing certificate from mac keychain...`)
|
|
60
|
+
logger.info(`${UNICODE.COMMAND} ${removeTrustedCertCommand}`)
|
|
61
|
+
try {
|
|
62
|
+
await exec(removeTrustedCertCommand)
|
|
63
|
+
logger.info(`${UNICODE.OK} certificate removed from mac keychain`)
|
|
64
|
+
return {
|
|
65
|
+
status: "not_trusted",
|
|
66
|
+
reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_COMPLETED,
|
|
67
|
+
}
|
|
68
|
+
} catch (e) {
|
|
69
|
+
logger.error(
|
|
70
|
+
createDetailedMessage(
|
|
71
|
+
`${UNICODE.FAILURE} failed to remove certificate from mac keychain`,
|
|
72
|
+
{
|
|
73
|
+
"error stack": e.stack,
|
|
74
|
+
"certificate file url": certificateFileUrl,
|
|
75
|
+
},
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
return {
|
|
79
|
+
status: "not_trusted",
|
|
80
|
+
reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_FAILED,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
56
85
|
if (!certificateFoundInCommandOutput) {
|
|
57
86
|
logger.info(`${UNICODE.INFO} certificate not found in mac keychain`)
|
|
58
87
|
if (verb === VERB_CHECK_TRUST || verb === VERB_REMOVE_TRUST) {
|
|
@@ -61,7 +90,13 @@ export const executeTrustQueryOnMacKeychain = async ({
|
|
|
61
90
|
reason: REASON_NOT_IN_KEYCHAIN,
|
|
62
91
|
}
|
|
63
92
|
}
|
|
64
|
-
|
|
93
|
+
if (verb === VERB_ENSURE_TRUST) {
|
|
94
|
+
// It seems possible for certificate PEM representation to be different
|
|
95
|
+
// in mackeychain and in the one we have written on the filesystem
|
|
96
|
+
// When it happens the certificate is not found but actually exists on mackeychain
|
|
97
|
+
// and must be deleted first
|
|
98
|
+
await removeCert()
|
|
99
|
+
}
|
|
65
100
|
const certificateFilePath = fileURLToPath(certificateFileUrl)
|
|
66
101
|
// https://ss64.com/osx/security-cert.html
|
|
67
102
|
const addTrustedCertCommand = `sudo security add-trusted-cert -d -r trustRoot -k ${systemKeychainPath} "${certificateFilePath}"`
|
|
@@ -96,37 +131,16 @@ export const executeTrustQueryOnMacKeychain = async ({
|
|
|
96
131
|
// but they shouldn't and I couldn't find an API to know if the cert is trusted or not
|
|
97
132
|
// just if it's in the keychain
|
|
98
133
|
logger.info(`${UNICODE.OK} certificate found in mac keychain`)
|
|
99
|
-
if (
|
|
134
|
+
if (
|
|
135
|
+
verb === VERB_CHECK_TRUST ||
|
|
136
|
+
verb === VERB_ADD_TRUST ||
|
|
137
|
+
verb === VERB_ENSURE_TRUST
|
|
138
|
+
) {
|
|
100
139
|
return {
|
|
101
140
|
status: "trusted",
|
|
102
141
|
reason: REASON_IN_KEYCHAIN,
|
|
103
142
|
}
|
|
104
143
|
}
|
|
105
144
|
|
|
106
|
-
|
|
107
|
-
const removeTrustedCertCommand = `sudo security delete-certificate -c "${certificateCommonName}"`
|
|
108
|
-
logger.info(`Removing certificate from mac keychain...`)
|
|
109
|
-
logger.info(`${UNICODE.COMMAND} ${removeTrustedCertCommand}`)
|
|
110
|
-
try {
|
|
111
|
-
await exec(removeTrustedCertCommand)
|
|
112
|
-
logger.info(`${UNICODE.OK} certificate removed from mac keychain`)
|
|
113
|
-
return {
|
|
114
|
-
status: "not_trusted",
|
|
115
|
-
reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_COMPLETED,
|
|
116
|
-
}
|
|
117
|
-
} catch (e) {
|
|
118
|
-
logger.error(
|
|
119
|
-
createDetailedMessage(
|
|
120
|
-
`${UNICODE.FAILURE} failed to remove certificate from mac keychain`,
|
|
121
|
-
{
|
|
122
|
-
"error stack": e.stack,
|
|
123
|
-
"certificate file url": certificateFileUrl,
|
|
124
|
-
},
|
|
125
|
-
),
|
|
126
|
-
)
|
|
127
|
-
return {
|
|
128
|
-
status: "not_trusted",
|
|
129
|
-
reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_FAILED,
|
|
130
|
-
}
|
|
131
|
-
}
|
|
145
|
+
return removeCert()
|
|
132
146
|
}
|
|
@@ -2,9 +2,9 @@ import { fileURLToPath } from "node:url"
|
|
|
2
2
|
import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
3
3
|
import { UNICODE } from "@jsenv/log"
|
|
4
4
|
|
|
5
|
-
import { memoize } from "
|
|
6
|
-
import { exec } from "
|
|
7
|
-
import { commandExists } from "
|
|
5
|
+
import { memoize } from "../memoize.js"
|
|
6
|
+
import { exec } from "../exec.js"
|
|
7
|
+
import { commandExists } from "../command.js"
|
|
8
8
|
|
|
9
9
|
export const nssCommandName = "nss"
|
|
10
10
|
|
|
@@ -5,14 +5,17 @@
|
|
|
5
5
|
|
|
6
6
|
import { existsSync } from "node:fs"
|
|
7
7
|
import { fileURLToPath } from "node:url"
|
|
8
|
-
import { createDetailedMessage } from "@jsenv/logger"
|
|
9
8
|
import { urlToFilename } from "@jsenv/urls"
|
|
9
|
+
import { createDetailedMessage, UNICODE } from "@jsenv/log"
|
|
10
10
|
import { assertAndNormalizeDirectoryUrl, collectFiles } from "@jsenv/filesystem"
|
|
11
|
-
import { UNICODE } from "@jsenv/log"
|
|
12
11
|
|
|
13
|
-
import { exec } from "
|
|
14
|
-
import { searchCertificateInCommandOutput } from "
|
|
15
|
-
import {
|
|
12
|
+
import { exec } from "./exec.js"
|
|
13
|
+
import { searchCertificateInCommandOutput } from "./search_certificate_in_command_output.js"
|
|
14
|
+
import {
|
|
15
|
+
VERB_CHECK_TRUST,
|
|
16
|
+
VERB_ADD_TRUST,
|
|
17
|
+
VERB_ENSURE_TRUST,
|
|
18
|
+
} from "./trust_query.js"
|
|
16
19
|
|
|
17
20
|
export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
18
21
|
logger,
|
|
@@ -53,7 +56,7 @@ export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
|
53
56
|
const nssIsInstalled = await detectIfNSSIsInstalled({ logger })
|
|
54
57
|
const cannotCheckMessage = `${UNICODE.FAILURE} cannot check if certificate is in ${browserName}`
|
|
55
58
|
if (!nssIsInstalled) {
|
|
56
|
-
if (verb === VERB_ADD_TRUST) {
|
|
59
|
+
if (verb === VERB_ADD_TRUST || verb === VERB_ENSURE_TRUST) {
|
|
57
60
|
const nssDynamicInstallInfo = await getNSSDynamicInstallInfo({ logger })
|
|
58
61
|
if (!nssDynamicInstallInfo.isInstallable) {
|
|
59
62
|
const reason = `"${nssCommandName}" is not installed and not cannot be installed`
|
|
@@ -210,7 +213,7 @@ export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
215
|
|
|
213
|
-
if (verb === VERB_ADD_TRUST) {
|
|
216
|
+
if (verb === VERB_ADD_TRUST || verb === VERB_ENSURE_TRUST) {
|
|
214
217
|
if (missingCount === 0 && outdatedCount === 0) {
|
|
215
218
|
logger.info(`${UNICODE.OK} certificate found in ${browserName}`)
|
|
216
219
|
return {
|
|
@@ -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
|
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { fileURLToPath } from "node:url"
|
|
7
|
-
import { createDetailedMessage } from "@jsenv/
|
|
8
|
-
import { UNICODE } from "@jsenv/log"
|
|
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,
|
|
13
|
+
VERB_ENSURE_TRUST,
|
|
14
14
|
VERB_REMOVE_TRUST,
|
|
15
15
|
} from "../trust_query.js"
|
|
16
16
|
|
|
@@ -94,7 +94,11 @@ export const executeTrustQueryOnWindows = async ({
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
logger.info(`${UNICODE.OK} certificate found in windows`)
|
|
97
|
-
if (
|
|
97
|
+
if (
|
|
98
|
+
verb === VERB_CHECK_TRUST ||
|
|
99
|
+
verb === VERB_ADD_TRUST ||
|
|
100
|
+
verb === VERB_ENSURE_TRUST
|
|
101
|
+
) {
|
|
98
102
|
return {
|
|
99
103
|
status: "trusted",
|
|
100
104
|
reason: REASON_FOUND_IN_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 {
|
|
19
|
+
export { requestCertificate } from "./certificate_request.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
|
|