openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
228 lines (197 loc) • 6.9 kB
JavaScript
var Client, Keystore, Q, SDC, application, clientLookup, config, domain, fs, logger, os, pem, rootCAs, sdc, statsdServer, utils;
fs = require("fs");
Q = require("q");
Client = require("../model/clients").Client;
Keystore = require("../model/keystore").Keystore;
logger = require("winston");
utils = require('../utils');
pem = require('pem');
rootCAs = require('ssl-root-cas/latest').rootCas;
config = require('../config/config');
config.tlsClientLookup = config.get('tlsClientLookup');
statsdServer = config.get('statsd');
application = config.get('application');
SDC = require('statsd-client');
os = require('os');
domain = (os.hostname()) + "." + application.name + ".appMetrics";
sdc = new SDC(statsdServer);
/*
* Fetches the trusted certificates, callsback with an array of certs.
*/
exports.getTrustedClientCerts = function(done) {
return Keystore.findOne(function(err, keystore) {
var cert, certs, i, len, ref;
if (err) {
done(err, null);
}
certs = rootCAs;
if (keystore.ca != null) {
ref = keystore.ca;
for (i = 0, len = ref.length; i < len; i++) {
cert = ref[i];
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
*/
exports.getServerOptions = function(mutualTLS, done) {
return Keystore.findOne(function(err, keystore) {
var options;
if (err) {
logger.error("Could not fetch keystore: " + err);
return done(err);
}
if (keystore != null) {
options = {
key: keystore.key,
cert: keystore.cert.data
};
if (keystore.passphrase) {
options.passphrase = keystore.passphrase;
}
} else {
return done(new Error('Keystore does not exist'));
}
if (mutualTLS) {
return exports.getTrustedClientCerts(function(err, certs) {
if (err) {
logger.error("Could not fetch trusted certificates: " + err);
return done(err, null);
}
options.ca = certs;
options.requestCert = true;
options.rejectUnauthorized = false;
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.
*/
clientLookup = function(fingerprint, subjectCN, issuerCN) {
var deferred;
logger.debug("Looking up client linked to cert with fingerprint " + fingerprint + " with subject " + subjectCN + " and issuer " + issuerCN);
deferred = Q.defer();
Client.findOne({
certFingerprint: fingerprint
}, function(err, result) {
if (err) {
deferred.reject(err);
}
if (result != null) {
return deferred.resolve(result);
}
if (subjectCN === issuerCN) {
return deferred.resolve(null);
}
if (config.tlsClientLookup.type === 'in-chain') {
return utils.getKeystore(function(err, keystore) {
var cert, i, len, missedMatches, ref, results;
if (err) {
deferred.reject(err);
}
missedMatches = 0;
if ((keystore.ca == null) || keystore.ca.length < 1) {
logger.info("Issuer cn=" + issuerCN + " for cn=" + subjectCN + " not found in keystore.");
return deferred.resolve(null);
} else {
ref = keystore.ca;
results = [];
for (i = 0, len = ref.length; i < len; i++) {
cert = ref[i];
results.push((function(cert) {
return pem.readCertificateInfo(cert.data, function(err, info) {
var promise;
if (err) {
return deferred.reject(err);
}
if (info.commonName === issuerCN) {
promise = clientLookup(cert.fingerprint, info.commonName, info.issuer.commonName);
promise.then(function(result) {
return deferred.resolve(result);
});
} else {
missedMatches++;
}
if (missedMatches === keystore.ca.length) {
logger.info("Issuer cn=" + issuerCN + " for cn=" + subjectCN + " not found in keystore.");
return deferred.resolve(null);
}
});
})(cert));
}
return results;
}
});
} else {
if (config.tlsClientLookup.type !== 'strict') {
logger.warn("tlsClientLookup.type config option does not contain a known value, defaulting to 'strict'. Available options are 'strict' and 'in-chain'.");
}
return deferred.resolve(null);
}
});
return deferred.promise;
};
if (process.env.NODE_ENV === "test") {
exports.clientLookup = clientLookup;
}
/*
* Koa middleware for mutual TLS authentication
*/
exports.koaMiddleware = function*(next) {
var cert, err, error, startTime;
if (statsdServer.enabled) {
startTime = new Date();
}
if (this.authenticated != null) {
return (yield next);
} else {
if (this.req.client.authorized === true) {
cert = this.req.connection.getPeerCertificate(true);
logger.info(cert.subject.CN + " is authenticated via TLS.");
try {
this.authenticated = (yield clientLookup(cert.fingerprint, cert.subject.CN, cert.issuer.CN));
} catch (error) {
err = error;
logger.error("Failed to lookup client: " + err);
}
if (this.authenticated != null) {
if (this.authenticated.clientID != null) {
this.header['X-OpenHIM-ClientID'] = this.authenticated.clientID;
}
if (statsdServer.enabled) {
sdc.timing(domain + ".tlsAuthenticationMiddleware", startTime);
}
this.authenticationType = 'tls';
return (yield next);
} else {
this.authenticated = null;
logger.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);
}
return (yield next);
}
} else {
this.authenticated = null;
logger.info("Could NOT authenticate via TLS: " + this.req.client.authorizationError + ", trying next auth mechanism if any...");
if (statsdServer.enabled) {
sdc.timing(domain + ".tlsAuthenticationMiddleware", startTime);
}
return (yield next);
}
}
};
//# sourceMappingURL=tlsAuthentication.js.map