@lamalibre/portlama-agent 1.0.20 → 1.0.21
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/package.json +1 -1
- package/src/commands/setup.js +31 -76
- package/src/lib/cert-store.js +20 -54
- package/src/lib/keychain.js +24 -9
package/package.json
CHANGED
package/src/commands/setup.js
CHANGED
|
@@ -7,7 +7,6 @@ import { Listr } from 'listr2';
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import {
|
|
9
9
|
assertSupportedPlatform,
|
|
10
|
-
isDarwin,
|
|
11
10
|
CHISEL_BIN_DIR,
|
|
12
11
|
AGENT_DIR,
|
|
13
12
|
agentDataDir,
|
|
@@ -161,9 +160,7 @@ async function runTokenSetup(flags) {
|
|
|
161
160
|
|
|
162
161
|
console.log('');
|
|
163
162
|
console.log(chalk.bold(' Portlama Agent Setup (Token-Based Enrollment)'));
|
|
164
|
-
console.log(chalk.dim(
|
|
165
|
-
? ' Connect this Mac to your Portlama server using a Keychain-bound certificate.'
|
|
166
|
-
: ' Connect this machine to your Portlama server using a certificate.'));
|
|
163
|
+
console.log(chalk.dim(' Connect this machine to your Portlama server using a certificate.'));
|
|
167
164
|
console.log('');
|
|
168
165
|
|
|
169
166
|
let panelUrl = flags.panelUrl;
|
|
@@ -185,7 +182,6 @@ async function runTokenSetup(flags) {
|
|
|
185
182
|
explicitLabel: flags.label,
|
|
186
183
|
agentLabel: null,
|
|
187
184
|
resolvedLabel: null,
|
|
188
|
-
keychainIdentity: null,
|
|
189
185
|
p12Path: null,
|
|
190
186
|
p12Password: null,
|
|
191
187
|
chiselVersion: null,
|
|
@@ -247,20 +243,7 @@ async function runTokenSetup(flags) {
|
|
|
247
243
|
},
|
|
248
244
|
},
|
|
249
245
|
{
|
|
250
|
-
title: '
|
|
251
|
-
skip: () => !isDarwin() && 'Not macOS',
|
|
252
|
-
task: async (_ctx, task) => {
|
|
253
|
-
task.output = chalk.dim('Required to authorize curl access to the Keychain certificate');
|
|
254
|
-
ctx._keychainPassword = await prompt('macOS login password (for Keychain access)');
|
|
255
|
-
if (!ctx._keychainPassword) {
|
|
256
|
-
throw new Error('Keychain password is required on macOS to grant curl access to the certificate.');
|
|
257
|
-
}
|
|
258
|
-
task.output = 'Password received';
|
|
259
|
-
},
|
|
260
|
-
rendererOptions: { persistentOutput: true },
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
title: isDarwin() ? 'Importing certificate into Keychain' : 'Storing certificate',
|
|
246
|
+
title: 'Storing certificate',
|
|
264
247
|
task: async (_ctx, task) => {
|
|
265
248
|
const result = await storeEnrolledCert(
|
|
266
249
|
ctx._keyData.keyPath,
|
|
@@ -268,16 +251,10 @@ async function runTokenSetup(flags) {
|
|
|
268
251
|
ctx._caCertPem,
|
|
269
252
|
ctx.resolvedLabel,
|
|
270
253
|
console,
|
|
271
|
-
{ keychainPassword: ctx._keychainPassword },
|
|
272
254
|
);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
} else {
|
|
277
|
-
ctx.p12Path = result.p12Path;
|
|
278
|
-
ctx.p12Password = result.p12Password;
|
|
279
|
-
task.output = `Certificate stored at ${result.p12Path}`;
|
|
280
|
-
}
|
|
255
|
+
ctx.p12Path = result.p12Path;
|
|
256
|
+
ctx.p12Password = result.p12Password;
|
|
257
|
+
task.output = `Certificate stored at ${result.p12Path}`;
|
|
281
258
|
},
|
|
282
259
|
rendererOptions: { persistentOutput: true },
|
|
283
260
|
},
|
|
@@ -291,9 +268,7 @@ async function runTokenSetup(flags) {
|
|
|
291
268
|
{
|
|
292
269
|
title: 'Verifying panel connectivity',
|
|
293
270
|
task: async (_ctx, task) => {
|
|
294
|
-
const authConfig = ctx.
|
|
295
|
-
? { panelUrl: ctx.panelUrl, authMethod: 'keychain', keychainIdentity: ctx.keychainIdentity }
|
|
296
|
-
: { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
|
|
271
|
+
const authConfig = { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
|
|
297
272
|
const health = await fetchHealth(authConfig);
|
|
298
273
|
task.output = `Panel is reachable (status: ${health.status || 'ok'})`;
|
|
299
274
|
},
|
|
@@ -315,9 +290,7 @@ async function runTokenSetup(flags) {
|
|
|
315
290
|
{
|
|
316
291
|
title: 'Fetching tunnel configuration',
|
|
317
292
|
task: async (_ctx, task) => {
|
|
318
|
-
const authConfig = ctx.
|
|
319
|
-
? { panelUrl: ctx.panelUrl, authMethod: 'keychain', keychainIdentity: ctx.keychainIdentity }
|
|
320
|
-
: { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
|
|
293
|
+
const authConfig = { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
|
|
321
294
|
|
|
322
295
|
const agentConfig = await fetchAgentConfig(authConfig);
|
|
323
296
|
ctx.domain = agentConfig.domain;
|
|
@@ -376,30 +349,24 @@ async function runTokenSetup(flags) {
|
|
|
376
349
|
task: async () => {
|
|
377
350
|
const configData = {
|
|
378
351
|
panelUrl: ctx.panelUrl,
|
|
352
|
+
authMethod: 'p12',
|
|
353
|
+
p12Path: ctx.p12Path,
|
|
354
|
+
p12Password: ctx.p12Password,
|
|
379
355
|
agentLabel: ctx.agentLabel,
|
|
380
356
|
domain: ctx.domain,
|
|
381
357
|
chiselVersion: ctx.chiselVersion,
|
|
382
358
|
setupAt: new Date().toISOString(),
|
|
383
359
|
};
|
|
384
360
|
|
|
385
|
-
if (ctx.keychainIdentity) {
|
|
386
|
-
configData.authMethod = 'keychain';
|
|
387
|
-
configData.keychainIdentity = ctx.keychainIdentity;
|
|
388
|
-
} else {
|
|
389
|
-
configData.authMethod = 'p12';
|
|
390
|
-
configData.p12Path = ctx.p12Path;
|
|
391
|
-
configData.p12Password = ctx.p12Password;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
361
|
await saveAgentConfig(ctx.resolvedLabel, configData);
|
|
395
362
|
|
|
396
363
|
// Add or update registry entry
|
|
397
364
|
await upsertAgent({
|
|
398
365
|
label: ctx.resolvedLabel,
|
|
399
366
|
panelUrl: ctx.panelUrl,
|
|
400
|
-
authMethod:
|
|
401
|
-
p12Path:
|
|
402
|
-
keychainIdentity:
|
|
367
|
+
authMethod: 'p12',
|
|
368
|
+
p12Path: ctx.p12Path,
|
|
369
|
+
keychainIdentity: null,
|
|
403
370
|
agentLabel: ctx.agentLabel,
|
|
404
371
|
domain: ctx.domain,
|
|
405
372
|
chiselVersion: ctx.chiselVersion,
|
|
@@ -454,7 +421,6 @@ async function runTokenSetupJson(flags) {
|
|
|
454
421
|
explicitLabel: flags.label,
|
|
455
422
|
agentLabel: null,
|
|
456
423
|
resolvedLabel: null,
|
|
457
|
-
keychainIdentity: null,
|
|
458
424
|
p12Path: null,
|
|
459
425
|
p12Password: null,
|
|
460
426
|
chiselVersion: null,
|
|
@@ -513,7 +479,7 @@ async function runTokenSetupJson(flags) {
|
|
|
513
479
|
},
|
|
514
480
|
{
|
|
515
481
|
key: 'import_cert',
|
|
516
|
-
title:
|
|
482
|
+
title: 'Storing certificate',
|
|
517
483
|
fn: async () => {
|
|
518
484
|
const result = await storeEnrolledCert(
|
|
519
485
|
ctx._keyData.keyPath,
|
|
@@ -522,12 +488,8 @@ async function runTokenSetupJson(flags) {
|
|
|
522
488
|
ctx.resolvedLabel,
|
|
523
489
|
{ log: () => {}, warn: () => {}, error: () => {} },
|
|
524
490
|
);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
} else {
|
|
528
|
-
ctx.p12Path = result.p12Path;
|
|
529
|
-
ctx.p12Password = result.p12Password;
|
|
530
|
-
}
|
|
491
|
+
ctx.p12Path = result.p12Path;
|
|
492
|
+
ctx.p12Password = result.p12Password;
|
|
531
493
|
},
|
|
532
494
|
},
|
|
533
495
|
{
|
|
@@ -542,9 +504,7 @@ async function runTokenSetupJson(flags) {
|
|
|
542
504
|
key: 'verify_connectivity',
|
|
543
505
|
title: 'Verifying panel connectivity',
|
|
544
506
|
fn: async () => {
|
|
545
|
-
const authConfig = ctx.
|
|
546
|
-
? { panelUrl: ctx.panelUrl, authMethod: 'keychain', keychainIdentity: ctx.keychainIdentity }
|
|
547
|
-
: { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
|
|
507
|
+
const authConfig = { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
|
|
548
508
|
await fetchHealth(authConfig);
|
|
549
509
|
},
|
|
550
510
|
},
|
|
@@ -560,9 +520,7 @@ async function runTokenSetupJson(flags) {
|
|
|
560
520
|
key: 'fetch_config',
|
|
561
521
|
title: 'Fetching tunnel configuration',
|
|
562
522
|
fn: async () => {
|
|
563
|
-
const authConfig = ctx.
|
|
564
|
-
? { panelUrl: ctx.panelUrl, authMethod: 'keychain', keychainIdentity: ctx.keychainIdentity }
|
|
565
|
-
: { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
|
|
523
|
+
const authConfig = { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
|
|
566
524
|
|
|
567
525
|
const agentConfig = await fetchAgentConfig(authConfig);
|
|
568
526
|
ctx.domain = agentConfig.domain;
|
|
@@ -619,29 +577,23 @@ async function runTokenSetupJson(flags) {
|
|
|
619
577
|
fn: async () => {
|
|
620
578
|
const configData = {
|
|
621
579
|
panelUrl: ctx.panelUrl,
|
|
580
|
+
authMethod: 'p12',
|
|
581
|
+
p12Path: ctx.p12Path,
|
|
582
|
+
p12Password: ctx.p12Password,
|
|
622
583
|
agentLabel: ctx.agentLabel,
|
|
623
584
|
domain: ctx.domain,
|
|
624
585
|
chiselVersion: ctx.chiselVersion,
|
|
625
586
|
setupAt: new Date().toISOString(),
|
|
626
587
|
};
|
|
627
588
|
|
|
628
|
-
if (ctx.keychainIdentity) {
|
|
629
|
-
configData.authMethod = 'keychain';
|
|
630
|
-
configData.keychainIdentity = ctx.keychainIdentity;
|
|
631
|
-
} else {
|
|
632
|
-
configData.authMethod = 'p12';
|
|
633
|
-
configData.p12Path = ctx.p12Path;
|
|
634
|
-
configData.p12Password = ctx.p12Password;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
589
|
await saveAgentConfig(ctx.resolvedLabel, configData);
|
|
638
590
|
|
|
639
591
|
await upsertAgent({
|
|
640
592
|
label: ctx.resolvedLabel,
|
|
641
593
|
panelUrl: ctx.panelUrl,
|
|
642
|
-
authMethod:
|
|
643
|
-
p12Path:
|
|
644
|
-
keychainIdentity:
|
|
594
|
+
authMethod: 'p12',
|
|
595
|
+
p12Path: ctx.p12Path,
|
|
596
|
+
keychainIdentity: null,
|
|
645
597
|
agentLabel: ctx.agentLabel,
|
|
646
598
|
domain: ctx.domain,
|
|
647
599
|
chiselVersion: ctx.chiselVersion,
|
|
@@ -664,12 +616,17 @@ async function runTokenSetupJson(flags) {
|
|
|
664
616
|
process.exit(1);
|
|
665
617
|
}
|
|
666
618
|
|
|
619
|
+
// The p12Password transits via stdout pipe to the parent process (Tauri desktop app),
|
|
620
|
+
// which stores it in the OS credential store. Pipes are not visible in process listings.
|
|
621
|
+
// This is the same trust boundary as the server provisioner's SCP-based P12 transfer.
|
|
667
622
|
emitJson({
|
|
668
623
|
event: 'complete',
|
|
669
624
|
agent: {
|
|
670
625
|
label: ctx.resolvedLabel,
|
|
671
626
|
panelUrl: ctx.panelUrl,
|
|
672
|
-
authMethod:
|
|
627
|
+
authMethod: 'p12',
|
|
628
|
+
p12Path: ctx.p12Path,
|
|
629
|
+
p12Password: ctx.p12Password,
|
|
673
630
|
domain: ctx.domain,
|
|
674
631
|
chiselVersion: ctx.chiselVersion,
|
|
675
632
|
},
|
|
@@ -690,9 +647,7 @@ async function runP12Setup(options = {}) {
|
|
|
690
647
|
|
|
691
648
|
console.log('');
|
|
692
649
|
console.log(chalk.bold(' Portlama Agent Setup'));
|
|
693
|
-
console.log(chalk.dim(
|
|
694
|
-
? ' Connect this Mac to your Portlama server.'
|
|
695
|
-
: ' Connect this machine to your Portlama server.'));
|
|
650
|
+
console.log(chalk.dim(' Connect this machine to your Portlama server.'));
|
|
696
651
|
console.log('');
|
|
697
652
|
console.log(chalk.dim(' The admin must generate an agent certificate from the panel first:'));
|
|
698
653
|
console.log(chalk.dim(' Panel → Certificates → Agent Certificates → Generate'));
|
package/src/lib/cert-store.js
CHANGED
|
@@ -1,75 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Portable certificate storage for token-based enrollment.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Stores enrolled certificates as P12 files on all platforms.
|
|
5
|
+
* The P12 password is returned to the caller for secure storage
|
|
6
|
+
* (e.g., macOS Keychain via security-framework, Linux libsecret).
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import crypto from 'node:crypto';
|
|
8
10
|
import { writeFile, access, constants } from 'node:fs/promises';
|
|
9
11
|
import path from 'node:path';
|
|
10
12
|
import { execa } from 'execa';
|
|
11
|
-
import {
|
|
13
|
+
import { agentDataDir } from './platform.js';
|
|
12
14
|
import { secureDelete } from './keychain.js';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
* Store an enrolled certificate
|
|
17
|
+
* Store an enrolled certificate as a P12 file.
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
+
* Creates a P12 bundle from key + cert + CA at
|
|
20
|
+
* ~/.portlama/agents/<label>/client.p12 with mode 0600.
|
|
19
21
|
*
|
|
20
22
|
* @param {string} keyPath - Path to the temporary private key PEM
|
|
21
23
|
* @param {string} certPem - PEM-encoded signed certificate
|
|
22
24
|
* @param {string} caCertPem - PEM-encoded CA certificate
|
|
23
25
|
* @param {string} label - Agent label
|
|
24
26
|
* @param {import('pino').Logger | Console} logger
|
|
25
|
-
* @
|
|
26
|
-
* @returns {Promise<{ identity?: string, p12Path?: string, p12Password?: string }>}
|
|
27
|
+
* @returns {Promise<{ p12Path: string, p12Password: string }>}
|
|
27
28
|
*/
|
|
28
|
-
export async function storeEnrolledCert(keyPath, certPem, caCertPem, label, logger
|
|
29
|
-
if (isDarwin()) {
|
|
30
|
-
const { importIdentityToKeychain } = await import('./keychain.js');
|
|
31
|
-
const { identity } = await importIdentityToKeychain(keyPath, certPem, caCertPem, label, logger, options);
|
|
32
|
-
return { identity };
|
|
33
|
-
}
|
|
34
|
-
return storeP12Linux(keyPath, certPem, caCertPem, label, logger);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Check if an enrolled certificate exists.
|
|
39
|
-
* @param {string} label - Agent label
|
|
40
|
-
* @returns {Promise<boolean>}
|
|
41
|
-
*/
|
|
42
|
-
export async function enrolledCertExists(label) {
|
|
43
|
-
if (isDarwin()) {
|
|
44
|
-
const { keychainIdentityExists } = await import('./keychain.js');
|
|
45
|
-
return keychainIdentityExists(label);
|
|
46
|
-
}
|
|
47
|
-
return linuxP12Exists(label);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Remove an enrolled certificate.
|
|
52
|
-
* @param {string} label - Agent label
|
|
53
|
-
*/
|
|
54
|
-
export async function removeEnrolledCert(label) {
|
|
55
|
-
if (isDarwin()) {
|
|
56
|
-
const { removeKeychainIdentity } = await import('./keychain.js');
|
|
57
|
-
return removeKeychainIdentity(label);
|
|
58
|
-
}
|
|
59
|
-
return removeLinuxP12(label);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// Linux — P12 file storage
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Create a P12 from key + cert + CA and store at ~/.portlama/client.p12.
|
|
68
|
-
*
|
|
69
|
-
* Uses the same `-keypbe PBE-SHA1-3DES` parameters as keychain.js for
|
|
70
|
-
* maximum curl compatibility.
|
|
71
|
-
*/
|
|
72
|
-
async function storeP12Linux(keyPath, certPem, caCertPem, label, logger) {
|
|
29
|
+
export async function storeEnrolledCert(keyPath, certPem, caCertPem, label, logger) {
|
|
73
30
|
const dataDir = agentDataDir(label);
|
|
74
31
|
const p12Path = path.join(dataDir, 'client.p12');
|
|
75
32
|
const suffix = crypto.randomBytes(8).toString('hex');
|
|
@@ -125,7 +82,12 @@ async function storeP12Linux(keyPath, certPem, caCertPem, label, logger) {
|
|
|
125
82
|
}
|
|
126
83
|
}
|
|
127
84
|
|
|
128
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Check if an enrolled certificate exists.
|
|
87
|
+
* @param {string} label - Agent label
|
|
88
|
+
* @returns {Promise<boolean>}
|
|
89
|
+
*/
|
|
90
|
+
export async function enrolledCertExists(label) {
|
|
129
91
|
try {
|
|
130
92
|
const p12Path = path.join(agentDataDir(label), 'client.p12');
|
|
131
93
|
await access(p12Path, constants.F_OK);
|
|
@@ -135,7 +97,11 @@ async function linuxP12Exists(label) {
|
|
|
135
97
|
}
|
|
136
98
|
}
|
|
137
99
|
|
|
138
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Remove an enrolled certificate.
|
|
102
|
+
* @param {string} label - Agent label
|
|
103
|
+
*/
|
|
104
|
+
export async function removeEnrolledCert(label) {
|
|
139
105
|
try {
|
|
140
106
|
const p12Path = path.join(agentDataDir(label), 'client.p12');
|
|
141
107
|
await secureDelete(p12Path);
|
package/src/lib/keychain.js
CHANGED
|
@@ -4,6 +4,23 @@ import path from 'node:path';
|
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import { AGENT_DIR } from './platform.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Prompt for the macOS login Keychain password via a native OS dialog.
|
|
9
|
+
* Uses osascript to display a secure input dialog (hidden answer).
|
|
10
|
+
* Throws if the user cancels.
|
|
11
|
+
*
|
|
12
|
+
* @returns {Promise<string>}
|
|
13
|
+
*/
|
|
14
|
+
async function promptKeychainPassword() {
|
|
15
|
+
const { stdout } = await execa('osascript', [
|
|
16
|
+
'-e',
|
|
17
|
+
'display dialog "Portlama needs your macOS login password to store the agent certificate in your Keychain." default answer "" with hidden answer buttons {"Cancel", "OK"} default button "OK" with title "Portlama — Keychain Access" with icon caution',
|
|
18
|
+
'-e',
|
|
19
|
+
'text returned of result',
|
|
20
|
+
]);
|
|
21
|
+
return stdout.trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
/**
|
|
8
25
|
* Overwrite a file with random bytes, then unlink it.
|
|
9
26
|
* Provides defense-in-depth against key recovery from disk.
|
|
@@ -82,10 +99,9 @@ export async function generateKeypairAndCSR(label) {
|
|
|
82
99
|
* @param {string} caCertPem - PEM-encoded CA certificate
|
|
83
100
|
* @param {string} label - Agent label
|
|
84
101
|
* @param {import('pino').Logger | Console} logger
|
|
85
|
-
* @param {{ keychainPassword?: string }} [options]
|
|
86
102
|
* @returns {Promise<{ identity: string }>}
|
|
87
103
|
*/
|
|
88
|
-
export async function importIdentityToKeychain(keyPath, certPem, caCertPem, label, logger
|
|
104
|
+
export async function importIdentityToKeychain(keyPath, certPem, caCertPem, label, logger) {
|
|
89
105
|
const suffix = crypto.randomBytes(8).toString('hex');
|
|
90
106
|
const certPath = path.join(AGENT_DIR, `.tmp-cert-${suffix}.pem`);
|
|
91
107
|
const caPath = path.join(AGENT_DIR, `.tmp-ca-${suffix}.pem`);
|
|
@@ -160,8 +176,8 @@ export async function importIdentityToKeychain(keyPath, certPem, caCertPem, labe
|
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
// Set the key partition list so curl can access the identity without prompts.
|
|
163
|
-
// Requires the login Keychain password —
|
|
164
|
-
const keychainPassword =
|
|
179
|
+
// Requires the login Keychain password — prompt via native macOS dialog.
|
|
180
|
+
const keychainPassword = await promptKeychainPassword();
|
|
165
181
|
try {
|
|
166
182
|
await execa('security', [
|
|
167
183
|
'set-key-partition-list',
|
|
@@ -172,11 +188,10 @@ export async function importIdentityToKeychain(keyPath, certPem, caCertPem, labe
|
|
|
172
188
|
'-l', // match by Label (friendly name from -name), not -D (Description)
|
|
173
189
|
identityName,
|
|
174
190
|
]);
|
|
175
|
-
} catch
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
logger.log?.(`Warning: Could not set key partition list for ${label}`);
|
|
191
|
+
} catch {
|
|
192
|
+
throw new Error(
|
|
193
|
+
'Could not authorize Keychain access. The macOS login password may be incorrect.',
|
|
194
|
+
);
|
|
180
195
|
}
|
|
181
196
|
|
|
182
197
|
return { identity: identityName };
|