openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
229 lines (192 loc) • 7.44 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getTrustedClientCerts = getTrustedClientCerts;
exports.getServerOptions = getServerOptions;
exports.koaMiddleware = koaMiddleware;
var _winston = require('winston');
var _winston2 = _interopRequireDefault(_winston);
var _pem = require('pem');
var _pem2 = _interopRequireDefault(_pem);
var _latest = require('ssl-root-cas/latest');
var _statsdClient = require('statsd-client');
var _statsdClient2 = _interopRequireDefault(_statsdClient);
var _os = require('os');
var _os2 = _interopRequireDefault(_os);
var _clients = require('../model/clients');
var _keystore = require('../model/keystore');
var _utils = require('../utils');
var utils = _interopRequireWildcard(_utils);
var _config = require('../config');
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_config.config.tlsClientLookup = _config.config.get('tlsClientLookup');
const statsdServer = _config.config.get('statsd');
const application = _config.config.get('application');
const domain = `${_os2.default.hostname()}.${application.name}.appMetrics`;
const sdc = new _statsdClient2.default(statsdServer);
/*
* 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) {
_winston2.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) {
_winston2.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) => {
_winston2.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) {
_winston2.default.info(`Issuer cn=${issuerCN} for cn=${subjectCN} not found in keystore.`);
return resolve(null);
} else {
return Array.from(keystore.ca).map(cert => (cert => _pem2.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) {
_winston2.default.info(`Issuer cn=${issuerCN} for cn=${subjectCN} not found in keystore.`);
return resolve(null);
}
}))(cert));
}
});
} else {
if (_config.config.tlsClientLookup.type !== 'strict') {
_winston2.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) {
let startTime;
if (statsdServer.enabled) {
startTime = new Date();
}
if (ctx.authenticated != null) {
await next();
} else if (ctx.req.client.authorized === true) {
const cert = ctx.req.connection.getPeerCertificate(true);
_winston2.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) {
_winston2.default.error(`Failed to lookup client: ${err}`);
}
if (ctx.authenticated != null) {
if (ctx.authenticated.clientID != null) {
ctx.header['X-OpenHIM-ClientID'] = ctx.authenticated.clientID;
}
if (statsdServer.enabled) {
sdc.timing(`${domain}.tlsAuthenticationMiddleware`, startTime);
}
ctx.authenticationType = 'tls';
await next();
} else {
ctx.authenticated = null;
_winston2.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...`);
if (statsdServer.enabled) {
sdc.timing(`${domain}.tlsAuthenticationMiddleware`, startTime);
}
await next();
}
} else {
ctx.authenticated = null;
_winston2.default.info(`Could NOT authenticate via TLS: ${ctx.req.client.authorizationError}, trying next auth mechanism if any...`);
if (statsdServer.enabled) {
sdc.timing(`${domain}.tlsAuthenticationMiddleware`, startTime);
}
await next();
}
}
//# sourceMappingURL=tlsAuthentication.js.map