diff options
| -rw-r--r-- | connect.js | 1 | ||||
| -rw-r--r-- | examples/server.js | 2 | ||||
| -rw-r--r-- | lib/request-parser.js | 1 | ||||
| -rw-r--r-- | lib/response-parser.js | 14 | ||||
| -rw-r--r-- | readme.md | 139 | ||||
| -rw-r--r-- | server.js | 3 |
6 files changed, 149 insertions, 11 deletions
@@ -22,7 +22,6 @@ const connectToGeminiServer = (opt, cb) => { port: DEFAULT_PORT, cert: null, key: null, passphrase: null, tlsOpt: {}, - // todo: TOFU via isTrustedCertificate() ...opt, } diff --git a/examples/server.js b/examples/server.js index d9eb587..5a9779e 100644 --- a/examples/server.js +++ b/examples/server.js @@ -34,7 +34,7 @@ createCert('example.org') tlsOpt: keys, // todo: SNICallback }, onRequest) - server.on('error', onError) + server.on('error', console.error) server.listen(DEFAULT_PORT, (err) => { if (err) return onError(err) diff --git a/lib/request-parser.js b/lib/request-parser.js index 04c5cd6..6cfba3d 100644 --- a/lib/request-parser.js +++ b/lib/request-parser.js @@ -1,7 +1,6 @@ 'use strict' const {Transform} = require('stream') -const {MESSAGES} = require('./statuses') // https://gemini.circumlunar.space/docs/spec-spec.txt, 1.2 // > Gemini requests are a single CRLF-terminated line with the diff --git a/lib/response-parser.js b/lib/response-parser.js index cb50f0a..2d64a47 100644 --- a/lib/response-parser.js +++ b/lib/response-parser.js @@ -79,12 +79,12 @@ const createResponseParser = () => { return out } -const p = createResponseParser() -p.on('error', console.error) -p.on('header', h => console.log('header', h)) -p.on('data', d => console.log('data', d.toString('utf8'))) -const b = str => Buffer.from(str, 'utf8') -p.write(b('31 gemini://examp')) -p.write(b('le.org/foo?bar\r\n')) +// const p = createResponseParser() +// p.on('error', console.error) +// p.on('header', h => console.log('header', h)) +// p.on('data', d => console.log('data', d.toString('utf8'))) +// const b = str => Buffer.from(str, 'utf8') +// p.write(b('31 gemini://examp')) +// p.write(b('le.org/foo?bar\r\n')) module.exports = createResponseParser @@ -19,8 +19,145 @@ npm install @derhuerst/gemini ## Usage +### Server + +The following code assumes that you have a valid SSL certificate & key. + +```js +const {createServer, DEFAULT_PORT} = require('@derhuerst/gemini') + +const onError = (err) => { + console.error(err) + process.exit(1) +} + +const server = createGeminiServer({ + cert: …, // certificate (+ chain) + key: …, // private key + passphrase: …, // passphrase, if the key is encrypted +}, (req, res) => { + if (req.path === '/foo') { + if (!req.clientFingerprint) { + return res.requestTransientClientCert('/foo is secret!') + } + res.write('foo') + res.end('!') + } else if (req.path === '/bar') { + res.redirect('/foo') + } else { + res.gone() + } +}) + +server.listen(DEFAULT_PORT) +server.on('error', console.error) +``` + +### Client + +```js +const request = require('@derhuerst/gemini/client') + +request('/bar', (err, res) => { + if (err) { + console.error(err) + process.exit(1) + } + + console.log(res.statusCode, res.statusMessage) + if (res.meta) console.log(res.meta) + res.pipe(process.stdout) +}) +``` + +#### [TOFU](https://en.wikipedia.org/wiki/Trust_on_first_use)-style client certificates + +> Interactive clients for human users MUST inform users that such a session has been requested and require the user to approve generation of such a certificate. Transient certificates MUST NOT be generated automatically. +– [Gemini spec](https://gemini.circumlunar.space/docs/spec-spec.txt), section 1.4.3 + +If is up to you how to implement that approval process. As an example, we're going to build a simple CLI prompt: + +```js +const {createInterface} = require('readline') + +const letUserConfirmClientCertUsage = ({host, reason}, cb) => { + const prompt = createInterface({ + input: process.stdin, + output: process.stdout, + history: 0, + }) + prompt.question(`Send client cert to ${host}? Server says: "${reason}". y/n > `, (confirmed) => { + prompt.close() + cb(confirmed === 'y' || confirmed === 'Y') + }) +} + +request('/foo', { + // opt into client certificates + useClientCerts: true, + letUserConfirmClientCertUsage, +}, cb) +``` + + +## API + +```js +const createServer = require('@derhuerst/gemini/server') +createServer(opt = {}, onRequest) +``` + +`opt` extends the following defaults: + +```js +{ + // SSL certificate & key + cert: null, key: null, passphrase: null, + // additional options to be passed into `tls.createServer` + tlsOpt: {}, +} +``` + +--- + +```js +const request = require('@derhuerst/gemini/client') +request(pathOrUrl, opt = {}, cb) +``` + +`opt` extends the following defaults: + +```js +{ + // follow redirects automatically + followRedirects: false, + // client certificates + useClientCerts: false, + letUserConfirmClientCertUsage: null, + clientCertStore: defaultClientCertStore, + // additional options to be passed into `tls.connect` + tlsOpt: {}, +} +``` + +--- + +```js +const connect = require('@derhuerst/gemini/connect') +connect(opt = {}, cb) +``` + +`opt` extends the following defaults: + ```js -todo +{ + hostname: '127.0.0.1', + port: 1965, + // client certificate + cert: null, key: null, passphrase: null, + // additional options to be passed into `tls.connect` + tlsOpt: {}, +} ``` @@ -15,8 +15,10 @@ const createGeminiServer = (opt = {}, onRequest) => { opt = {} } const { + cert, key, passphrase, tlsOpt, } = { + cert: null, key: null, passphrase: null, tlsOpt: {}, ...opt, } @@ -103,6 +105,7 @@ const createGeminiServer = (opt = {}, onRequest) => { // the request with a "transient certificate" to initiate a client // > certificate section. rejectUnauthorized: false, + cert, key, passphrase, ...tlsOpt, }, onConnection) |
