summaryrefslogtreecommitdiffstats
path: root/server.js
blob: 285b63b44864615e340b56455c5a511761ecc602 (plain)
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
'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 {
		tlsOpt,
	} = {
		tlsOpt: {},
		...opt,
	}

	const onConnection = (socket) => {
		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,
		...tlsOpt,
	}, onConnection)

	return server
}

module.exports = createGeminiServer