@small-tech/syswide-cas
Version:
Fork of syswide-cas by a now-defunct company called Capriza. Enable node to use system wide certificate authorities in conjunction with the bundled root CAs.
101 lines (89 loc) • 2.79 kB
JavaScript
import fs from 'node:fs'
import path from 'node:path'
import tls from 'node:tls'
/** @type {string[]} */
const rootCAs = []
// Monkeypatch tls.createSecureContext method to inject custom root CAs when invoked.
const originalCreateSecureContext = tls.createSecureContext
tls.createSecureContext = function (options) {
var secureContext = originalCreateSecureContext.apply(null, arguments)
if (!options?.ca && rootCAs.length > 0) {
rootCAs.forEach(function (ca) {
// Add our own root CAs to created context.
secureContext.context.addCACert(ca)
})
}
return secureContext
}
export default class SyswideCas {
/**
Adds custom Certificate Authorities from the specified directories or files.
@param {string|string[]} dirs - A single directory/file path or an array of paths, or a comma-separated string of paths.
*/
static addCAs (dirs) {
if (!dirs) {
return
}
if (typeof dirs === 'string') {
dirs = dirs.split(',').map(function (dir) {
return dir.trim()
})
}
var files, stat, file, i, j
for (i = 0; i < dirs.length; ++i) {
try {
stat = fs.statSync(dirs[i])
if (stat.isDirectory()) {
files = fs.readdirSync(dirs[i])
for (j = 0; j < files.length; ++j) {
file = path.resolve(dirs[i], files[j])
try {
stat = fs.statSync(file)
if (stat.isFile()) {
this.addDefaultCA(file)
}
} catch (e) {
if (e.code !== 'ENOENT') {
console.error(`Error reading ${file}: ${e.message}`)
}
}
}
} else {
this.addDefaultCA(dirs[i])
}
} catch (e) {
if (e.code !== 'ENOENT') {
console.log(`Error reading ${dirs[i]}: ${e.message}`)
}
}
}
}
/**
Adds certificate authority to list of root certificate authorities.
@param {string} file
*/
static addDefaultCA (file) {
try {
var content = fs.readFileSync(file, {encoding: "ascii"}).trim()
content = content.replace(/\r\n/g, "\n") // Handles certificates that have been created in Windows
var regex = /-----BEGIN CERTIFICATE-----\n[\s\S]+?\n-----END CERTIFICATE-----/g
var results = content.match(regex)
if (!results) {
throw new Error('Could not parse certificate.')
}
results.forEach(function (match) {
var cert = match.trim()
rootCAs.push(cert)
})
} catch (e) {
if (e.code !== 'ENOENT') {
console.log(`Error reading file ${file}: ${e.message}`)
}
}
}
}
//
// Module load-time side-effects.
//
const defaultCALocations = ['/etc/ssl/ca-node.pem']
SyswideCas.addCAs(defaultCALocations)