logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git
commit: e2bdd08bb9b69bb588ef826b67fcd89d5e2d2585
parent 836679cab40b0d901d3abeda5b43617dfa15f511
Author: Drew DeVault <sir@cmpwn.com>
Date:   Thu,  4 Mar 2021 11:13:03 -0500

Update Gemini TOFU recommendations

Diffstat:

Mcontent/blog/Gemini-TOFU.gmi27++++++++++++++++-----------
1 file changed, 16 insertions(+), 11 deletions(-)

diff --git a/content/blog/Gemini-TOFU.gmi b/content/blog/Gemini-TOFU.gmi @@ -2,40 +2,39 @@ I will have more to say about Gemini in the future, but for now, I wanted to wri => /gmni.gmi gmni: A gemini client -First of all, it's important to note that the Gemini specification explicitly mentions TOFU and the role of self-signed certificates: they are the norm in Geminiland, and if your client does not support them then you're going to be unable to browse many sites. However, the exact details are left up to the implementation. Here's what mine does: +First of all, it's important to note that the Gemini specification explicitly mentions TOFU and the role of self-signed certificates: they are the norm in Geminiland, and if your client does not support them then you're going to be unable to browse many sites. However, the exact details are left up to the implementation. + +## Client recommendations First, on startup, it finds the known_hosts file. For my client, this is `~/.local/share/gmni/known_hosts` (the exact path is adjusted as necessary per the XDG basedirs specification). Each line of this file represents a known host, and each host has four fields separated by spaces, in this order: * Hostname (e.g. gemini.circumlunar.space) * Fingerprint algorithm (e.g. SHA-512) * Fingerprint, in hexadecimal, with ':' between each octet (e.g. 55:01:D8...) -* Unix timestamp of the certificate's notAfter date If a known_hosts entry is encountered with a hashing algorithm you don't understand, it is disregarded. Then, when processing a request and deciding whether or not to trust its certificate, take the following steps: -1. Verify that the certificate makes sense. Check the notBefore and notAfter dates against the current time, and check that the hostname is correct (including wildcards). Apply any other scrutiny you want, like enforcing a good hash algorithm or an upper limit on the expiration date. If these checks do not pass, the trust state is INVALID, GOTO 5. - -2. Compute the certificate's fingerprint. Use the entire certificate (in OpenSSL terms, `X509_digest` will do this), not just the public key.† +1. Compute the certificate's fingerprint. Use the entire certificate (in OpenSSL terms, `X509_digest` will do this), not just the public key.† -3. Look up the known_hosts record for this hostname. If one is found, but the record is expired, disregard it. If one is found, and the fingerprint does not match, the trust state is UNTRUSTED, GOTO 5. Otherwise, the trust state is TRUSTED. GOTO 7. +2. Look up the known_hosts record for this hostname. If one is found, and the fingerprint does not match, the trust state is UNTRUSTED, GOTO 4. If found, and the fingerprint matches, the trust state is TRUSTED, GOTO 6. -4. The trust state is UNKNOWN. GOTO 5. +3. The trust state is UNKNOWN. GOTO 4. -5. Display information about the certficate and its trust state to the user, and prompt them to choose an action, from the following options: +4. Display information about the certficate and its trust state to the user, and prompt them to choose an action, from the following options: * If INVALID, the user's choices are ABORT or TRUST_TEMPORARY. * If UNKNOWN, the user's choices are ABORT, TRUST_TEMPORARY, or TRUST_ALWAYS. * If UNTRUSTED, abort the request and display a diagnostic message. The user must manually edit the known_hosts file to correct the issue. -6. Complete the requested action: +5. Complete the requested action: * If ABORT, terminate the request. * If TRUST_TEMPORARY, update the session's list of known hosts. * If TRUST_ALWAYS, append a record to the known_hosts file and update the session's list of known hosts. -7. Allow the request to proceed. +6. Allow the request to proceed. † Rationale: this fingerprint matches the output of `openssl x509 -sha512 -fingerprint`. @@ -51,7 +50,13 @@ My implementation doesn't *entirely* match this behavior, but it's close and I'l => https://git.sr.ht/~sircmpwn/gmni/tree/master/src/tofu.c src/tofu.c -Bonus recommendation for servers: you *should* use a self-signed certificate, and you *should not* use a certificate signed by one of the mainstream certificate authorities. We don't need to carry along the legacy CA cabal into our brave new Gemini future. +## Server recommendations + +You should use a self-signed certificate, and you should not use a certificate signed by one of the mainstream certificate authorities. We don't need to carry along the legacy CA cabal into our brave new Gemini future. + +You should also set the certificate expiration into the far future, hundreds of years from now, and move certificates from server to server to keep the trust state intact. Think how SSH host keys work. + +Finally, if you're writing a Gemini server, you should not burden your users with certificate maintenance at all. Generate a self-signed certificate for each host you intend to service on startup. Certificate maintenance is annoying and error prone, and because we use TOFU, we don't have to make the user do it. See also: