-- Leo's gemini proxy

-- Connecting to makeworld.space:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini; lang=en

TOFU Recommendations


gemini.circumlunar.space's certificate has changed, so it's time for a TOFU post!


EDIT: I have proposed another idea for TOFU in another post of mine. That does not invalidate this post, it is a separate idea. If that other idea becomes more widely accepted, and makes this post dated, I will update this page.


Intro


This post is about the TOFU aspect of Gemini. TOFU stands for Trust-On-First-Use, and it's how Gemini handles TLS certificate verification. It's also how SSH handles security.

TOFU on Wikipedia


This post also contains my personal recommendations and ideas for TOFU, and consequently how TOFU is implemented in

Amfora.


I hope that this document can act as a TOFU guide, and help Gemini client authors implement TOFU and make Gemini more secure for everyone.


For Gemini, this means that whenever a user visits a site for the first time, the client should record the site's TLS/x509 certificate in some way, and then when the user wants want to visit the site again, the client should check that the certificate matches the previous one. If it does not match, some sort of warning should be displayed to the user.


Recording a site's cert the first time it is seen is where the TOFU acronym comes from. You are trusting the site, on it's first "use", aka visit. But the above explanation is simplified, and client authors must do more to properly implement TOFU in their clients.


Before I continue, I want to point out, as Solderpunk as pointed out to me, that Gemini's TOFU+TLS system is not perfectly secure, and likely never will be. There are still ways to potentially intercept or change user traffic, like when a cert expires for example. But it is a lot better than nothing, a la Gopher.


Cert Storage


A basic TOFU implementation should store four things:


Host/domain (taken from URL)

Port (taken from URL)

The cert data itself (aka the cert ID)

The cert's expiry date (preferably stored in UTC)


NOTE: Whenever I refer to "saving" the cert below, I mean storing these four pieces of data, not just the entire cert.


The port one is easily forgotten, I know I forgot it when I first built Amfora.


The way to store cert data is not standardized, but my recommendation is a SHA-256 hash of the entire *binary* cert, or preferably, a SHA-256 hash of the binary "Subject Public Key Info" (SPKI) section of the cert.


Justification


SHA-256 is a widely available, fast, and robust hash function, and using it will reduce the storage space taken up in comparison to storing the entire binary cert.


As for why just hashing the SPKI is preferably, it's because that is the part of the certificate that stores the public key type, and the public key itself. If that part of the cert has not changed, then it doesn't matter if other parts of the cert have, like the signature algorithm or the serial number, because you know the same person is making the connection, because they still have control of that keypair.


As the report linked in the coda says:


> the raw Subject Public Key Info (SPKI) structure in DER format should be pinned, which contains the type of public key and data specific to that public key type. Pinning just the public key without the type would allow attacks using a different type of public key, while pinning the whole certificate would result in more change notifications then necessary.


> This is because certificates are sometimes re-issued using the same publickey, e.g. with a different hash algorithm for signature – a pinned publickey would thus remain valid when this happens.


This is mostly me being fancy. Most Gemini sysadmins don't change only part of their cert, they just change the entire thing when it's going to expire, which will affect the SPKI section too. But if you can only hash the SPKI section in your code, I would suggest it, as it can only improve UX. And if you're a Gemini server sysadmin, consider only changing the expiry date and signature, instead of the entire cert.


Walkthrough


The following is a step-by-step example of what your code should do every time a request is made. Note that this code assumes that there are already basic TLS checks being made with each request: the cert is not expired, the cert is valid for the requested host, the cert is not for a future date, etc.


First make the TLS connection (making the actual Gemini request is optional). Then perform a series of checks on the connection's cert, with the end result being a boolean: Either continuing with the request because the certificate is valid according to TOFU logic, or stopping and raising an error to the user, because it is not valid.


Checks / if statements, in order:


Attempt to load the cert ID and expiry from the TOFU database, given the request's hostname and port. If it doesn't exist, save the new cert and continue with the request. If it does exist, move to the next step.


Check the ID of the connection cert matches the stored cert ID. If it does, continue with the request. If the IDs do not match, move to the next step.


Check the stored expiry date for the cert. If it's expired (current time is later than that time), save the connection cert, overwriting any previous data for this host. This is trusting a new cert, because the old one expired. The request can be continued. If the stored expiry date has not passed, move to next step.


If all these checks fail, than the cert is invalid. This might be a security attack, or that a cert has changed early. Raise an error to the user indicating that there is a security issue. Optionally continue with the request if the user wants to, and save the cert if they do, overwriting any previous data for this host.


NOTE: If you are using SPKI as a cert ID as I recommend, then you should be re-storing the expiry date of the cert if the ID is the same (second bullet in the steps above). This is because it's possible to change a cert's expiry date but leave the SPKI section (and therefore the ID) the same.


User Error Message


What kind of error to show to user if the cert is invalid will likely be a debated topic. It's hard one to answer, because an invalid cert potentially represents a large security issue, where someone is intercepting your traffic, and can now record anything you access, or even worse - send their own data back to you, pretending to be the server you wanted. However, 99% of the time, all it means is that someone has changed their cert a bit early. So, what should be said? And more importantly, how easy should it be for the user to continue with the request anyway?


In Amfora, I have taken the somewhat less secure route of having a popup when the TOFU cert is invalid, that allows the user to either continue with the request or cancel. The popup is red and scary of course, but it is still very easy to continue. The text is:


> example.com's certificate has changed, possibly indicating an security issue. Are you sure you want to continue?


This is pretty vague to a user who doesn't understand TOFU. I'd like to update it, but I don't want to have too much text in the popup box. For now, I have been relying on Amfora users being informed.


I would be happy to hear about your ideas for this, and what your clients do or will do.


Other clients


Bombadillo

will say:

> No matching certificate was found for host "makeworld.gq"

which personally I find obtuse and non-obvious. There was a mailing list post today where a user asked about what this meant, thinking it was maybe an issue with the site they were visiting.


To accept a new cert in Bombadillo, one needs to edit the config file, or use the :purge command.


Kristall's

error message is:


> Mistrusted Host

> The host you tried to visit does not look trustworty anymore. The certificate changed since your last visit.

> Fingerprint:

> a7:6d:6f:54:7e:7c:a6:63:44:df:77:66:44:9a:10:47:fe:4c:ac:74:89:a9:26:50:91:be:46:ab:cd:98:e3:7b

> If you still trust this host, please revoke trust in the settings menu, then reload the page.


AV-98

allows you to continue with the request, and has a great error message that I plan on sort of using in Amfora.


> [SECURITY WARNING] Unrecognised certificate!

> The certificate presented for {host} ({address}) has never been seen before.

> This MIGHT be a Man-in-the-Middle attack.

> A different certificate has previously been seen {n} times.


And then:

> That certificate has expired, which reduces suspicion somewhat.

or

> That certificate is still valid for: {time}

Example time: "29 days, 14:23:08.649315"


This is very informative. It's not obvious to non-technical users, but I doubt a client like AV-98 needs to worry about that too much.


Note that it doesn't automatically allow any cert after the current one has expired, unlike what I recommended above. This is more secure, as an attacker could wait for a cert to expire and then perform a MITM attack. I've opted not to recommend this for UX reasons, but I might change that in the future.


EDIT 2020-07-04:

Deedum,

the Gemini mobile browser, has a TOFU message like

this. [IMG]


It asks users to confirm the new fingerprint out-of-band, and it gives some cert information.


Coda


If you disagree with my implementation, please let me know in the comments! Or email me, it's on my about page. This is an important security topic.


View comments 💬


The recommendations in this post were informed by this report on TOFU, that Solderpunk shared on the mailing list:

Public Key Pinning for TLS Using a Trust on First Use Model [HTTPS]


-- Response ended

-- Page fetched on Fri Apr 19 10:44:53 2024