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
|
'use strict'
const crypto = require('crypto')
const https = require('https')
const { test } = require('tap')
const { Client, buildConnector } = require('..')
const pem = require('https-pem')
const caFingerprint = getFingerprint(pem.cert.toString()
.split('\n')
.slice(1, -1)
.map(line => line.trim())
.join('')
)
test('Validate CA fingerprint with a custom connector', t => {
t.plan(2)
const server = https.createServer(pem, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end('hello')
})
server.listen(0, function () {
const connector = buildConnector({ rejectUnauthorized: false })
const client = new Client(`https://localhost:${server.address().port}`, {
connect (opts, cb) {
connector(opts, (err, socket) => {
if (err) {
cb(err)
} else if (getIssuerCertificate(socket).fingerprint256 !== caFingerprint) {
socket.destroy()
cb(new Error('Fingerprint does not match'))
} else {
cb(null, socket)
}
})
}
})
t.teardown(() => {
client.close()
server.close()
})
client.request({
path: '/',
method: 'GET'
}, (err, data) => {
t.error(err)
data.body
.resume()
.on('end', () => {
t.pass()
})
})
})
})
test('Bad CA fingerprint with a custom connector', t => {
t.plan(2)
const server = https.createServer(pem, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end('hello')
})
server.listen(0, function () {
const connector = buildConnector({ rejectUnauthorized: false })
const client = new Client(`https://localhost:${server.address().port}`, {
connect (opts, cb) {
connector(opts, (err, socket) => {
if (err) {
cb(err)
} else if (getIssuerCertificate(socket).fingerprint256 !== 'FO:OB:AR') {
socket.destroy()
cb(new Error('Fingerprint does not match'))
} else {
cb(null, socket)
}
})
}
})
t.teardown(() => {
client.close()
server.close()
})
client.request({
path: '/',
method: 'GET'
}, (err, data) => {
t.equal(err.message, 'Fingerprint does not match')
t.equal(data.body, undefined)
})
})
})
function getIssuerCertificate (socket) {
let certificate = socket.getPeerCertificate(true)
while (certificate && Object.keys(certificate).length > 0) {
// invalid certificate
if (certificate.issuerCertificate == null) {
return null
}
// We have reached the root certificate.
// In case of self-signed certificates, `issuerCertificate` may be a circular reference.
if (certificate.fingerprint256 === certificate.issuerCertificate.fingerprint256) {
break
}
// continue the loop
certificate = certificate.issuerCertificate
}
return certificate
}
function getFingerprint (content, inputEncoding = 'base64', outputEncoding = 'hex') {
const shasum = crypto.createHash('sha256')
shasum.update(content, inputEncoding)
const res = shasum.digest(outputEncoding)
return res.toUpperCase().match(/.{1,2}/g).join(':')
}
|