UNPKG

cert-store

Version:

🔐 Install, check and delete trusted root certificates.

279 lines (231 loc) 8.35 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('node-forge'), require('path'), require('child_process'), require('os'), require('util'), require('fs')) : typeof define === 'function' && define.amd ? define(['node-forge', 'path', 'child_process', 'os', 'util', 'fs'], factory) : (global['cert-store'] = factory(global['node-forge'],global.path,global.child_process,global.os,global.util,global.fs)); }(this, (function (forge,path,cp,os,util,_fs) { 'use strict'; forge = forge && forge.hasOwnProperty('default') ? forge['default'] : forge; path = path && path.hasOwnProperty('default') ? path['default'] : path; cp = cp && cp.hasOwnProperty('default') ? cp['default'] : cp; os = os && os.hasOwnProperty('default') ? os['default'] : os; util = util && util.hasOwnProperty('default') ? util['default'] : util; _fs = _fs && _fs.hasOwnProperty('default') ? _fs['default'] : _fs; var exec = util.promisify(cp.exec); // not using fs.promise because we're supporting Node 8. var fs = {}; for (let [name, method] of Object.entries(_fs)) { if (typeof method === 'function') fs[name] = util.promisify(method); } async function ensureDirectory(directory) { try { await fs.stat(directory); } catch(err) { await fs.mkdir(directory); } } // accepts PEM string, removes the --- header/footer, and calculates sha1 hash/thumbprint/fingerprint function pemToHash(pem) { return pem.toString() .replace('-----BEGIN CERTIFICATE-----', '') .replace('-----END CERTIFICATE-----', '') .replace(/\r+/g, '') .replace(/\n+/g, '') .trim() } function isNodeForgeCert(arg) { if (typeof arg !== 'object') return false return arg.version !== undefined && arg.serialNumber !== undefined && arg.signature !== undefined && arg.publicKey !== undefined } const LINUX_CERT_DIR = '/usr/share/ca-certificates/extra/'; // Not tested. I don't have a mac. help needed. var MAC_DIR; if (os.platform() === 'darwin') { let [major, minor] = os.release().split('.').map(Number); if (major >= 10 && minor >= 14) MAC_DIR = '/Library/Keychains/System.keychain'; else MAC_DIR = '/System/Library/Keychains/SystemRootCertificates.keychain'; } class CertStruct { constructor(arg) { if (Buffer.isBuffer(arg)) arg = arg.toString(); if (typeof arg === 'string') { if (arg.includes('-----BEGIN CERTIFICATE-----')) this.pem = arg; else this.path = arg; } else if (typeof arg === 'object') { this.path = arg.path || arg.path; this.serialNumber = arg.serialNumber; if (isNodeForgeCert(arg)) this.pem = forge.pki.certificateToPem(arg); else this.pem = arg.pem || arg.cert || arg.data; } } async ensureCertReadFromFs() { if (this.pem) return if (!this.path) return this.pem = (await fs.readFile(this.path)).toString(); } get name() { if (this._name) return this._name if (this.path) { this._name = path.basename(this.path); if (this._name.endsWith('.crt') && this._name.endsWith('.cer')) this._name = this._name.slice(0, -4); else if (this._name.endsWith('.pem')) this._name = this._name.slice(0, -5); } else if (this.certificate) { let attributes = certificate.subject.attributes; if (attributes) { var obj = attributes.find(obj => obj.shortName === 'CN') || attributes.find(obj => obj.shortName === 'O'); if (obj) this._name = obj.value.toLowerCase().replace(/\W+/g, '-'); } } return this._name } get certificate() { if (this._certificate) return this._certificate if (this.pem) return this._certificate = forge.pki.certificateFromPem(this.pem) } set certificate(object) { this._certificate = object; } get serialNumber() { if (this._serialNumber) return this._serialNumber if (this.certificate) return this._serialNumber = this.certificate.serialNumber } set serialNumber(string) { this._serialNumber = string; } } // https://manuals.gfi.com/en/kerio/connect/content/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html class CertStore { // Only works on linux (ubuntu, debian). // Finds certificate in /usr/share/ca-certificates/extra/ by its serial number. // Returns path to the certificate if found, otherwise undefined. static async _findLinuxCert(arg) { await arg.ensureCertReadFromFs(); let filenames = await fs.readdir(LINUX_CERT_DIR); for (let fileName of filenames) { let filepath = LINUX_CERT_DIR + fileName; let pem = await fs.readFile(filepath); let certificate = forge.pki.certificateFromPem(pem); if (arg.serialNumber === certificate.serialNumber) return filepath } } static async createTempFileIfNeeded(arg) { if (arg.path) return arg.tempPath = `temp-${Date.now()}-${Math.random()}.crt`; await fs.writeFile(arg.tempPath, arg.pem); } static async deleteTempFileIfNeeded(arg) { if (!arg.tempPath) return await fs.unlink(arg.tempPath); } // SUGARY METHODS static async install(arg) { arg = new CertStruct(arg); if (!arg.path && !arg.pem) throw new Error('path to or contents of the certificate has to be defined.') try { return await this._install(arg) } catch(err) { throw new Error(`Couldn't install certificate.\n${err.stack}`) } finally { this.deleteTempFileIfNeeded(arg); } } static async delete(arg) { arg = new CertStruct(arg); try { return await this._delete(arg) } catch(err) { throw new Error(`Couldn't delete certificate.\n${err.stack}`) } } static async isInstalled(arg) { arg = new CertStruct(arg); try { return await this._isInstalled(arg) } catch(err) { throw new Error(`Couldn't find if certificate is installed.\n${err.stack}`) } } } class WindowsCertStore extends CertStore { static async _install(arg) { await this.createTempFileIfNeeded(arg); await exec(`certutil -addstore -user -f root "${arg.path || arg.tempPath}"`); } static async _delete(arg) { await arg.ensureCertReadFromFs(); await exec(`certutil -delstore -user root ${arg.serialNumber}`); } static async _isInstalled(arg) { await arg.ensureCertReadFromFs(); try { await exec(`certutil -verifystore -user root ${arg.serialNumber}`); return true } catch(err) { // certutil always fails if the serial number is not found. return false } } } class LinuxCertStore extends CertStore { static async _install(arg) { await ensureDirectory(LINUX_CERT_DIR); var targetPath = LINUX_CERT_DIR + arg.name + '.crt'; if (!arg.pem && arg.path) arg.pem = await fs.readFile(arg.path); await fs.writeFile(targetPath, arg.pem); await exec('update-ca-certificates'); } static async _delete(arg) { var targetPath = await this._findLinuxCert(arg); if (targetPath) await fs.unlink(targetPath); } static async _isInstalled(arg) { return !!(await this._findLinuxCert(arg)) } } // Not tested. I don't have a mac. help needed. class MacCertStore extends CertStore { static async _install(arg) { await this.createTempFileIfNeeded(arg); await exec(`security add-trusted-cert -d -r trustRoot -k "${MAC_DIR}" "${arg.path}"`); } static async _delete(arg) { await arg.ensureCertReadFromFs(); var fingerPrint = pemToHash(arg.pem); await exec(`security delete-certificate -Z ${fingerPrint} "${MAC_DIR}"`); } static async _isInstalled(arg) { // TODO throw new Error('isInstalled() not yet implemented on this platform') } } var PlatformSpecificCertStore; switch (process.platform) { case 'win32': PlatformSpecificCertStore = WindowsCertStore; break case 'darwin': PlatformSpecificCertStore = MacCertStore; break default: PlatformSpecificCertStore = LinuxCertStore; break } var PlatformSpecificCertStore$1 = PlatformSpecificCertStore; return PlatformSpecificCertStore$1; })));