UNPKG

openhim-core

Version:

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

175 lines (155 loc) 5.7 kB
import logger from 'winston' import pem from 'pem' import { rootCas as rootCAs } from 'ssl-root-cas/latest' import { ClientModel } from '../model/clients' import { KeystoreModel } from '../model/keystore' import * as utils from '../utils' import { config } from '../config' config.tlsClientLookup = config.get('tlsClientLookup') /* * Fetches the trusted certificates, callsback with an array of certs. */ export function getTrustedClientCerts (done) { return KeystoreModel.findOne((err, keystore) => { if (err) { done(err, null) } const certs = 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 */ export function getServerOptions (mutualTLS, done) { return KeystoreModel.findOne((err, keystore) => { let 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 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) { logger.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) => { logger.debug(`Looking up client linked to cert with fingerprint ${fingerprint} with subject ${subjectCN} and issuer ${issuerCN}`) 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.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)) { logger.info(`Issuer cn=${issuerCN} for cn=${subjectCN} not found in keystore.`) return resolve(null) } else { return Array.from(keystore.ca).map((cert) => (cert => pem.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) { logger.info(`Issuer cn=${issuerCN} for cn=${subjectCN} not found in keystore.`) return resolve(null) } }))(cert)) } }) } 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 resolve(null) } }) }) } if (process.env.NODE_ENV === 'test') { exports.clientLookup = clientLookup } /* * Koa middleware for mutual TLS authentication */ export 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) logger.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) { logger.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 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...`) await next() } } else { ctx.authenticated = null logger.info(`Could NOT authenticate via TLS: ${ctx.req.client.authorizationError}, trying next auth mechanism if any...`) await next() } }