@jsenv/https-local 2.0.0 → 3.0.1
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 -11
- package/package.json +12 -13
- package/src/certificate_authority.js +1 -2
- package/src/{certificate_for_localhost.js → certificate_request.js} +2 -6
- package/src/internal/linux/linux_trust_store.js +6 -1
- package/src/internal/mac/firefox_mac.js +2 -1
- package/src/internal/mac/mac_keychain.js +43 -28
- package/src/internal/nssdb_browser.js +7 -3
- package/src/internal/trust_query.js +1 -2
- package/src/internal/windows/windows_certutil.js +6 -1
- package/src/main.js +1 -1
package/README.md
CHANGED
|
@@ -61,13 +61,13 @@ 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 { certificate, privateKey } =
|
|
70
|
+
const { certificate, privateKey } = requestCertificate()
|
|
71
71
|
|
|
72
72
|
const server = createServer(
|
|
73
73
|
{
|
|
@@ -99,10 +99,10 @@ The rest of the documentation goes into details.
|
|
|
99
99
|
|
|
100
100
|
# Certificate expiration
|
|
101
101
|
|
|
102
|
-
| Certificate | Expires after | How to renew?
|
|
103
|
-
| ----------- | ------------- |
|
|
104
|
-
| server | 1 year | Re-run
|
|
105
|
-
| 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_ |
|
|
106
106
|
|
|
107
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.
|
|
108
108
|
|
|
@@ -352,15 +352,15 @@ Check if certificate is trusted by firefox...
|
|
|
352
352
|
|
|
353
353
|
</details>
|
|
354
354
|
|
|
355
|
-
#
|
|
355
|
+
# requestCertificate
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
_requestCertificate_ function returns a certificate and private key that can be used to start a server in HTTPS.
|
|
358
358
|
|
|
359
359
|
```js
|
|
360
360
|
import { createServer } from "node:https"
|
|
361
|
-
import {
|
|
361
|
+
import { requestCertificate } from "@jsenv/https-local"
|
|
362
362
|
|
|
363
|
-
const { certificate, privateKey } =
|
|
363
|
+
const { certificate, privateKey } = requestCertificate({
|
|
364
364
|
altNames: ["localhost", "local.example"],
|
|
365
365
|
})
|
|
366
366
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/https-local",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "A programmatic way to generate locally trusted certificates",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
"url": "https://github.com/jsenv/https-local"
|
|
14
14
|
},
|
|
15
15
|
"publishConfig": {
|
|
16
|
-
"access": "public"
|
|
17
|
-
"registry": "https://registry.npmjs.org"
|
|
16
|
+
"access": "public"
|
|
18
17
|
},
|
|
19
18
|
"engines": {
|
|
20
19
|
"node": ">=16.13.0"
|
|
@@ -48,9 +47,9 @@
|
|
|
48
47
|
"playwright-install": "npx playwright install-deps && npx playwright install"
|
|
49
48
|
},
|
|
50
49
|
"dependencies": {
|
|
51
|
-
"@jsenv/filesystem": "4.1.
|
|
52
|
-
"@jsenv/log": "
|
|
53
|
-
"@jsenv/urls": "1.2.
|
|
50
|
+
"@jsenv/filesystem": "4.1.2",
|
|
51
|
+
"@jsenv/log": "3.1.0",
|
|
52
|
+
"@jsenv/urls": "1.2.7",
|
|
54
53
|
"command-exists": "1.2.9",
|
|
55
54
|
"node-forge": "1.3.1",
|
|
56
55
|
"sudo-prompt": "9.2.1",
|
|
@@ -58,15 +57,15 @@
|
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
60
59
|
"@jsenv/assert": "2.6.0",
|
|
61
|
-
"@jsenv/core": "
|
|
62
|
-
"@jsenv/eslint-config": "16.
|
|
60
|
+
"@jsenv/core": "28.0.2",
|
|
61
|
+
"@jsenv/eslint-config": "16.2.1",
|
|
63
62
|
"@jsenv/eslint-import-resolver": "0.3.0",
|
|
64
|
-
"@jsenv/github-release-package": "1.
|
|
65
|
-
"@jsenv/package-publish": "1.
|
|
63
|
+
"@jsenv/github-release-package": "1.5.0",
|
|
64
|
+
"@jsenv/package-publish": "1.10.0",
|
|
66
65
|
"@jsenv/performance-impact": "2.3.0",
|
|
67
|
-
"eslint": "8.
|
|
66
|
+
"eslint": "8.21.0",
|
|
68
67
|
"eslint-plugin-import": "2.26.0",
|
|
69
|
-
"playwright": "1.
|
|
68
|
+
"playwright": "1.24.2",
|
|
70
69
|
"prettier": "2.7.1"
|
|
71
70
|
}
|
|
72
|
-
}
|
|
71
|
+
}
|
|
@@ -231,13 +231,12 @@ export const installCertificateAuthority = async ({
|
|
|
231
231
|
const rootCertificatePrivateKeyForgeObject = pki.privateKeyFromPem(
|
|
232
232
|
rootCertificatePrivateKey,
|
|
233
233
|
)
|
|
234
|
-
|
|
235
234
|
const trustInfo = await platformMethods.executeTrustQuery({
|
|
236
235
|
logger,
|
|
237
236
|
certificateCommonName,
|
|
238
237
|
certificateFileUrl: rootCertificateFileInfo.url,
|
|
239
238
|
certificate: rootCertificate,
|
|
240
|
-
verb: tryToTrust ? "
|
|
239
|
+
verb: tryToTrust ? "ENSURE_TRUST" : "CHECK_TRUST",
|
|
241
240
|
NSSDynamicInstall,
|
|
242
241
|
})
|
|
243
242
|
|
|
@@ -11,7 +11,7 @@ import { getAuthorityFileInfos } from "./internal/authority_file_infos.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
|
|
|
@@ -47,7 +47,7 @@ export const requestCertificateForLocalhost = ({
|
|
|
47
47
|
} = getAuthorityFileInfos()
|
|
48
48
|
if (!rootCertificateFileInfo.exists) {
|
|
49
49
|
throw new Error(
|
|
50
|
-
`Certificate authority not found, "installCertificateAuthority" must be called before "
|
|
50
|
+
`Certificate authority not found, "installCertificateAuthority" must be called before "requestServerCertificate"`,
|
|
51
51
|
)
|
|
52
52
|
}
|
|
53
53
|
if (!rootCertificatePrivateKeyFileInfo.exists) {
|
|
@@ -81,10 +81,6 @@ export const requestCertificateForLocalhost = ({
|
|
|
81
81
|
JSON.stringify({ serialNumber: serverCertificateSerialNumber }, null, " "),
|
|
82
82
|
)
|
|
83
83
|
|
|
84
|
-
if (!altNames.includes("localhost")) {
|
|
85
|
-
altNames.push("localhost")
|
|
86
|
-
}
|
|
87
|
-
|
|
88
84
|
logger.debug(`Generating server certificate...`)
|
|
89
85
|
const { certificateForgeObject, certificatePrivateKeyForgeObject } =
|
|
90
86
|
requestCertificateFromAuthority({
|
|
@@ -10,6 +10,7 @@ import { createDetailedMessage, UNICODE } from "@jsenv/log"
|
|
|
10
10
|
import {
|
|
11
11
|
VERB_CHECK_TRUST,
|
|
12
12
|
VERB_ADD_TRUST,
|
|
13
|
+
VERB_ENSURE_TRUST,
|
|
13
14
|
VERB_REMOVE_TRUST,
|
|
14
15
|
} from "../trust_query.js"
|
|
15
16
|
import { exec } from "../exec.js"
|
|
@@ -100,7 +101,11 @@ export const executeTrustQueryOnLinux = async ({
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
logger.info(`${UNICODE.OK} certificate found in linux`)
|
|
103
|
-
if (
|
|
104
|
+
if (
|
|
105
|
+
verb === VERB_CHECK_TRUST ||
|
|
106
|
+
verb === VERB_ADD_TRUST ||
|
|
107
|
+
verb === VERB_ENSURE_TRUST
|
|
108
|
+
) {
|
|
104
109
|
return {
|
|
105
110
|
status: "trusted",
|
|
106
111
|
reason: REASON_FOUND_IN_LINUX,
|
|
@@ -8,6 +8,7 @@ import { searchCertificateInCommandOutput } from "../search_certificate_in_comma
|
|
|
8
8
|
import {
|
|
9
9
|
VERB_CHECK_TRUST,
|
|
10
10
|
VERB_ADD_TRUST,
|
|
11
|
+
VERB_ENSURE_TRUST,
|
|
11
12
|
VERB_REMOVE_TRUST,
|
|
12
13
|
} from "../trust_query.js"
|
|
13
14
|
|
|
@@ -52,6 +53,35 @@ export const executeTrustQueryOnMacKeychain = async ({
|
|
|
52
53
|
certificate,
|
|
53
54
|
)
|
|
54
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
|
+
|
|
55
85
|
if (!certificateFoundInCommandOutput) {
|
|
56
86
|
logger.info(`${UNICODE.INFO} certificate not found in mac keychain`)
|
|
57
87
|
if (verb === VERB_CHECK_TRUST || verb === VERB_REMOVE_TRUST) {
|
|
@@ -60,7 +90,13 @@ export const executeTrustQueryOnMacKeychain = async ({
|
|
|
60
90
|
reason: REASON_NOT_IN_KEYCHAIN,
|
|
61
91
|
}
|
|
62
92
|
}
|
|
63
|
-
|
|
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
|
+
}
|
|
64
100
|
const certificateFilePath = fileURLToPath(certificateFileUrl)
|
|
65
101
|
// https://ss64.com/osx/security-cert.html
|
|
66
102
|
const addTrustedCertCommand = `sudo security add-trusted-cert -d -r trustRoot -k ${systemKeychainPath} "${certificateFilePath}"`
|
|
@@ -95,37 +131,16 @@ export const executeTrustQueryOnMacKeychain = async ({
|
|
|
95
131
|
// but they shouldn't and I couldn't find an API to know if the cert is trusted or not
|
|
96
132
|
// just if it's in the keychain
|
|
97
133
|
logger.info(`${UNICODE.OK} certificate found in mac keychain`)
|
|
98
|
-
if (
|
|
134
|
+
if (
|
|
135
|
+
verb === VERB_CHECK_TRUST ||
|
|
136
|
+
verb === VERB_ADD_TRUST ||
|
|
137
|
+
verb === VERB_ENSURE_TRUST
|
|
138
|
+
) {
|
|
99
139
|
return {
|
|
100
140
|
status: "trusted",
|
|
101
141
|
reason: REASON_IN_KEYCHAIN,
|
|
102
142
|
}
|
|
103
143
|
}
|
|
104
144
|
|
|
105
|
-
|
|
106
|
-
const removeTrustedCertCommand = `sudo security delete-certificate -c "${certificateCommonName}"`
|
|
107
|
-
logger.info(`Removing certificate from mac keychain...`)
|
|
108
|
-
logger.info(`${UNICODE.COMMAND} ${removeTrustedCertCommand}`)
|
|
109
|
-
try {
|
|
110
|
-
await exec(removeTrustedCertCommand)
|
|
111
|
-
logger.info(`${UNICODE.OK} certificate removed from mac keychain`)
|
|
112
|
-
return {
|
|
113
|
-
status: "not_trusted",
|
|
114
|
-
reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_COMPLETED,
|
|
115
|
-
}
|
|
116
|
-
} catch (e) {
|
|
117
|
-
logger.error(
|
|
118
|
-
createDetailedMessage(
|
|
119
|
-
`${UNICODE.FAILURE} failed to remove certificate from mac keychain`,
|
|
120
|
-
{
|
|
121
|
-
"error stack": e.stack,
|
|
122
|
-
"certificate file url": certificateFileUrl,
|
|
123
|
-
},
|
|
124
|
-
),
|
|
125
|
-
)
|
|
126
|
-
return {
|
|
127
|
-
status: "not_trusted",
|
|
128
|
-
reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_FAILED,
|
|
129
|
-
}
|
|
130
|
-
}
|
|
145
|
+
return removeCert()
|
|
131
146
|
}
|
|
@@ -11,7 +11,11 @@ import { assertAndNormalizeDirectoryUrl, collectFiles } from "@jsenv/filesystem"
|
|
|
11
11
|
|
|
12
12
|
import { exec } from "./exec.js"
|
|
13
13
|
import { searchCertificateInCommandOutput } from "./search_certificate_in_command_output.js"
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
VERB_CHECK_TRUST,
|
|
16
|
+
VERB_ADD_TRUST,
|
|
17
|
+
VERB_ENSURE_TRUST,
|
|
18
|
+
} from "./trust_query.js"
|
|
15
19
|
|
|
16
20
|
export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
17
21
|
logger,
|
|
@@ -52,7 +56,7 @@ export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
|
52
56
|
const nssIsInstalled = await detectIfNSSIsInstalled({ logger })
|
|
53
57
|
const cannotCheckMessage = `${UNICODE.FAILURE} cannot check if certificate is in ${browserName}`
|
|
54
58
|
if (!nssIsInstalled) {
|
|
55
|
-
if (verb === VERB_ADD_TRUST) {
|
|
59
|
+
if (verb === VERB_ADD_TRUST || verb === VERB_ENSURE_TRUST) {
|
|
56
60
|
const nssDynamicInstallInfo = await getNSSDynamicInstallInfo({ logger })
|
|
57
61
|
if (!nssDynamicInstallInfo.isInstallable) {
|
|
58
62
|
const reason = `"${nssCommandName}" is not installed and not cannot be installed`
|
|
@@ -209,7 +213,7 @@ export const executeTrustQueryOnBrowserNSSDB = async ({
|
|
|
209
213
|
}
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
if (verb === VERB_ADD_TRUST) {
|
|
216
|
+
if (verb === VERB_ADD_TRUST || verb === VERB_ENSURE_TRUST) {
|
|
213
217
|
if (missingCount === 0 && outdatedCount === 0) {
|
|
214
218
|
logger.info(`${UNICODE.OK} certificate found in ${browserName}`)
|
|
215
219
|
return {
|
|
@@ -10,6 +10,7 @@ import { exec } from "../exec.js"
|
|
|
10
10
|
import {
|
|
11
11
|
VERB_CHECK_TRUST,
|
|
12
12
|
VERB_ADD_TRUST,
|
|
13
|
+
VERB_ENSURE_TRUST,
|
|
13
14
|
VERB_REMOVE_TRUST,
|
|
14
15
|
} from "../trust_query.js"
|
|
15
16
|
|
|
@@ -93,7 +94,11 @@ export const executeTrustQueryOnWindows = async ({
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
logger.info(`${UNICODE.OK} certificate found in windows`)
|
|
96
|
-
if (
|
|
97
|
+
if (
|
|
98
|
+
verb === VERB_CHECK_TRUST ||
|
|
99
|
+
verb === VERB_ADD_TRUST ||
|
|
100
|
+
verb === VERB_ENSURE_TRUST
|
|
101
|
+
) {
|
|
97
102
|
return {
|
|
98
103
|
status: "trusted",
|
|
99
104
|
reason: REASON_FOUND_IN_WINDOWS,
|
package/src/main.js
CHANGED