forked from derhuerst/gemini
-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.js
134 lines (120 loc) · 3.82 KB
/
server.js
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
'use strict'
const debug = require('debug')('gemini:server')
const {createServer: createTlsServer} = require('tls')
const {EventEmitter} = require('events')
const {pipeline: pipe} = require('stream')
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 ? alpnId === ALPN_ID : true,
...opt,
}
const onConnection = (socket) => {
debug('connection', socket)
// todo: clarify if this is desired behavior
if (verifyAlpnId(socket.alpnProtocol) !== true) {
debug('invalid ALPN ID, closing socket')
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'
) {
debug('authorization error, closing socket')
socket.destroy(new Error(socket.authorizationError))
return;
}
const clientCert = socket.getPeerCertificate()
const req = createParser()
pipe(
socket,
req,
(err) => {
if (err) debug('error receiving request', err)
if (timeout && err) {
debug('socket closed while waiting for header')
}
// todo? https://nodejs.org/api/http.html#http_event_clienterror
},
)
const reportTimeout = () => {
socket.destroy(new Error('timeout waiting for header'))
}
let timeout = setTimeout(reportTimeout, 20 * 1000)
req.once('header', (header) => {
clearTimeout(timeout)
timeout = null
debug('received header', header)
// 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()
Object.defineProperty(res, 'socket', {value: socket})
pipe(
res,
socket,
(err) => {
if (err) debug('error sending response', err)
},
)
onRequest(req, res)
server.emit('request', req, res)
})
}
const server = createTlsServer({
// Disabled ALPNProtocols to mitigate connection issues in gemini
// clients as reported in #5
// 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