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
|
'use strict'
const {createServer: createTlsServer} = require('tls')
const {EventEmitter} = require('events')
const createParser = require('./lib/request-parser')
const createResponse = require('./lib/response')
const {
ALPN_ID,
MIN_TLS_VERSION,
} = require('./lib/util')
const createGeminiServer = (opt = {}, onRequest) => {
if (typeof opt === 'function') {
onRequest = opt
opt = {}
}
const {
cert, key, passphrase,
tlsOpt,
verifyAlpnId,
} = {
cert: null, key: null, passphrase: null,
tlsOpt: {},
verifyAlpnId: alpnId => alpnId === ALPN_ID,
...opt,
}
const onConnection = (socket) => {
// todo: clarify if this is desired behavior
if (verifyAlpnId(socket.alpnProtocol) !== true) {
socket.destroy()
return;
}
if (
socket.authorizationError &&
// allow self-signed certs
socket.authorizationError !== 'SELF_SIGNED_CERT_IN_CHAIN' &&
socket.authorizationError !== 'DEPTH_ZERO_SELF_SIGNED_CERT' &&
socket.authorizationError !== 'UNABLE_TO_GET_ISSUER_CERT'
) {
socket.destroy(new Error(socket.authorizationError))
return;
}
const clientCert = socket.getPeerCertificate()
const req = createParser()
socket.pipe(req)
socket.once('error', (err) => {
socket.unpipe(req)
req.destroy(err)
})
const close = () => {
socket.destroy()
req.destroy()
}
let timeout = setTimeout(close, 20 * 1000)
req.once('header', (header) => {
clearTimeout(timeout)
// prepare req
req.socket = socket
req.url = header.url
const url = new URL(header.url, 'http://foo/')
req.path = url.pathname
if (clientCert && clientCert.fingerprint) {
req.clientFingerprint = clientCert.fingerprint
}
// todo: req.abort(), req.destroy()
// prepare res
const res = createResponse()
res.pipe(socket)
res.once('error', (err) => {
console.error('error', err)
res.unpipe(socket)
socket.destroy(err)
})
Object.defineProperty(res, 'socket', {value: socket})
onRequest(req, res)
server.emit('request', req, res)
})
}
const server = createTlsServer({
ALPNProtocols: [ALPN_ID],
minVersion: MIN_TLS_VERSION,
// > Usually the server specifies in the Server Hello message if a
// > client certificate is needed/wanted.
// > Does anybody know if it is possible to perform an authentication
// > via client cert if the server does not request it?
//
// > The client won't send a certificate unless the server asks for it
// > with a `Certificate Request` message (see the standard, section
// > 7.4.4). If the server does not ask for a certificate, the sending
// > of a `Certificate` and a `CertificateVerify` message from the
// > client is likely to imply an immediate termination from the server
// > (with an unexpected_message alert).
// https://security.stackexchange.com/a/36101
requestCert: true,
// > Gemini requests typically will be made without a client
// > certificate being sent to the server. If a requested resource
// > is part of a server-side application which requires persistent
// > state, a Gemini server can [...] request that the client repeat
// the request with a "transient certificate" to initiate a client
// > certificate section.
rejectUnauthorized: false,
cert, key, passphrase,
...tlsOpt,
}, onConnection)
return server
}
module.exports = createGeminiServer
|