UNPKG

openhim-core

Version:

The OpenHIM core application that provides logging and routing of http requests

228 lines (197 loc) 6.9 kB
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