openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
540 lines (539 loc) • 20.1 kB
HTML
<html lang="en">
<head>
<title>Code coverage report for src/middleware/tlsAuthentication.coffee</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../../prettify.css" />
<link rel="stylesheet" href="../../base.css" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1>
<a href="../../index.html">All files</a> / <a href="index.html">src/middleware</a> tlsAuthentication.coffee
</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">29.58% </span>
<span class="quiet">Statements</span>
<span class='fraction'>21/71</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/8</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/12</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">30% </span>
<span class="quiet">Lines</span>
<span class='fraction'>21/70</span>
</div>
</div>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet">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
159</td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-no"> </span>
<span class="cline-any cline-neutral"> </span>
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">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 = <span class="fstat-no" title="function not covered" >(</span>done) ->
Keystore.findOne <span class="fstat-no" title="function not covered" >(</span>err, keystore) ->
<span class="cstat-no" title="statement not covered" ></span> done err, null if err
<span class="cstat-no" title="statement not covered" > certs = r</span>ootCAs
if keystore.ca?
<span class="cstat-no" title="statement not covered" ><span class="cstat-no" title="statement not covered" > for cert in keystore.ca</span></span>
<span class="cstat-no" title="statement not covered" > certs.push cert.data</span>
<span class="cstat-no" title="statement not covered" > return d</span>one 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 = <span class="fstat-no" title="function not covered" >(</span>mutualTLS, done) ->
Keystore.findOne <span class="fstat-no" title="function not covered" >(</span>err, keystore) ->
if err
<span class="cstat-no" title="statement not covered" > logger.error "Could not fetch keystore: #{err}"</span>
<span class="cstat-no" title="statement not covered" > return done err</span>
if keystore?
options =
key: keystore.key
cert: keystore.cert.data
#if key has password add it to the options
<span class="cstat-no" title="statement not covered" > if keystore.passphrase</span>
<span class="cstat-no" title="statement not covered" > options.passphrase = keystore.passphrase</span>
else
<span class="cstat-no" title="statement not covered" > return done(new Error 'Keystore does not exist')</span>
if mutualTLS
exports.getTrustedClientCerts <span class="fstat-no" title="function not covered" >(</span>err, certs) ->
if err
<span class="cstat-no" title="statement not covered" > logger.error "Could not fetch trusted certificates: #{err}"</span>
<span class="cstat-no" title="statement not covered" > return done err, null</span>
<span class="cstat-no" title="statement not covered" > options.ca = c</span>erts
<span class="cstat-no" title="statement not covered" > options.requestCert = t</span>rue
<span class="cstat-no" title="statement not covered" > options.rejectUnauthorized = f</span>alse # 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 = <span class="fstat-no" title="function not covered" >(</span>fingerprint, subjectCN, issuerCN) ->
<span class="cstat-no" title="statement not covered" > logger.debug "Looking up client linked to cert with fingerprint #{fingerprint} with subject #{subjectCN} and issuer #{issuerCN}"</span>
<span class="cstat-no" title="statement not covered" > deferred = Q</span>.defer()
<span class="cstat-no" title="statement not covered" > Client.findOne certFingerprint: fingerprint, <span class="fstat-no" title="function not covered" >(</span>err, result) -></span>
<span class="cstat-no" title="statement not covered" ></span> deferred.reject err if err
if result?
# found a match
<span class="cstat-no" title="statement not covered" > return deferred.resolve result</span>
if subjectCN is issuerCN
# top certificate reached
<span class="cstat-no" title="statement not covered" > return deferred.resolve null</span>
if config.tlsClientLookup.type is 'in-chain'
# walk further up and cert chain and check
utils.getKeystore <span class="fstat-no" title="function not covered" >(</span>err, keystore) ->
<span class="cstat-no" title="statement not covered" ></span> deferred.reject err if err
<span class="cstat-no" title="statement not covered" > missedMatches = 0</span>
# find the isser cert
if not keystore.ca? || keystore.ca.length < 1
<span class="cstat-no" title="statement not covered" > logger.info "Issuer cn=#{issuerCN} for cn=#{subjectCN} not found in keystore."</span>
deferred.resolve null
else
<span class="cstat-no" title="statement not covered" > for cert in keystore.ca</span>
do <span class="fstat-no" title="function not covered" >(</span>cert) ->
pem.readCertificateInfo cert.data, <span class="fstat-no" title="function not covered" >(</span>err, info) ->
if err
<span class="cstat-no" title="statement not covered" > return deferred.reject err</span>
if info.commonName is issuerCN
<span class="cstat-no" title="statement not covered" > promise = c</span>lientLookup cert.fingerprint, info.commonName, info.issuer.commonName
<span class="cstat-no" title="statement not covered" > promise.then <span class="fstat-no" title="function not covered" >(</span>result) -> deferred.resolve result</span>
else
<span class="cstat-no" title="statement not covered" > missedMatches++</span>
if missedMatches is keystore.ca.length
<span class="cstat-no" title="statement not covered" > logger.info "Issuer cn=#{issuerCN} for cn=#{subjectCN} not found in keystore."</span>
deferred.resolve null
else
if config.tlsClientLookup.type isnt 'strict'
<span class="cstat-no" title="statement not covered" > logger.warn "tlsClientLookup.type config option does not contain a known value, defaulting to 'strict'. Available options are 'strict' and 'in-chain'."</span>
deferred.resolve null
<span class="cstat-no" title="statement not covered" > return deferred.p</span>romise
if process.env.NODE_ENV == "test"
exports.clientLookup = clientLookup
###
# Koa middleware for mutual TLS authentication
###
exports.koaMiddleware = <span class="fstat-no" title="function not covered" >(</span>next) ->
<span class="cstat-no" title="statement not covered" ></span> startTime = new Date() if statsdServer.enabled
if this.authenticated?
yield next
else
<span class="cstat-no" title="statement not covered" > if this.req.client.authorized is true</span>
<span class="cstat-no" title="statement not covered" > cert = t</span>his.req.connection.getPeerCertificate true
<span class="cstat-no" title="statement not covered" > logger.info "#{cert.subject.CN} is authenticated via TLS."</span>
# lookup client by cert fingerprint and set them as the authenticated user
try
<span class="cstat-no" title="statement not covered" > this.authenticated = yield clientLookup cert.fingerprint, cert.subject.CN, cert.issuer.CN</span>
catch <span class="cstat-no" title="statement not covered" >err</span>
<span class="cstat-no" title="statement not covered" > logger.error "Failed to lookup client: #{err}"</span>
<span class="cstat-no" title="statement not covered" > if this.authenticated?</span>
if this.authenticated.clientID?
<span class="cstat-no" title="statement not covered" > this.header['X-OpenHIM-ClientID'] = this.authenticated.clientID</span>
<span class="cstat-no" title="statement not covered" ></span> sdc.timing "#{domain}.tlsAuthenticationMiddleware", startTime if statsdServer.enabled
<span class="cstat-no" title="statement not covered" > this.authenticationType = '</span>tls'
yield next
else
<span class="cstat-no" title="statement not covered" > this.authenticated = n</span>ull
<span class="cstat-no" title="statement not covered" > 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..."</span>
<span class="cstat-no" title="statement not covered" ></span> sdc.timing "#{domain}.tlsAuthenticationMiddleware", startTime if statsdServer.enabled
yield next
else
<span class="cstat-no" title="statement not covered" > this.authenticated = n</span>ull
<span class="cstat-no" title="statement not covered" > logger.info "Could NOT authenticate via TLS: #{this.req.client.authorizationError}, trying next auth mechanism if any..."</span>
<span class="cstat-no" title="statement not covered" ></span> sdc.timing "#{domain}.tlsAuthenticationMiddleware", startTime if statsdServer.enabled
yield next
</pre></td></tr>
</table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage
generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Mon Oct 10 2016 13:39:22 GMT+0200 (SAST)
</div>
</div>
<script src="../../prettify.js"></script>
<script>
window.onload = function () {
if (typeof prettyPrint === 'function') {
prettyPrint();
}
};
</script>
<script src="../../sorter.js"></script>
</body>
</html>