| export type asn1 = { | ||
| serial: any; | ||
| issuer: any; | ||
| valid: any; | ||
| subject: any; | ||
| }; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); |
| import * as forge from 'node-forge'; | ||
| import { asn1 } from './asn1'; | ||
| export declare enum Format { | ||
| der = "der", | ||
| pem = "pem", | ||
| txt = "txt", | ||
| asn1 = "asn1", | ||
| x509 = "x509", | ||
| fingerprint = "fingerprint" | ||
| } | ||
| export type ConvertResult = string | forge.util.ByteStringBuffer | asn1 | forge.pki.Certificate; | ||
| export declare function convert(pem: string, format: Format.pem | Format.txt | Format.fingerprint): string; | ||
| export declare function convert(pem: string, format: Format.der): forge.util.ByteStringBuffer; | ||
| export declare function convert(pem: string, format: Format.asn1): asn1; | ||
| export declare function convert(pem: string, format: Format.x509): forge.pki.Certificate; | ||
| export declare function convert(pem: string, format: Format): string | forge.util.ByteStringBuffer | asn1 | forge.pki.Certificate; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.convert = exports.Format = void 0; | ||
| var forge = require("node-forge"); | ||
| var Format; | ||
| (function (Format) { | ||
| Format["der"] = "der"; | ||
| Format["pem"] = "pem"; | ||
| Format["txt"] = "txt"; | ||
| Format["asn1"] = "asn1"; | ||
| Format["x509"] = "x509"; | ||
| Format["fingerprint"] = "fingerprint"; | ||
| })(Format = exports.Format || (exports.Format = {})); | ||
| ; | ||
| function myASN(pem) { | ||
| var der = forge.pki.pemToDer(pem); | ||
| var asn1 = forge.asn1; | ||
| // @ts-ignore | ||
| var crt = asn1.fromDer(der.data.toString('binary')).value[0].value; | ||
| var serial = crt[0]; | ||
| var hasSerial = serial.tagClass === asn1.Class.CONTEXT_SPECIFIC && | ||
| serial.type === 0 && | ||
| serial.constructed; | ||
| var slicedCrt = crt.slice(hasSerial); | ||
| return { | ||
| serial: slicedCrt[0], | ||
| issuer: slicedCrt[2], | ||
| valid: slicedCrt[3], | ||
| subject: slicedCrt[4], | ||
| }; | ||
| } | ||
| function txtFormat(pem) { | ||
| var crt = myASN(pem); | ||
| var subject = crt.subject.value | ||
| // @ts-ignore | ||
| .map(function (rdn) { return rdn.value[0].value[1].value; }) | ||
| .join('/'); | ||
| var valid = crt.valid.value | ||
| // @ts-ignore | ||
| .map(function (date) { return date.value; }) | ||
| .join(' - '); | ||
| return [ | ||
| "Subject\t".concat(subject), | ||
| "Valid\t".concat(valid), | ||
| String(pem), | ||
| ].join('\n'); | ||
| } | ||
| function convert(pem, format) { | ||
| 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: | ||
| var md = forge.md.sha1.create(); | ||
| var 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 ".concat(format)); | ||
| } | ||
| } | ||
| exports.convert = convert; | ||
| ; |
| import * as forge from 'node-forge'; | ||
| import { Format, convert } from './formatter'; | ||
| import { asn1 } from './asn1'; | ||
| 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; | ||
| }; | ||
| export declare function get(params?: GetParams & { | ||
| format: Format.pem | Format.txt | Format.fingerprint; | ||
| }): string[]; | ||
| export declare function get(params?: GetParams & { | ||
| format: Format.der; | ||
| }): forge.util.ByteStringBuffer[]; | ||
| export declare function get(params?: GetParams & { | ||
| format: Format.asn1; | ||
| }): asn1[]; | ||
| export declare function get(params?: GetParams & { | ||
| format: Format.x509; | ||
| }): forge.pki.Certificate[]; | ||
| export declare function get(): string[]; | ||
| type AddToGAType = Exclude<GetParams, 'format'>; | ||
| export declare const addToGlobalAgent: (params?: AddToGAType) => void; | ||
| export { Format, convert }; |
+4
-1
| { | ||
| "name": "mac-ca", | ||
| "version": "2.0.0", | ||
| "version": "2.0.2", | ||
| "description": "Get Mac OS Root certificates", | ||
| "main": "dist/index.js", | ||
| "files": [ | ||
| "dist/**/*" | ||
| ], | ||
| "repository": { | ||
@@ -7,0 +10,0 @@ "type": "git", |
+19
-10
@@ -1,6 +0,4 @@ | ||
| 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. | ||
| # mac-ca | ||
| Get MacOS System Root certificates for [Node.js][]. | ||
| Get MacOS System certificates into [Node.js][]. | ||
@@ -15,4 +13,6 @@ ## Rationale | ||
| This package is intended to fetch Root CAs from MacOS "SystemRootCertificates.keychain" and make them available to [Node.js] application with minimal efforts. | ||
| This is most of the time ok, but there are cases where you might for example want to use TLS in development with self signed certificates for which you have a root certificate in your local store. Eg: [mkcert](https://github.com/FiloSottile/mkcert) | ||
| This package is intended to fetch Root CAs from MacOS "SystemRootCertificates.keychain" and the user's keychain into [Node.js]. | ||
| ### Advantages | ||
@@ -27,11 +27,19 @@ | ||
| Just say `npm install --save mac-ca` and then call `require('mac-ca').addToGlobalAgent();`. | ||
| Install with | ||
| ``` | ||
| npm install --save mac-ca | ||
| ``` | ||
| If called in other operative systems the method will do nothing. | ||
| And then use it as follows: | ||
| ``` | ||
| require('mac-ca').addToGlobalAgent(); | ||
| ``` | ||
| If called in other operative systems the method will just do nothing. | ||
| ## API | ||
| 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. | ||
| 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 and any higher-level library like axios, node-fetch or request. | ||
| For use in other places, these certificates are also available via `.get()` method (in [node-forge][]'s format). | ||
| For use in other places, these certificates are also available via `.get()` method. | ||
@@ -42,4 +50,4 @@ ```js | ||
| for (let crt of ca.get()) | ||
| console.log(forge.pki.certificateToPem(crt)) | ||
| for (let pem of ca.get()) | ||
| console.log(pem) | ||
| ``` | ||
@@ -76,2 +84,3 @@ | ||
| [win-ca]: https://github.com/ukoloff/win-ca | ||
| [mkcert]: https://github.com/FiloSottile/mkcert | ||
| [node-forge]: https://github.com/digitalbazaar/forge | ||
@@ -78,0 +87,0 @@ [OpenSSL::Win::Root]: https://github.com/ukoloff/openssl-win-root |
| { | ||
| "env": { | ||
| "es6": true, | ||
| "node": true, | ||
| "jest": true | ||
| }, | ||
| "extends": "eslint:recommended", | ||
| "rules": { | ||
| "no-useless-escape": 1, | ||
| "no-console": 0, | ||
| "indent": [ | ||
| "error", | ||
| 2, | ||
| { | ||
| "SwitchCase": 1 | ||
| } | ||
| ], | ||
| "linebreak-style": [ | ||
| "error", | ||
| "unix" | ||
| ], | ||
| "quotes": [ | ||
| "error", | ||
| "single" | ||
| ], | ||
| "semi": [ | ||
| "error", | ||
| "always" | ||
| ] | ||
| } | ||
| } |
| { | ||
| "editor.formatOnSave": true, | ||
| "eslint.format.enable": true, | ||
| "[javascript]": { | ||
| "editor.defaultFormatter": "dbaeumer.vscode-eslint" | ||
| } | ||
| } |
| 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/**/*" | ||
| ] | ||
| } |
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
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
89
11.25%4
-55.56%13586
-19.55%9
-18.18%233
-31.87%