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