Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

mac-ca

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mac-ca - npm Package Compare versions

Comparing version
1.1.0
to
2.0.0
+7
.vscode/settings.json
{
"editor.formatOnSave": true,
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.convert = exports.Format = exports.addToGlobalAgent = exports.get = void 0;
var https_1 = require("https");
var tls_1 = require("tls");
var child_process_1 = require("child_process");
var formatter_1 = require("./formatter");
Object.defineProperty(exports, "Format", { enumerable: true, get: function () { return formatter_1.Format; } });
Object.defineProperty(exports, "convert", { enumerable: true, get: function () { return formatter_1.convert; } });
var isMac = process.platform === 'darwin';
var getParamsDefaults = {
keychain: 'all',
unique: true,
excludeBundled: true,
format: formatter_1.Format.pem
};
function get(params) {
if (params === void 0) { params = getParamsDefaults; }
if (!isMac) {
return [];
}
params = __assign(__assign({}, getParamsDefaults), params);
var splitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g;
var args = ['find-certificate', '-a', '-p'];
var result = [];
if (params.keychain === 'all' || params.keychain === 'SystemRootCertificates') {
var systemRootCertsPath = '/System/Library/Keychains/SystemRootCertificates.keychain';
var root = (0, child_process_1.spawnSync)('/usr/bin/security', args.concat(systemRootCertsPath))
.stdout.toString()
.split(splitPattern)
.map(function (c) { return c.trim(); });
result = __spreadArray(__spreadArray([], result, true), root, true);
}
if (params.keychain === 'all' || params.keychain === 'SystemRootCertificates') {
var trusted = (0, child_process_1.spawnSync)('/usr/bin/security', args)
.stdout.toString()
.split(splitPattern)
.map(function (c) { return c.trim(); });
result = __spreadArray(__spreadArray([], result, true), trusted, true);
}
if (params.unique || params.excludeBundled) {
var fingerprints_1 = result.map(function (c) { return (0, formatter_1.convert)(c, formatter_1.Format.fingerprint); });
var nodeFingerprints_1 = params.excludeBundled ?
tls_1.rootCertificates.map(function (c) { return (0, formatter_1.convert)(c, formatter_1.Format.fingerprint); }) :
[];
result = result.filter(function (pem, index) {
var fingerprint = fingerprints_1[index];
if (params.unique && index !== fingerprints_1.indexOf(fingerprint)) {
return false;
}
if (params.excludeBundled && nodeFingerprints_1.includes(fingerprint)) {
return false;
}
return true;
});
}
return result.map(function (c) { return (0, formatter_1.convert)(c, params.format); });
}
exports.get = get;
;
var originalCA = https_1.globalAgent.options.ca;
var addToGlobalAgent = function (params) {
if (params === void 0) { params = getParamsDefaults; }
if (!isMac) {
return;
}
var cas;
if (!Array.isArray(originalCA)) {
cas = typeof originalCA !== 'undefined' ? [originalCA] : [];
}
else {
cas = Array.from(originalCA);
}
get(__assign(__assign(__assign({}, getParamsDefaults), params), { format: formatter_1.Format.pem }))
.forEach(function (cert) { return cas.push(cert); });
https_1.globalAgent.options.ca = cas;
};
exports.addToGlobalAgent = addToGlobalAgent;
export type asn1 = {
serial: any;
issuer: any;
valid: any;
subject: any;
};
import * as forge from 'node-forge';
import { asn1 } from './asn1';
export enum Format {
der = 'der',
pem = 'pem',
txt = 'txt',
asn1 = 'asn1',
x509 = 'x509',
fingerprint = 'fingerprint'
};
function myASN(pem: string): asn1 {
const der = forge.pki.pemToDer(pem);
const asn1 = forge.asn1;
// @ts-ignore
const crt = asn1.fromDer(der.data.toString('binary')).value[0].value;
const serial = crt[0];
const hasSerial =
serial.tagClass === asn1.Class.CONTEXT_SPECIFIC &&
serial.type === 0 &&
serial.constructed;
const slicedCrt = crt.slice(hasSerial);
return {
serial: slicedCrt[0],
issuer: slicedCrt[2],
valid: slicedCrt[3],
subject: slicedCrt[4],
};
}
function txtFormat(pem: string) {
const crt = myASN(pem);
const subject = crt.subject.value
// @ts-ignore
.map((rdn) => rdn.value[0].value[1].value)
.join('/');
const valid = crt.valid.value
// @ts-ignore
.map((date) => date.value)
.join(' - ');
return [
`Subject\t${subject}`,
`Valid\t${valid}`,
String(pem),
].join('\n');
}
export type ConvertResult = string | forge.util.ByteStringBuffer | asn1 | forge.pki.Certificate;
export function convert(pem: string, format: Format.pem | Format.txt | Format.fingerprint): string
export function convert(pem: string, format: Format.der): forge.util.ByteStringBuffer
export function convert(pem: string, format: Format.asn1): asn1
export function convert(pem: string, format: Format.x509): forge.pki.Certificate
export function convert(pem: string, format: Format): string | forge.util.ByteStringBuffer | asn1 | forge.pki.Certificate
export function convert(pem: string, format: Format): ConvertResult {
switch (format) {
case Format.der:
return forge.pki.pemToDer(pem);
case Format.pem:
return pem;
case Format.txt:
return txtFormat(pem);
case Format.asn1:
return myASN(pem);
case Format.fingerprint:
const md = forge.md.sha1.create();
const der = convert(pem, Format.der);
md.update(der.getBytes());
return md.digest().toHex();
case Format.x509:
return forge.pki.certificateFromPem(pem);
default:
throw new Error(`unknown format ${format}`);
}
};
import { globalAgent } from 'https';
import { rootCertificates } from 'tls';
import { spawnSync } from 'child_process';
import * as forge from 'node-forge';
import { Format, convert } from './formatter';
import { asn1 } from './asn1';
const isMac = process.platform === 'darwin';
type GetParams = {
/**
* The keychain to search.
* Defaults to "all".
*/
keychain?: 'all' | 'current' | 'SystemRootCertificates',
/**
* remove duplicated certificates
* Defaults to true
*/
unique?: Boolean,
/**
* Exclude node.js bundled root certificates.
* Defaults to true.
*/
excludeBundled?: Boolean,
/**
* The format to retrieve them
* Defaults to "pem".
*/
format?: Format
};
const getParamsDefaults: GetParams = {
keychain: 'all',
unique: true,
excludeBundled: true,
format: Format.pem
};
export function get(params?: GetParams & { format: Format.pem | Format.txt | Format.fingerprint }): string[]
export function get(params?: GetParams & { format: Format.der }): forge.util.ByteStringBuffer[]
export function get(params?: GetParams & { format: Format.asn1 }): asn1[]
export function get(params?: GetParams & { format: Format.x509 }): forge.pki.Certificate[]
export function get(): string[]
export function get(params: GetParams = getParamsDefaults): string[] | forge.util.ByteStringBuffer[] | asn1[] | forge.pki.Certificate[] {
if (!isMac) { return []; }
params = { ...getParamsDefaults, ...params };
const splitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g;
const args = ['find-certificate', '-a', '-p'];
let result: string[] = [];
if (params.keychain === 'all' || params.keychain === 'SystemRootCertificates') {
const systemRootCertsPath =
'/System/Library/Keychains/SystemRootCertificates.keychain';
const root = spawnSync('/usr/bin/security', args.concat(systemRootCertsPath))
.stdout.toString()
.split(splitPattern)
.map(c => c.trim());
result = [...result, ...root];
}
if (params.keychain === 'all' || params.keychain === 'SystemRootCertificates') {
const trusted = spawnSync('/usr/bin/security', args)
.stdout.toString()
.split(splitPattern)
.map(c => c.trim());
result = [...result, ...trusted];
}
if (params.unique || params.excludeBundled) {
const fingerprints = result.map(c => convert(c, Format.fingerprint));
const nodeFingerprints = params.excludeBundled ?
rootCertificates.map(c => convert(c, Format.fingerprint)) :
[];
result = result.filter((pem, index) => {
const fingerprint = fingerprints[index];
if (params.unique && index !== fingerprints.indexOf(fingerprint)) {
return false;
}
if (params.excludeBundled && nodeFingerprints.includes(fingerprint)) {
return false;
}
return true;
});
}
return result.map(c => convert(c, params.format)) as
string[] | forge.util.ByteStringBuffer[] | asn1[] | forge.pki.Certificate[];
};
const originalCA = globalAgent.options.ca;
type AddToGAType = Exclude<GetParams, 'format'>;
export const addToGlobalAgent = (params: AddToGAType = getParamsDefaults) => {
if (!isMac) { return; }
let cas: (string | Buffer)[];
if (!Array.isArray(originalCA)) {
cas = typeof originalCA !== 'undefined' ? [originalCA] : [];
} else {
cas = Array.from(originalCA);
}
get({ ...getParamsDefaults, ...params, format: Format.pem })
.forEach((cert) => cas.push(cert));
globalAgent.options.ca = cas;
};
export { Format, convert };
import { rootCertificates } from 'tls';
import * as assert from 'assert';
import { globalAgent } from 'https';
import * as macca from '../src';
describe('macca', () => {
it('should properly exclude node.js certs', () => {
const excludingNodejs = macca.get({ excludeBundled: true, format: macca.Format.fingerprint });
const nodejs = rootCertificates.map(c => macca.convert(c, macca.Format.fingerprint));
assert.ok(!excludingNodejs.find(c => nodejs.includes(c)));
});
it('should include some of the node.js certs when excludeBundled is false', () => {
const includingNodejs = macca.get({ excludeBundled: false, format: macca.Format.fingerprint });
const nodejs = rootCertificates.map(c => macca.convert(c, macca.Format.fingerprint));
assert.ok(includingNodejs.find(c => nodejs.includes(c)));
});
it('should install in the global agent', () => {
const certs = macca.get();
macca.addToGlobalAgent();
assert.ok(certs.every(c => globalAgent.options.ca?.includes(c)));
});
});
{
"compilerOptions": {
"outDir": "./dist",
"allowJs": true,
"target": "es5",
"declaration": true,
"esModuleInterop": false
},
"include": [
"./src/**/*"
]
}
+1
-1

@@ -5,3 +5,3 @@ {

"node": true,
"mocha": true
"jest": true
},

@@ -8,0 +8,0 @@ "extends": "eslint:recommended",

{
"name": "mac-ca",
"version": "1.1.0",
"version": "2.0.0",
"description": "Get Mac OS Root certificates",
"main": "index.js",
"main": "dist/index.js",
"repository": {

@@ -11,4 +11,5 @@ "type": "git",

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint ."
"test": "mocha --reporter spec --require ts-node/register test/**",
"lint": "eslint .",
"prepare": "tsc"
},

@@ -21,4 +22,9 @@ "author": "José F. Romaniello <jfromaniello@gmail.com> (https://joseoncode.com)",

"devDependencies": {
"eslint": "^8.22.0"
"@types/mocha": "^10.0.1",
"@types/node-forge": "^1.3.1",
"eslint": "^8.22.0",
"mocha": "^10.2.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
}
}
+14
-28

@@ -26,11 +26,11 @@ This module is similar to [win-ca][] but for Mac OS. In fact I have copied most of its documentation and I have tried to maintain api compatibility.

Just say `npm install --save mac-ca` and then call `require('mac-ca')`.
Just say `npm install --save mac-ca` and then call `require('mac-ca').addToGlobalAgent();`.
It is safe to use it under other OSes (not MacOS).
If called in other operative systems the method will do nothing.
## API
After `require('mac-ca')` MacOs' Root CAs are found, deduplicated and installed to `https.globalAgent.options.ca` so they are automatically used for all requests with Node.js' https module.
After `require('mac-ca').addToGlobalAgent()` MacOs' Root CAs are found, deduplicated and installed to `https.globalAgent.options.ca` so they are automatically used for all requests with Node.js' https module.
For use in other places, these certificates are also available via `.all()` method (in [node-forge][]'s format).
For use in other places, these certificates are also available via `.get()` method (in [node-forge][]'s format).

@@ -41,19 +41,13 @@ ```js

for (let crt of ca.all())
for (let crt of ca.get())
console.log(forge.pki.certificateToPem(crt))
```
Unfortunately, `node-forge` at the time of writing is unable to
parse non-RSA certificates
(namely, ECC certificates becoming more popular).
If your *Trusted Root Certification Authorities* store
contains modern certificates,
`.all()` method will throw exception.
To fix this, one can pass `format` parameter to `.all` method:
You can also specify the format as follows:
```js
let ca = require('mac-ca')
ca.get({ format: ca.Format.der })
```
for (let crt of ca.all(ca.der2.pem))
console.log(crt)
```
Available values for `format` are:

@@ -69,15 +63,7 @@

One can enumerate Root CAs himself using `.each()` method:
Other options for `get`:
- `keychain` (defaults to `all`): it could be `all`, `current` or `SystemRootCertificates`.
- `unique` (defaults to `true`): exclude duplicated certificates found.
- `excludeBundled` (defaults to `true`): exclude certificates found in node.js's `tls.rootCertificates`.
```js
let ca = require('mac-ca')
ca.each(crt=>
console.log(forge.pki.certificateToPem(crt)))
```
But this list may contain duplicates.
Asynchronous enumeration is not supported by this module yet.
## Credits

@@ -84,0 +70,0 @@

const https = require('https');
const formatter = require('./lib/formatter');
if (process.platform !== 'darwin') {
module.exports.all = () => [];
module.exports.each = () => {};
}
else {
const child_process = require('child_process');
const splitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g;
const systemRootCertsPath = '/System/Library/Keychains/SystemRootCertificates.keychain';
const args = [ 'find-certificate', '-a', '-p' ];
const allTrusted = child_process.spawnSync('/usr/bin/security', args)
.stdout.toString().split(splitPattern);
const allRoot = child_process.spawnSync('/usr/bin/security', args.concat(systemRootCertsPath))
.stdout.toString().split(splitPattern);
https.globalAgent.options.ca = https.globalAgent.options.ca || [];
const ca = https.globalAgent.options.ca;
function duplicated(cert, index, arr) {
return arr.indexOf(cert) === index;
}
const all = allTrusted.concat(allRoot);
all.filter(duplicated).forEach(cert => ca.push(cert));
module.exports.der2 = formatter.validFormats;
module.exports.all = function(format){
return all
.map(formatter.transform(format))
.filter(c => c);
};
module.exports.each = function(format, callback) {
if (typeof format === 'function') {
callback = format;
format = undefined;
}
return all
.map(formatter.transform(format))
.filter(c => c)
.forEach(callback);
};
}
const forge = require('node-forge');
const packageJson = require('../package.json');
var formats = (module.exports.validFormats = {
der: 0,
pem: 1,
txt: 2,
asn1: 3,
});
function myASN(pem) {
const der = forge.pki.pemToDer(pem);
const asn1 = forge.asn1;
const crt = asn1.fromDer(der.data.toString('binary')).value[0].value;
const serial = crt[0];
const hasSerial =
serial.tagClass === asn1.Class.CONTEXT_SPECIFIC &&
serial.type === 0 &&
serial.constructed;
const slicedCrt = crt.slice(hasSerial);
return {
serial: slicedCrt[0],
issuer: slicedCrt[2],
valid: slicedCrt[3],
subject: slicedCrt[4],
};
}
function txtFormat(pem) {
const crt = myASN(pem);
const d = new Date();
const subject = crt.subject.value
.map((rdn) => rdn.value[0].value[1].value)
.join('/');
const valid = crt.valid.value
.map((date) => date.value)
.join(' - ');
const savedTime = d
.toTimeString()
.replace(/\s*\(.*\)\s*/, '');
return [
`Subject\t${subject}`,
`Valid\t${valid}`,
`Saved\t${d.toLocaleDateString()} ${savedTime} by ${packageJson.name}@${packageJson.version}`,
String(pem),
].join('\n');
}
module.exports.transform = function (format) {
return function (pem) {
try {
switch (format) {
case formats.der:
return forge.pki.pemToDer(pem);
case formats.pem:
return pem;
case formats.txt:
return txtFormat(pem);
case formats.asn1:
return myASN(pem);
default:
return forge.pki.certificateFromPem(pem);
}
} catch (er) {
return;
}
};
};