UNPKG

cert-store

Version:

🔐 Install, check and delete trusted root certificates.

269 lines (224 loc) 7.2 kB
import forge from 'node-forge' import path from 'path' import cp from 'child_process' import os from 'os' import util from 'util' import _fs from '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 } export default PlatformSpecificCertStore