openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
220 lines (169 loc) • 7.21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getTrustedClientCerts = getTrustedClientCerts;
exports.getServerOptions = getServerOptions;
exports.koaMiddleware = koaMiddleware;
var _winston = _interopRequireDefault(require("winston"));
var _pem = _interopRequireDefault(require("pem"));
var _latest = require("ssl-root-cas/latest");
var _clients = require("../model/clients");
var _keystore = require("../model/keystore");
var utils = _interopRequireWildcard(require("../utils"));
var _config = require("../config");
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_config.config.tlsClientLookup = _config.config.get('tlsClientLookup');
/*
* Fetches the trusted certificates, callsback with an array of certs.
*/
function getTrustedClientCerts(done) {
return _keystore.KeystoreModel.findOne((err, keystore) => {
if (err) {
done(err, null);
}
const certs = _latest.rootCas;
if (keystore.ca != null) {
for (const cert of Array.from(keystore.ca)) {
certs.push(cert.data);
}
}
return done(null, certs);
});
}
/*
* Gets server options object for use with a HTTPS node server
*
* mutualTLS is a boolean, when true mutual TLS authentication is enabled
*/
function getServerOptions(mutualTLS, done) {
return _keystore.KeystoreModel.findOne((err, keystore) => {
let options;
if (err) {
_winston.default.error(`Could not fetch keystore: ${err}`);
return done(err);
}
if (keystore != null) {
options = {
key: keystore.key,
cert: keystore.cert.data
}; // if key has password add it to the options
if (keystore.passphrase) {
options.passphrase = keystore.passphrase;
}
} else {
return done(new Error('Keystore does not exist'));
}
if (mutualTLS) {
return exports.getTrustedClientCerts((err, certs) => {
if (err) {
_winston.default.error(`Could not fetch trusted certificates: ${err}`);
return done(err, null);
}
options.ca = certs;
options.requestCert = true;
options.rejectUnauthorized = false; // we test authority ourselves
return done(null, options);
});
} else {
return done(null, options);
}
});
}
/*
* A promise returning function that lookups up a client via the given cert fingerprint,
* if not found and config.tlsClientLookup.type is 'in-chain' then the function will
* recursively walk up the certificate chain and look for clients with certificates
* higher in the chain.
*/
function clientLookup(fingerprint, subjectCN, issuerCN) {
return new Promise((resolve, reject) => {
_winston.default.debug(`Looking up client linked to cert with fingerprint ${fingerprint} with subject ${subjectCN} and issuer ${issuerCN}`);
_clients.ClientModel.findOne({
certFingerprint: fingerprint
}, (err, result) => {
if (err) {
return reject(err);
}
if (result != null) {
// found a match
return resolve(result);
}
if (subjectCN === issuerCN) {
// top certificate reached
return resolve(null);
}
if (_config.config.tlsClientLookup.type === 'in-chain') {
// walk further up and cert chain and check
return utils.getKeystore((err, keystore) => {
if (err) {
return reject(err);
}
let missedMatches = 0; // find the isser cert
if (keystore.ca == null || keystore.ca.length < 1) {
_winston.default.info(`Issuer cn=${issuerCN} for cn=${subjectCN} not found in keystore.`);
return resolve(null);
} else {
return Array.from(keystore.ca).map(cert => (cert => _pem.default.readCertificateInfo(cert.data, (err, info) => {
if (err) {
return reject(err);
}
if (info.commonName === issuerCN) {
const promise = clientLookup(cert.fingerprint, info.commonName, info.issuer.commonName);
promise.then(resolve);
} else {
missedMatches++;
}
if (missedMatches === keystore.ca.length) {
_winston.default.info(`Issuer cn=${issuerCN} for cn=${subjectCN} not found in keystore.`);
return resolve(null);
}
}))(cert));
}
});
} else {
if (_config.config.tlsClientLookup.type !== 'strict') {
_winston.default.warn('tlsClientLookup.type config option does not contain a known value, defaulting to \'strict\'. Available options are \'strict\' and \'in-chain\'.');
}
return resolve(null);
}
});
});
}
if (process.env.NODE_ENV === 'test') {
exports.clientLookup = clientLookup;
}
/*
* Koa middleware for mutual TLS authentication
*/
async function koaMiddleware(ctx, next) {
if (ctx.authenticated != null) {
await next();
} else if (ctx.req.client.authorized === true) {
const cert = ctx.req.connection.getPeerCertificate(true);
_winston.default.info(`${cert.subject.CN} is authenticated via TLS.`); // lookup client by cert fingerprint and set them as the authenticated user
try {
ctx.authenticated = await clientLookup(cert.fingerprint, cert.subject.CN, cert.issuer.CN);
} catch (err) {
_winston.default.error(`Failed to lookup client: ${err}`);
}
if (ctx.authenticated != null) {
if (ctx.authenticated.clientID != null) {
ctx.header['X-OpenHIM-ClientID'] = ctx.authenticated.clientID;
}
ctx.authenticationType = 'tls';
await next();
} else {
ctx.authenticated = null;
_winston.default.info(`Certificate Authentication Failed: the certificate's fingerprint ${cert.fingerprint} did not match any client's certFingerprint attribute, trying next auth mechanism if any...`);
await next();
}
} else {
ctx.authenticated = null;
_winston.default.info(`Could NOT authenticate via TLS: ${ctx.req.client.authorizationError}, trying next auth mechanism if any...`);
await next();
}
}
//# sourceMappingURL=tlsAuthentication.js.map