navy 7.0.0-alpha.4 → 7.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/lib/cli/__tests__/https.js +193 -1
- package/lib/cli/https.js +103 -18
- package/lib/cli/program.js +11 -1
- package/lib/navy/__tests__/index.js +142 -0
- package/lib/navy/index.js +12 -2
- package/lib/util/__tests__/https.js +187 -10
- package/lib/util/__tests__/install-root-ca.js +282 -0
- package/lib/util/https.js +166 -40
- package/lib/util/install-root-ca.js +114 -0
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ describe('util/https', function () {
|
|
|
18
18
|
let pkiStub;
|
|
19
19
|
let mdStub;
|
|
20
20
|
let httpsModule;
|
|
21
|
+
let certObj;
|
|
21
22
|
function loadModule() {
|
|
22
23
|
httpsModule = _proxyquire.default.noCallThru()('../https', {
|
|
23
24
|
'../config': {
|
|
@@ -52,9 +53,21 @@ describe('util/https', function () {
|
|
|
52
53
|
mkdirSync: sandbox.stub(),
|
|
53
54
|
unlinkSync: sandbox.stub(),
|
|
54
55
|
readFileSync: sandbox.stub().returns('--PEM--'),
|
|
55
|
-
writeFileSync: sandbox.stub()
|
|
56
|
+
writeFileSync: sandbox.stub(),
|
|
57
|
+
copyFileSync: sandbox.stub()
|
|
56
58
|
};
|
|
57
|
-
const
|
|
59
|
+
const caCertFromPem = {
|
|
60
|
+
subject: {
|
|
61
|
+
attributes: [{
|
|
62
|
+
name: 'commonName',
|
|
63
|
+
value: 'ca'
|
|
64
|
+
}]
|
|
65
|
+
},
|
|
66
|
+
generateSubjectKeyIdentifier: sandbox.stub().returns({
|
|
67
|
+
getBytes: () => 'ca-subject-key-id'
|
|
68
|
+
})
|
|
69
|
+
};
|
|
70
|
+
certObj = {
|
|
58
71
|
publicKey: null,
|
|
59
72
|
serialNumber: null,
|
|
60
73
|
validity: {
|
|
@@ -81,14 +94,7 @@ describe('util/https', function () {
|
|
|
81
94
|
publicKeyToPem: sandbox.stub().returns('pub-pem'),
|
|
82
95
|
certificateToPem: sandbox.stub().returns('cert-pem'),
|
|
83
96
|
privateKeyFromPem: sandbox.stub().returns('priv-key-obj'),
|
|
84
|
-
certificateFromPem: sandbox.stub().returns(
|
|
85
|
-
subject: {
|
|
86
|
-
attributes: [{
|
|
87
|
-
name: 'commonName',
|
|
88
|
-
value: 'ca'
|
|
89
|
-
}]
|
|
90
|
-
}
|
|
91
|
-
})
|
|
97
|
+
certificateFromPem: sandbox.stub().returns(caCertFromPem)
|
|
92
98
|
};
|
|
93
99
|
mdStub = {
|
|
94
100
|
sha256: {
|
|
@@ -297,5 +303,176 @@ describe('util/https', function () {
|
|
|
297
303
|
}
|
|
298
304
|
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
299
305
|
});
|
|
306
|
+
it('should prefer the parsed hostname over the raw certName segment', async function () {
|
|
307
|
+
fsStub.existsSync.callsFake(p => {
|
|
308
|
+
if (p === '/cfg/tls-certs') return true;
|
|
309
|
+
if (p === '/default-ca-dir') return true;
|
|
310
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
311
|
+
return false;
|
|
312
|
+
});
|
|
313
|
+
await httpsModule.createCert({
|
|
314
|
+
serviceUrl: 'https://tls-host.example.com:8443/app'
|
|
315
|
+
});
|
|
316
|
+
(0, _chai.expect)(certObj.setSubject.firstCall.args[0][0].value).to.equal('tls-host.example.com');
|
|
317
|
+
});
|
|
318
|
+
it('should keep certName when serviceUrl is not a valid URL', async function () {
|
|
319
|
+
fsStub.existsSync.callsFake(p => {
|
|
320
|
+
if (p === '/cfg/tls-certs') return true;
|
|
321
|
+
if (p === '/default-ca-dir') return true;
|
|
322
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
323
|
+
return false;
|
|
324
|
+
});
|
|
325
|
+
await httpsModule.createCert({
|
|
326
|
+
serviceUrl: 'https://bad['
|
|
327
|
+
});
|
|
328
|
+
const writtenPaths = fsStub.writeFileSync.getCalls().map(c => c.args[0]);
|
|
329
|
+
(0, _chai.expect)(writtenPaths.some(p => p.endsWith('bad[.key'))).to.equal(true);
|
|
330
|
+
});
|
|
331
|
+
it('should fall back to certName when serviceUrl has no hostname', async function () {
|
|
332
|
+
fsStub.existsSync.callsFake(p => {
|
|
333
|
+
if (p === '/cfg/tls-certs') return true;
|
|
334
|
+
if (p === '/default-ca-dir') return true;
|
|
335
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
336
|
+
return false;
|
|
337
|
+
});
|
|
338
|
+
await httpsModule.createCert({
|
|
339
|
+
serviceUrl: 'https://?q=1'
|
|
340
|
+
});
|
|
341
|
+
(0, _chai.expect)(certObj.setSubject.firstCall.args[0][0].value).to.equal('?q=1');
|
|
342
|
+
});
|
|
343
|
+
it('should wrap non-Error signing failures in NavyError', async function () {
|
|
344
|
+
fsStub.existsSync.callsFake(p => {
|
|
345
|
+
if (p === '/cfg/tls-certs') return true;
|
|
346
|
+
if (p === '/default-ca-dir') return true;
|
|
347
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
348
|
+
return false;
|
|
349
|
+
});
|
|
350
|
+
pkiStub.certificateFromPem.callsFake(() => {
|
|
351
|
+
const failure = 'string failure';
|
|
352
|
+
throw failure;
|
|
353
|
+
});
|
|
354
|
+
let caught;
|
|
355
|
+
try {
|
|
356
|
+
await httpsModule.createCert({
|
|
357
|
+
serviceUrl: 'https://web.local'
|
|
358
|
+
});
|
|
359
|
+
} catch (err) {
|
|
360
|
+
caught = err;
|
|
361
|
+
}
|
|
362
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
363
|
+
(0, _chai.expect)(caught.message).to.equal('string failure');
|
|
364
|
+
});
|
|
365
|
+
it('should use IP SAN when hostName is an IP address', async function () {
|
|
366
|
+
fsStub.existsSync.callsFake(p => {
|
|
367
|
+
if (p === '/cfg/tls-certs') return true;
|
|
368
|
+
if (p === '/default-ca-dir') return true;
|
|
369
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
370
|
+
return false;
|
|
371
|
+
});
|
|
372
|
+
await httpsModule.createCert({
|
|
373
|
+
hostName: '127.0.0.1'
|
|
374
|
+
});
|
|
375
|
+
const extensions = certObj.setExtensions.firstCall.args[0];
|
|
376
|
+
const sanExt = extensions.find(ext => ext.name === 'subjectAltName');
|
|
377
|
+
(0, _chai.expect)(sanExt.altNames).to.eql([{
|
|
378
|
+
type: 7,
|
|
379
|
+
ip: '127.0.0.1'
|
|
380
|
+
}]);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
describe('hostNameFromIssueUrl', function () {
|
|
384
|
+
it('should throw when the URL is empty', function () {
|
|
385
|
+
(0, _chai.expect)(() => httpsModule.hostNameFromIssueUrl('')).to.throw(/must not be empty/);
|
|
386
|
+
(0, _chai.expect)(() => httpsModule.hostNameFromIssueUrl(' ')).to.throw(/must not be empty/);
|
|
387
|
+
});
|
|
388
|
+
it('should prepend https when no scheme is supplied', function () {
|
|
389
|
+
(0, _chai.expect)(httpsModule.hostNameFromIssueUrl('example.local')).to.equal('example.local');
|
|
390
|
+
});
|
|
391
|
+
it('should accept an explicit scheme', function () {
|
|
392
|
+
(0, _chai.expect)(httpsModule.hostNameFromIssueUrl('http://example.local')).to.equal('example.local');
|
|
393
|
+
});
|
|
394
|
+
it('should throw when the URL cannot be parsed', function () {
|
|
395
|
+
(0, _chai.expect)(() => httpsModule.hostNameFromIssueUrl('http://')).to.throw(/Invalid URL/);
|
|
396
|
+
});
|
|
397
|
+
it('should throw when no hostname can be determined', function () {
|
|
398
|
+
(0, _chai.expect)(() => httpsModule.hostNameFromIssueUrl('file:///tmp/cert.crt')).to.throw(/Could not determine a hostname/);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
describe('issueLeafCertForUrl', function () {
|
|
402
|
+
it('should write cert, key, and CA copy files to the output dir', async function () {
|
|
403
|
+
fsStub.existsSync.callsFake(p => {
|
|
404
|
+
if (p === '/default-ca-dir') return true;
|
|
405
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
406
|
+
return false;
|
|
407
|
+
});
|
|
408
|
+
const result = await httpsModule.issueLeafCertForUrl('https://issued.local', '/out/certs');
|
|
409
|
+
(0, _chai.expect)(fsStub.mkdirSync.calledWith('/out/certs', {
|
|
410
|
+
recursive: true
|
|
411
|
+
})).to.equal(true);
|
|
412
|
+
(0, _chai.expect)(fsStub.writeFileSync.callCount).to.equal(2);
|
|
413
|
+
(0, _chai.expect)(fsStub.copyFileSync.calledOnce).to.equal(true);
|
|
414
|
+
(0, _chai.expect)(result.certPath).to.equal('/out/certs/issued.local.crt');
|
|
415
|
+
(0, _chai.expect)(result.keyPath).to.equal('/out/certs/issued.local.key');
|
|
416
|
+
(0, _chai.expect)(result.caCopyPath).to.equal('/out/certs/navy-root-ca.crt');
|
|
417
|
+
});
|
|
418
|
+
it('should sanitise unsafe characters in the output file base name', async function () {
|
|
419
|
+
fsStub.existsSync.callsFake(p => {
|
|
420
|
+
if (p === '/default-ca-dir') return true;
|
|
421
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
422
|
+
return false;
|
|
423
|
+
});
|
|
424
|
+
const result = await httpsModule.issueLeafCertForUrl('https://[::1]', '/out');
|
|
425
|
+
(0, _chai.expect)(result.certPath).to.equal('/out/___1_.crt');
|
|
426
|
+
(0, _chai.expect)(result.keyPath).to.equal('/out/___1_.key');
|
|
427
|
+
});
|
|
428
|
+
it('should wrap non-Error build failures in NavyError', async function () {
|
|
429
|
+
fsStub.existsSync.callsFake(p => {
|
|
430
|
+
if (p === '/default-ca-dir') return true;
|
|
431
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
432
|
+
return false;
|
|
433
|
+
});
|
|
434
|
+
pkiStub.certificateFromPem.callsFake(() => {
|
|
435
|
+
const failure = 'forge broke';
|
|
436
|
+
throw failure;
|
|
437
|
+
});
|
|
438
|
+
let caught;
|
|
439
|
+
try {
|
|
440
|
+
await httpsModule.issueLeafCertForUrl('https://issued.local', '/out');
|
|
441
|
+
} catch (err) {
|
|
442
|
+
caught = err;
|
|
443
|
+
}
|
|
444
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
445
|
+
(0, _chai.expect)(caught.message).to.equal('forge broke');
|
|
446
|
+
});
|
|
447
|
+
it('should wrap build failures in NavyError', async function () {
|
|
448
|
+
fsStub.existsSync.callsFake(p => {
|
|
449
|
+
if (p === '/default-ca-dir') return true;
|
|
450
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
451
|
+
return false;
|
|
452
|
+
});
|
|
453
|
+
pkiStub.certificateFromPem.throws(new Error('forge broke'));
|
|
454
|
+
let caught;
|
|
455
|
+
try {
|
|
456
|
+
await httpsModule.issueLeafCertForUrl('https://issued.local', '/out');
|
|
457
|
+
} catch (err) {
|
|
458
|
+
caught = err;
|
|
459
|
+
}
|
|
460
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
461
|
+
(0, _chai.expect)(caught.message).to.equal('forge broke');
|
|
462
|
+
});
|
|
463
|
+
it('should use IP SAN when issuing for an IP address URL', async function () {
|
|
464
|
+
fsStub.existsSync.callsFake(p => {
|
|
465
|
+
if (p === '/default-ca-dir') return true;
|
|
466
|
+
if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
|
|
467
|
+
return false;
|
|
468
|
+
});
|
|
469
|
+
await httpsModule.issueLeafCertForUrl('https://192.168.1.10', '/out');
|
|
470
|
+
const extensions = certObj.setExtensions.firstCall.args[0];
|
|
471
|
+
const sanExt = extensions.find(ext => ext.name === 'subjectAltName');
|
|
472
|
+
(0, _chai.expect)(sanExt.altNames).to.eql([{
|
|
473
|
+
type: 7,
|
|
474
|
+
ip: '192.168.1.10'
|
|
475
|
+
}]);
|
|
476
|
+
});
|
|
300
477
|
});
|
|
301
478
|
});
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _chai = require("chai");
|
|
5
|
+
var _sinon = _interopRequireDefault(require("sinon"));
|
|
6
|
+
var _proxyquire = _interopRequireDefault(require("proxyquire"));
|
|
7
|
+
var _path = _interopRequireDefault(require("path"));
|
|
8
|
+
var _errors = require("../../errors");
|
|
9
|
+
/* eslint-env mocha */
|
|
10
|
+
|
|
11
|
+
describe('util/install-root-ca', function () {
|
|
12
|
+
let sandbox;
|
|
13
|
+
let fsStub;
|
|
14
|
+
let osStub;
|
|
15
|
+
let execFileSyncStub;
|
|
16
|
+
let installRootCaModule;
|
|
17
|
+
let platformStub;
|
|
18
|
+
const caPath = '/tmp/navy/ca.crt';
|
|
19
|
+
function certutilPath(systemRoot = 'C:\\Windows') {
|
|
20
|
+
return _path.default.join(systemRoot, 'System32', 'certutil.exe');
|
|
21
|
+
}
|
|
22
|
+
function expectThrownMessage(fn, messagePattern) {
|
|
23
|
+
let caught;
|
|
24
|
+
try {
|
|
25
|
+
fn();
|
|
26
|
+
} catch (err) {
|
|
27
|
+
caught = err;
|
|
28
|
+
}
|
|
29
|
+
(0, _chai.expect)(caught).to.not.equal(undefined);
|
|
30
|
+
(0, _chai.expect)(String(caught.message)).to.match(messagePattern);
|
|
31
|
+
}
|
|
32
|
+
function loadModule() {
|
|
33
|
+
installRootCaModule = _proxyquire.default.noCallThru()('../install-root-ca', {
|
|
34
|
+
'../errors': {
|
|
35
|
+
NavyError: _errors.NavyError
|
|
36
|
+
},
|
|
37
|
+
fs: fsStub,
|
|
38
|
+
os: osStub,
|
|
39
|
+
child_process: {
|
|
40
|
+
execFileSync: execFileSyncStub
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
beforeEach(function () {
|
|
45
|
+
sandbox = _sinon.default.createSandbox();
|
|
46
|
+
fsStub = {
|
|
47
|
+
existsSync: sandbox.stub()
|
|
48
|
+
};
|
|
49
|
+
osStub = {
|
|
50
|
+
homedir: sandbox.stub().returns('/Users/tester')
|
|
51
|
+
};
|
|
52
|
+
execFileSyncStub = sandbox.stub();
|
|
53
|
+
platformStub = sandbox.stub(process, 'platform').value('darwin');
|
|
54
|
+
loadModule();
|
|
55
|
+
});
|
|
56
|
+
afterEach(function () {
|
|
57
|
+
sandbox.restore();
|
|
58
|
+
});
|
|
59
|
+
describe('caughtErrorMessage', function () {
|
|
60
|
+
it('should return nullish values as strings', function () {
|
|
61
|
+
(0, _chai.expect)(installRootCaModule.caughtErrorMessage(null)).to.equal('null');
|
|
62
|
+
(0, _chai.expect)(installRootCaModule.caughtErrorMessage(undefined)).to.equal('undefined');
|
|
63
|
+
});
|
|
64
|
+
it('should return message text when it is present', function () {
|
|
65
|
+
(0, _chai.expect)(installRootCaModule.caughtErrorMessage({
|
|
66
|
+
message: 'object failed'
|
|
67
|
+
})).to.equal('object failed');
|
|
68
|
+
(0, _chai.expect)(installRootCaModule.caughtErrorMessage(new Error('error failed'))).to.equal('error failed');
|
|
69
|
+
});
|
|
70
|
+
it('should fall back to String(e) when no usable message exists', function () {
|
|
71
|
+
(0, _chai.expect)(installRootCaModule.caughtErrorMessage('plain failure')).to.equal('plain failure');
|
|
72
|
+
(0, _chai.expect)(installRootCaModule.caughtErrorMessage({})).to.equal('[object Object]');
|
|
73
|
+
(0, _chai.expect)(installRootCaModule.caughtErrorMessage({
|
|
74
|
+
message: ''
|
|
75
|
+
})).to.equal('[object Object]');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('installRootCaToTrustStore', function () {
|
|
79
|
+
it('should throw when the CA file does not exist', function () {
|
|
80
|
+
fsStub.existsSync.returns(false);
|
|
81
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /CA certificate not found/);
|
|
82
|
+
});
|
|
83
|
+
describe('macOS', function () {
|
|
84
|
+
beforeEach(function () {
|
|
85
|
+
platformStub.value('darwin');
|
|
86
|
+
fsStub.existsSync.callsFake(p => p === caPath || p.endsWith('login.keychain-db'));
|
|
87
|
+
});
|
|
88
|
+
it('should add the CA to the login keychain when found', function () {
|
|
89
|
+
installRootCaModule.installRootCaToTrustStore(caPath);
|
|
90
|
+
(0, _chai.expect)(execFileSyncStub.calledOnce).to.equal(true);
|
|
91
|
+
(0, _chai.expect)(execFileSyncStub.firstCall.args[0]).to.equal('security');
|
|
92
|
+
(0, _chai.expect)(execFileSyncStub.firstCall.args[1]).to.eql(['add-trusted-cert', '-d', '-r', 'trustRoot', '-k', '/Users/tester/Library/Keychains/login.keychain-db', caPath]);
|
|
93
|
+
});
|
|
94
|
+
it('should fall back to login.keychain when login.keychain-db is missing', function () {
|
|
95
|
+
fsStub.existsSync.callsFake(p => p === caPath || p.endsWith('login.keychain'));
|
|
96
|
+
installRootCaModule.installRootCaToTrustStore(caPath);
|
|
97
|
+
(0, _chai.expect)(execFileSyncStub.firstCall.args[1][5]).to.equal('/Users/tester/Library/Keychains/login.keychain');
|
|
98
|
+
});
|
|
99
|
+
it('should throw when no login keychain can be found', function () {
|
|
100
|
+
fsStub.existsSync.callsFake(p => p === caPath);
|
|
101
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /Could not find your macOS login keychain/);
|
|
102
|
+
});
|
|
103
|
+
it('should wrap security command failures in NavyError', function () {
|
|
104
|
+
execFileSyncStub.throws(new Error('security failed'));
|
|
105
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /Could not add the Navy root CA/);
|
|
106
|
+
});
|
|
107
|
+
it('should use error.message when the thrown value exposes one', function () {
|
|
108
|
+
execFileSyncStub.throws({
|
|
109
|
+
message: 'security object failed'
|
|
110
|
+
});
|
|
111
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /security object failed/);
|
|
112
|
+
});
|
|
113
|
+
it('should stringify non-Error exec failures', function () {
|
|
114
|
+
execFileSyncStub.throws('plain failure');
|
|
115
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /plain failure/);
|
|
116
|
+
});
|
|
117
|
+
it('should stringify null exec failures', function () {
|
|
118
|
+
execFileSyncStub.callsFake(() => {
|
|
119
|
+
const thrown = null;
|
|
120
|
+
throw thrown;
|
|
121
|
+
});
|
|
122
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /null/);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe('linux', function () {
|
|
126
|
+
beforeEach(function () {
|
|
127
|
+
platformStub.value('linux');
|
|
128
|
+
fsStub.existsSync.callsFake(p => p === caPath);
|
|
129
|
+
});
|
|
130
|
+
it('should install via update-ca-certificates on Debian-style systems', function () {
|
|
131
|
+
fsStub.existsSync.callsFake(p => {
|
|
132
|
+
if (p === caPath) return true;
|
|
133
|
+
if (p === '/usr/sbin/update-ca-certificates') return true;
|
|
134
|
+
return false;
|
|
135
|
+
});
|
|
136
|
+
installRootCaModule.installRootCaToTrustStore(caPath);
|
|
137
|
+
(0, _chai.expect)(execFileSyncStub.callCount).to.equal(2);
|
|
138
|
+
(0, _chai.expect)(execFileSyncStub.firstCall.args[0]).to.equal('sudo');
|
|
139
|
+
(0, _chai.expect)(execFileSyncStub.firstCall.args[1]).to.eql(['cp', caPath, '/usr/local/share/ca-certificates/navy-dev-ca.crt']);
|
|
140
|
+
(0, _chai.expect)(execFileSyncStub.secondCall.args[1]).to.eql(['/usr/sbin/update-ca-certificates']);
|
|
141
|
+
});
|
|
142
|
+
it('should wrap Debian install failures in NavyError', function () {
|
|
143
|
+
fsStub.existsSync.callsFake(p => {
|
|
144
|
+
if (p === caPath) return true;
|
|
145
|
+
if (p === '/usr/sbin/update-ca-certificates') return true;
|
|
146
|
+
return false;
|
|
147
|
+
});
|
|
148
|
+
execFileSyncStub.throws(new Error('sudo denied'));
|
|
149
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /update-ca-certificates/);
|
|
150
|
+
});
|
|
151
|
+
it('should use error.message for Debian install failures when present', function () {
|
|
152
|
+
fsStub.existsSync.callsFake(p => {
|
|
153
|
+
if (p === caPath) return true;
|
|
154
|
+
if (p === '/usr/sbin/update-ca-certificates') return true;
|
|
155
|
+
return false;
|
|
156
|
+
});
|
|
157
|
+
execFileSyncStub.throws({
|
|
158
|
+
message: 'debian object failed'
|
|
159
|
+
});
|
|
160
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /debian object failed/);
|
|
161
|
+
});
|
|
162
|
+
it('should stringify null Debian install failures', function () {
|
|
163
|
+
fsStub.existsSync.callsFake(p => {
|
|
164
|
+
if (p === caPath) return true;
|
|
165
|
+
if (p === '/usr/sbin/update-ca-certificates') return true;
|
|
166
|
+
return false;
|
|
167
|
+
});
|
|
168
|
+
execFileSyncStub.callsFake(() => {
|
|
169
|
+
const thrown = null;
|
|
170
|
+
throw thrown;
|
|
171
|
+
});
|
|
172
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /null/);
|
|
173
|
+
});
|
|
174
|
+
it('should install via update-ca-trust on Fedora-style systems', function () {
|
|
175
|
+
fsStub.existsSync.callsFake(p => {
|
|
176
|
+
if (p === caPath) return true;
|
|
177
|
+
if (p === '/usr/bin/update-ca-trust') return true;
|
|
178
|
+
return false;
|
|
179
|
+
});
|
|
180
|
+
installRootCaModule.installRootCaToTrustStore(caPath);
|
|
181
|
+
(0, _chai.expect)(execFileSyncStub.callCount).to.equal(2);
|
|
182
|
+
(0, _chai.expect)(execFileSyncStub.secondCall.args[1]).to.eql(['/usr/bin/update-ca-trust', 'extract']);
|
|
183
|
+
});
|
|
184
|
+
it('should wrap Fedora install failures in NavyError', function () {
|
|
185
|
+
fsStub.existsSync.callsFake(p => {
|
|
186
|
+
if (p === caPath) return true;
|
|
187
|
+
if (p === '/usr/bin/update-ca-trust') return true;
|
|
188
|
+
return false;
|
|
189
|
+
});
|
|
190
|
+
execFileSyncStub.throws(new Error('trust failed'));
|
|
191
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /update-ca-trust/);
|
|
192
|
+
});
|
|
193
|
+
it('should use error.message for Fedora install failures when present', function () {
|
|
194
|
+
fsStub.existsSync.callsFake(p => {
|
|
195
|
+
if (p === caPath) return true;
|
|
196
|
+
if (p === '/usr/bin/update-ca-trust') return true;
|
|
197
|
+
return false;
|
|
198
|
+
});
|
|
199
|
+
execFileSyncStub.throws({
|
|
200
|
+
message: 'fedora object failed'
|
|
201
|
+
});
|
|
202
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /fedora object failed/);
|
|
203
|
+
});
|
|
204
|
+
it('should stringify null Fedora install failures', function () {
|
|
205
|
+
fsStub.existsSync.callsFake(p => {
|
|
206
|
+
if (p === caPath) return true;
|
|
207
|
+
if (p === '/usr/bin/update-ca-trust') return true;
|
|
208
|
+
return false;
|
|
209
|
+
});
|
|
210
|
+
execFileSyncStub.callsFake(() => {
|
|
211
|
+
const thrown = null;
|
|
212
|
+
throw thrown;
|
|
213
|
+
});
|
|
214
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /null/);
|
|
215
|
+
});
|
|
216
|
+
it('should throw when no supported Linux tooling is present', function () {
|
|
217
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /Automatic Linux install needs/);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
describe('windows', function () {
|
|
221
|
+
beforeEach(function () {
|
|
222
|
+
platformStub.value('win32');
|
|
223
|
+
fsStub.existsSync.callsFake(p => p === caPath);
|
|
224
|
+
});
|
|
225
|
+
it('should add the CA via certutil when available', function () {
|
|
226
|
+
const certutil = certutilPath();
|
|
227
|
+
fsStub.existsSync.callsFake(p => p === caPath || p === certutil);
|
|
228
|
+
installRootCaModule.installRootCaToTrustStore(caPath);
|
|
229
|
+
(0, _chai.expect)(execFileSyncStub.calledOnce).to.equal(true);
|
|
230
|
+
(0, _chai.expect)(execFileSyncStub.firstCall.args[0]).to.equal(certutil);
|
|
231
|
+
(0, _chai.expect)(execFileSyncStub.firstCall.args[1]).to.eql(['-user', '-addstore', 'Root', caPath]);
|
|
232
|
+
});
|
|
233
|
+
it('should use SystemRoot from the environment when set', function () {
|
|
234
|
+
const previousSystemRoot = process.env.SystemRoot;
|
|
235
|
+
process.env.SystemRoot = 'D:\\WinRoot';
|
|
236
|
+
const certutil = certutilPath('D:\\WinRoot');
|
|
237
|
+
fsStub.existsSync.callsFake(p => p === caPath || p === certutil);
|
|
238
|
+
try {
|
|
239
|
+
installRootCaModule.installRootCaToTrustStore(caPath);
|
|
240
|
+
(0, _chai.expect)(execFileSyncStub.firstCall.args[0]).to.equal(certutil);
|
|
241
|
+
} finally {
|
|
242
|
+
if (previousSystemRoot === undefined) {
|
|
243
|
+
delete process.env.SystemRoot;
|
|
244
|
+
} else {
|
|
245
|
+
process.env.SystemRoot = previousSystemRoot;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
it('should throw when certutil.exe is missing', function () {
|
|
250
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /certutil.exe not found/);
|
|
251
|
+
});
|
|
252
|
+
it('should wrap certutil failures in NavyError', function () {
|
|
253
|
+
const certutil = certutilPath();
|
|
254
|
+
fsStub.existsSync.callsFake(p => p === caPath || p === certutil);
|
|
255
|
+
execFileSyncStub.throws(new Error('certutil failed'));
|
|
256
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /Trusted Root store/);
|
|
257
|
+
});
|
|
258
|
+
it('should use error.message for certutil failures when present', function () {
|
|
259
|
+
const certutil = certutilPath();
|
|
260
|
+
fsStub.existsSync.callsFake(p => p === caPath || p === certutil);
|
|
261
|
+
execFileSyncStub.throws({
|
|
262
|
+
message: 'certutil object failed'
|
|
263
|
+
});
|
|
264
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /certutil object failed/);
|
|
265
|
+
});
|
|
266
|
+
it('should stringify null certutil failures', function () {
|
|
267
|
+
const certutil = certutilPath();
|
|
268
|
+
fsStub.existsSync.callsFake(p => p === caPath || p === certutil);
|
|
269
|
+
execFileSyncStub.callsFake(() => {
|
|
270
|
+
const thrown = null;
|
|
271
|
+
throw thrown;
|
|
272
|
+
});
|
|
273
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /null/);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
it('should throw on unsupported platforms', function () {
|
|
277
|
+
platformStub.value('freebsd');
|
|
278
|
+
fsStub.existsSync.returns(true);
|
|
279
|
+
expectThrownMessage(() => installRootCaModule.installRootCaToTrustStore(caPath), /not supported on freebsd/);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|