localssl-cli 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/package.json +1 -1
- package/src/bootstrap.js +66 -7
- package/src/prompt.js +22 -0
- package/src/trust/windows.js +46 -9
package/README.md
CHANGED
|
@@ -146,6 +146,14 @@ It never stores CA private keys.
|
|
|
146
146
|
- Team file validation blocks private-key content in `localssl.json`
|
|
147
147
|
- Never share root CA private key files
|
|
148
148
|
|
|
149
|
+
## Windows permissions behavior
|
|
150
|
+
|
|
151
|
+
- localssl-cli first trusts certs in `CurrentUser\\Root` (no admin expected)
|
|
152
|
+
- if needed, it prompts: `Admin access needed for machine-wide trust. Continue? (y/N)`
|
|
153
|
+
- choosing `No` keeps safe mode and skips machine-wide trust
|
|
154
|
+
- even if trust-store writes fail, localssl continues project cert setup (non-blocking)
|
|
155
|
+
- rerunning `localssl-cli init` repairs trust if CA already exists
|
|
156
|
+
|
|
149
157
|
---
|
|
150
158
|
|
|
151
159
|
## Troubleshooting
|
package/package.json
CHANGED
package/src/bootstrap.js
CHANGED
|
@@ -99,8 +99,8 @@ async function trustSystem(certPath) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
if (process.platform === 'win32') {
|
|
102
|
-
await trustWindows(certPath);
|
|
103
|
-
return
|
|
102
|
+
const scope = await trustWindows(certPath);
|
|
103
|
+
return scope;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
await trustLinux(certPath);
|
|
@@ -138,8 +138,39 @@ async function initMachine({ quiet = false } = {}) {
|
|
|
138
138
|
|
|
139
139
|
const hasCA = await fs.pathExists(LOCALSSL_CA_PUBLIC);
|
|
140
140
|
if (hasCA) {
|
|
141
|
+
let systemResult = 'system trust skipped';
|
|
142
|
+
let nodeResult = 'NODE_EXTRA_CA_CERTS skipped';
|
|
143
|
+
let firefoxResult = { trusted: false };
|
|
144
|
+
let chromiumResult = { trusted: false };
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
systemResult = await trustSystem(LOCALSSL_CA_PUBLIC);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (!quiet) warn(`System trust repair skipped: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
firefoxResult = await trustInFirefox(LOCALSSL_CA_PUBLIC);
|
|
154
|
+
} catch {
|
|
155
|
+
firefoxResult = { trusted: false };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
chromiumResult = await trustInChromium(LOCALSSL_CA_PUBLIC);
|
|
160
|
+
} catch {
|
|
161
|
+
chromiumResult = { trusted: false };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
nodeResult = await configureNodeExtraCACerts();
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (!quiet) warn(`NODE_EXTRA_CA_CERTS repair skipped: ${error.message}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const repairSummary = `${systemResult}; ${nodeResult}; Firefox ${firefoxResult.trusted ? 'ok' : 'skipped'}; Chrome/Edge ${chromiumResult.trusted ? 'ok' : 'skipped'}`;
|
|
171
|
+
|
|
141
172
|
if (!quiet) {
|
|
142
|
-
step(1, 1, 'Machine CA setup', 'skip',
|
|
173
|
+
step(1, 1, 'Machine CA setup', 'skip', `(${repairSummary})`);
|
|
143
174
|
}
|
|
144
175
|
return { mkcertPath, initialized: false };
|
|
145
176
|
}
|
|
@@ -154,10 +185,34 @@ async function initMachine({ quiet = false } = {}) {
|
|
|
154
185
|
}
|
|
155
186
|
warn('Java trust store update skipped (no admin access). System/browser trust still configured.');
|
|
156
187
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
188
|
+
let systemResult = 'system trust skipped';
|
|
189
|
+
let firefoxResult = { trusted: false, reason: 'not attempted' };
|
|
190
|
+
let chromiumResult = { trusted: false, reason: 'not attempted' };
|
|
191
|
+
let nodeResult = 'NODE_EXTRA_CA_CERTS skipped';
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
systemResult = await trustSystem(LOCALSSL_CA_PUBLIC);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
warn(`System trust skipped: ${error.message}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
firefoxResult = await trustInFirefox(LOCALSSL_CA_PUBLIC);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
firefoxResult = { trusted: false, reason: error.message };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
chromiumResult = await trustInChromium(LOCALSSL_CA_PUBLIC);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
chromiumResult = { trusted: false, reason: error.message };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
nodeResult = await configureNodeExtraCACerts();
|
|
213
|
+
} catch (error) {
|
|
214
|
+
warn(`NODE_EXTRA_CA_CERTS skipped: ${error.message}`);
|
|
215
|
+
}
|
|
161
216
|
|
|
162
217
|
if (!quiet) {
|
|
163
218
|
const firefoxText = firefoxResult.trusted ? `+ Firefox (${firefoxResult.reason})` : `+ Firefox skipped (${firefoxResult.reason})`;
|
|
@@ -165,6 +220,10 @@ async function initMachine({ quiet = false } = {}) {
|
|
|
165
220
|
step(1, 1, 'Installing local CA', 'ok', `(${systemResult} ${firefoxText} ${chromiumText}; ${nodeResult})`);
|
|
166
221
|
}
|
|
167
222
|
|
|
223
|
+
if (/unavailable|skipped/i.test(systemResult)) {
|
|
224
|
+
warn('Windows trust was not installed. You can still run HTTPS, but browser may warn until trust is added.');
|
|
225
|
+
}
|
|
226
|
+
|
|
168
227
|
if (!firefoxResult.trusted) {
|
|
169
228
|
warn(`Firefox trust skipped: ${firefoxResult.reason}`);
|
|
170
229
|
}
|
package/src/prompt.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
|
|
3
|
+
async function askYesNo(question, defaultNo = true) {
|
|
4
|
+
if (process.env.LOCALSSL_ASSUME_YES === '1') {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
9
|
+
return !defaultNo;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
14
|
+
rl.question(question, (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
const normalized = String(answer || '').trim().toLowerCase();
|
|
17
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { askYesNo };
|
package/src/trust/windows.js
CHANGED
|
@@ -1,20 +1,57 @@
|
|
|
1
1
|
const { execFile } = require('child_process');
|
|
2
|
+
const { askYesNo } = require('../prompt');
|
|
3
|
+
|
|
4
|
+
function run(file, args) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
execFile(file, args, { windowsHide: true }, (error) => resolve(!error));
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function trustCurrentUserWithPowerShell(certPath) {
|
|
11
|
+
const command = `Import-Certificate -FilePath \"${certPath}\" -CertStoreLocation Cert:\\CurrentUser\\Root | Out-Null`;
|
|
12
|
+
return run('powershell', ['-NoProfile', '-NonInteractive', '-Command', command]);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function trustMachineWithElevation(certPath) {
|
|
16
|
+
const command = `$p = Start-Process certutil -ArgumentList '-addstore','-f','ROOT','${certPath}' -Verb RunAs -Wait -PassThru; if ($p.ExitCode -eq 0) { exit 0 } else { exit 1 }`;
|
|
17
|
+
return run('powershell', ['-NoProfile', '-NonInteractive', '-Command', command]);
|
|
18
|
+
}
|
|
2
19
|
|
|
3
20
|
function trustCertificate(certPath) {
|
|
4
|
-
return new Promise((resolve
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
return new Promise(async (resolve) => {
|
|
22
|
+
const userViaCertutil = await run('certutil', ['-user', '-addstore', '-f', 'ROOT', certPath]);
|
|
23
|
+
if (userViaCertutil) {
|
|
24
|
+
resolve('Windows CurrentUser Root');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const userViaPowerShell = await trustCurrentUserWithPowerShell(certPath);
|
|
29
|
+
if (userViaPowerShell) {
|
|
30
|
+
resolve('Windows CurrentUser Root (PowerShell)');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const consent = await askYesNo(' Admin access needed for machine-wide trust. Continue? (y/N): ');
|
|
35
|
+
if (!consent) {
|
|
36
|
+
resolve('Windows trust skipped (user declined admin prompt)');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const machineStore = await trustMachineWithElevation(certPath);
|
|
41
|
+
if (machineStore) {
|
|
42
|
+
resolve('Windows LocalMachine Root (elevated)');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
resolve('Windows trust unavailable (no changes made)');
|
|
12
47
|
});
|
|
13
48
|
}
|
|
14
49
|
|
|
15
50
|
function untrustCertificate(certPath) {
|
|
16
51
|
return new Promise((resolve) => {
|
|
17
|
-
execFile('certutil', ['-delstore', 'ROOT', certPath], { windowsHide: true }, () =>
|
|
52
|
+
execFile('certutil', ['-user', '-delstore', 'ROOT', certPath], { windowsHide: true }, () => {
|
|
53
|
+
execFile('certutil', ['-delstore', 'ROOT', certPath], { windowsHide: true }, () => resolve());
|
|
54
|
+
});
|
|
18
55
|
});
|
|
19
56
|
}
|
|
20
57
|
|