| { | ||
| "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}`); | ||
| } | ||
| }; |
+121
| 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 }; |
+24
| 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", |
+11
-5
| { | ||
| "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 @@ |
-51
| 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; | ||
| } | ||
| }; | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
16888
95.17%11
83.33%342
155.22%1
-50%6
500%80
-14.89%9
350%