# All files / src/middleware tlsAuthentication.coffee

29.58% Statements 21/71
0% Branches 0/8
0% Functions 0/12
30% Lines 21/70

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    1591x
    1x
    1x
    1x
    1x
    1x
    1x
    1x

    1x
    1x
    1x
    1x
    1x
    1x

    1x
    1x

    1x

    1x

    1x

    1x

    1x

     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 = (done) ->
      Keystore.findOne (err, keystore) ->
        done err, null if err
        certs = rootCAs
        if keystore.ca?
          for cert in 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
    ###
    exports.getServerOptions = (mutualTLS, done) ->
      Keystore.findOne (err, keystore) ->
        if err
          logger.error "Could not fetch keystore: #{err}"
          return done err

        if keystore?
          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
          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
            done null, options
        else
          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 = (fingerprint, subjectCN, issuerCN) ->
      logger.debug "Looking up client linked to cert with fingerprint #{fingerprint} with subject #{subjectCN} and issuer #{issuerCN}"
      deferred = Q.defer()

      Client.findOne certFingerprint: fingerprint, (err, result) ->
        deferred.reject err if err

        if result?
          # found a match
          return deferred.resolve result

        if subjectCN is issuerCN
          # top certificate reached
          return deferred.resolve null

        if config.tlsClientLookup.type is 'in-chain'
          # walk further up and cert chain and check
          utils.getKeystore (err, keystore) ->
            deferred.reject err if err
            missedMatches = 0
            # find the isser cert
            if not keystore.ca? || keystore.ca.length < 1
              logger.info "Issuer cn=#{issuerCN} for cn=#{subjectCN} not found in keystore."
              deferred.resolve null
            else
              for cert in keystore.ca
                do (cert) ->
                  pem.readCertificateInfo cert.data, (err, info) ->
                    if err
                      return deferred.reject err

                    if info.commonName is issuerCN
                      promise = clientLookup cert.fingerprint, info.commonName, info.issuer.commonName
                      promise.then (result) -> deferred.resolve result
                    else
                      missedMatches++

                    if missedMatches is keystore.ca.length
                      logger.info "Issuer cn=#{issuerCN} for cn=#{subjectCN} not found in keystore."
                      deferred.resolve null
        else
          if config.tlsClientLookup.type isnt 'strict'
            logger.warn "tlsClientLookup.type config option does not contain a known value, defaulting to 'strict'. Available options are 'strict' and 'in-chain'."
          deferred.resolve null

      return deferred.promise

    if process.env.NODE_ENV == "test"
      exports.clientLookup = clientLookup

    ###
    # Koa middleware for mutual TLS authentication
    ###
    exports.koaMiddleware = (next) ->
      startTime = new Date() if statsdServer.enabled
      if this.authenticated?
        yield next
      else
        if this.req.client.authorized is true
          cert = this.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
            this.authenticated = yield clientLookup cert.fingerprint, cert.subject.CN, cert.issuer.CN
          catch err
            logger.error "Failed to lookup client: #{err}"

          if this.authenticated?
            if this.authenticated.clientID?
              this.header['X-OpenHIM-ClientID'] = this.authenticated.clientID
            sdc.timing "#{domain}.tlsAuthenticationMiddleware", startTime if statsdServer.enabled
            this.authenticationType = 'tls'
            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..."
            sdc.timing "#{domain}.tlsAuthenticationMiddleware", startTime if statsdServer.enabled
            yield next
        else
          this.authenticated = null
          logger.info "Could NOT authenticate via TLS: #{this.req.client.authorizationError}, trying next auth mechanism if any..."
          sdc.timing "#{domain}.tlsAuthenticationMiddleware", startTime if statsdServer.enabled
          yield next