golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.34k stars 17.7k forks source link

proposal: crypto/x509: implement OCSP verifier #40017

Open FiloSottile opened 4 years ago

FiloSottile commented 4 years ago

Although we have a golang.org/x/crypto/ocsp package, we don't in fact have an OCSP verifier. The existing package provides serialization and parsing, but not APIs for "get me an OCSP response for this certificate" and "given this certificate and this OCSP response tell me if it's revoked". (They are separate because you only want the latter when checking stapled responses.)

There is a lot of complexity, subtlety, and dark knowledge involved in OCSP unfortunately. Here are a few notes on things the verifier needs to do (from reading this thread):

There are definitely a lot more things to consider (for example, the id-pkix-ocsp-nocheck extension needs to be processed itself), the list above are just notes of things I learned from that one incident.

A difficult question is where to put the code, and how to surface it. We'll want to use it in crypto/tls for Must-Staple (#22274) but it feels like the wrong place for the code to live. An obvious answer would be golang.org/x/crypto/ocsp, but then we can't use it from crypto/x509 without an import loop. Should x509.VerifyOptions have an option to verify a stapled response? Probably, I feel like we'd regret doing OCSP verification separately from path building and certificate verification anyway. What about the API to fetch a response? Maybe that can stay in golang.org/x/crypto/ocsp, separating the concerns of obtaining responses and verifying them.

We should also look around the ecosystem, because surely someone had to implement this, and we should compare results.

rolandshoemaker commented 4 years ago

I think this would go a long way towards getting rid of the numerous traps x/crypto/ocsp currently presents users. It definitely feels like response/certificate validation should happen in Cerifitcate.Verify, we'll already have all the requisite information and it feels unlikely that users will want to check OCSP at some other time (either pre or post chain building).

For delegated responders it probably makes sense to ignore whether a delegated responder contains id-pkix-ocsp-nocheck, but it's something response fetching code would probably want to take into consideration. There is also the question of what to do if the EE being verified has id-pkix-ocsp-nocheck but the user provided an OCSP response to verify against (this is a weird corner case, since presumably if the CA put the ext in a leaf it wouldn't be generating an OCSP response for it, but I wouldn't put it past anyone).

Fetching code probably should live in x/crypto/ocsp (or a paired down version of the API could make its way into crypto/x509 as a way of dissuading users from using the more dangerous bits that lurk in x/crypto/ocsp entirely) since the request generation code is already mostly complete and all that would need to be added would be a wrapper around a http.Client which generates the request and extracts the responder URL from the AIA extension in the certificate to check.

There is a bit of a chicken and egg problem with the fetching code and the verification code. For generating the OCSP request you need the leaf issuer, which means you might want to do chain building before you request the OCSP response (otherwise you need to do some pre-processing to determine the issuer, or you just assume the second cert in any chain you were sent is the issuer...), but as previously mentioned it'd probably be nicer to be able to do OCSP verification during chain building. The other less desirable option would be to to add a new Certificate.VerifyOCSP (or something) method which only verifies the OCSP response, so you could do Verify chain -> Fetch OCSP -> Verify OCSP...

vanbroup commented 4 years ago

I vote to implement this in Cerifitcate.Verify during the chain building as @rolandshoemaker suggested. If you would go for something like Certificate.VerifyOCSP at some point you would get Certificate.VerifyCRL as not all PKI is based on OCSP.

Ideally VerifyOptions would have an option CheckStatus or similar to let it check the current certificate status either using OCSP or CRL whatever is available in the certificate.

The id-pkix-ocsp-nocheck should be taken into account by the OCSP client when verifying the response but can be ignored when validating certificates of the chain itself. This because, if the responder is using a CA signed response, it should not include the id-pkix-ocsp-nocheck extension and the CA certificate who signed the response should be checked for revocation with it's issuer.

We might also want to cache the response according to the NotAfter, Expires or Cache-Control information.

sleevi commented 4 years ago

There is a bit of a chicken and egg problem with the fetching code and the verification code. For generating the OCSP request you need the leaf issuer, which means you might want to do chain building before you request the OCSP response (otherwise you need to do some pre-processing to determine the issuer, or you just assume the second cert in any chain you were sent is the issuer...), but as previously mentioned it'd probably be nicer to be able to do OCSP verification during chain building. The other less desirable option would be to to add a new Certificate.VerifyOCSP (or something) method which only verifies the OCSP response, so you could do Verify chain -> Fetch OCSP -> Verify OCSP...

Right, as Roland suggests, doing it during chain building is the 'desired' implementation.

RFC 4158, Section 3.5.9 discusses some of the trade-offs here. Note that, depending on how you want to handle nocheck, revocation is potentially ideally implemented via a 'reverse' path search. That is, as reflected in RFC 5280, 6.1.3, you revocation check the root, then the intermediate, then the leaf. If, during revocation checking the leaf, you encounter a delegated responder for the intermediate, you can either check just that responder (with the intermediate) or via CRL. Since you can get into responder bounces, you probably have to have a limit, or force a fallback to CRL, or just refuse anything with nocheck (as it seems Microsoft is doing, as best I can tell)

That is, if you have

Intermediate ----> Responder 1
              \--> Responder 2
               \-> Responder 3
                \> Server

It's possible for Server to be responded by Responder 1, who when you check status (i.e. it lacks nocheck), gets a response from Responder 2, which also lacks nocheck and gets a response from Responder 3... etc. So either requiring nocheck or profiling that down seems sensible, or requiring a CRL if nocheck isn't present.

rolandshoemaker commented 4 years ago

It feels like there are two different paths we can go down here, implementing verification with out-of-band fetching (i.e. verifying using a stapled, or otherwise fetched, response) or verification with in-band fetching (i.e. fetching responses during verification) These can share a good bit of code (basically the actual verification logic) but have different implementation complexities.

It seems like probably the path with the least friction, for now anyway, is implementation out-of-band verification first and then deciding on the more complex problem of adding in-band verification once that is complete.

tialaramex commented 4 years ago

Fetching OCSP can introduce both privacy and usability surprises. Privacy: Your client is now talking to somebody unexpected (based on an HTTP URL in the AIA OCSP item) and it's telling them which certificate you're currently interested in. It is obliged to use plaintext HTTP for this transaction and although the answers should be signed (so a MITM can't fake the response) the query can be read by anybody on the path.

Usability: Sometimes (too often) OCSP servers aren't reachable. Sometimes this is because they are on the Internet and you can't reach some or all of the public Internet from where you are. Sometimes though it's just the OCSP server is down.

In contrast the Stapled scenario is pretty much worry free.

As a result I'd strongly urge that any opt-in / opt-out type behaviour focuses on these network behaviours (whether to fetch stuff, over HTTP, from some server we discovered in the AIA records) not on disabling / enabling OCSP checking itself.

In particular it's desirable that on the one hand a quiet piece of software doesn't end up broadcasting its existence to the world because it saw some random Let's Encrypt certificate, while on the other hand a certificate with OCSP-must-staple set is always rejected by a Go program if that OCSP response is missing.

FiloSottile commented 4 years ago

Yes, however we end up structuring the API, a verification by default will not contact the network.

I'd still like to put fetching in a separate package, but it sounds like it would require a chain, and the chain comes from Verify, but the response would be fed to Verify.

xCoderly commented 4 years ago

you should keep in mind that not just Verification ( Valid / Invalid ) that's needed, a complete OCSP-stapling diagnose is needed, for example someone want to verify Stapling if activated on any web server or no, and to be able to get information from the OCSP-must-staple response from web server ..

rsc commented 4 years ago

Is this really for crypto/x509 and not x/crypto?

rsc commented 4 years ago

Maybe we should put it in x/crypto and figure out how to deal with import loops between the standard library and x repos?

FiloSottile commented 3 years ago

It's not (just) a matter of import loops. A good OCSP verifier needs a chain builder to do its job well for all cases, and the thing that has a chain builder is x509.Verify, so the natural place to put the OCSP response to be validated is x509.VerifyOptions. It's also a better and safer API, because Verify just returns an error if the certificate is revoked instead of making you go check something else while holding a chain you might mistake for valid.

OTOH, fetching an OCSP response if it's not stapled requires the chain, which is the output of Verify. This makes it awkward to fetch a OCSP response to put into VerifyOptions if you don't already have a stapled one. How other verifiers solved it has been by making connections to the Internet during Verify, which we are not going to do. We might just accept that this is for stapling primarily.

rsc commented 3 years ago

I am not sure I see a proposal here. Do you have concrete API you are proposing? If not, maybe we should put this on hold until there is API to evaluate.

rolandshoemaker commented 3 years ago

I think the current API proposal is basically just adding a OCSPResponses field to x509.VerifyOptions.

The main question currently is do we either use a [][]byte, which means we need to re-implement OCSP parsing in crypto/x509, or ocsp.Response, which means we probably need to move the existing x/crypto/ocsp code into crypto/x509 because of the import loops.

FiloSottile commented 3 years ago

I think there are multiple open questions on how the fetch-verify cycle looks like. I'm ok with putting this on hold to take it off the active proposal radar, I expect it will take a while.

rsc commented 3 years ago

Putting on hold until there is API to review.

nightlyone commented 2 years ago

@FiloSottile the import loop has a well known solution: The bundler.

That is how you automatically get a version of golang.org/x/net/http2 into the net/http package to support transparent http2 support.

Not the most beautiful solution but a pragmatic, existing and proven one.

seankhliao commented 4 months ago

https://letsencrypt.org/2024/07/23/replacing-ocsp-with-crls.html

Does OCSP still have a future worth supporting?

mholt commented 4 months ago

It depends if the rest of the ecosystem follows Let's Encrypt. (Which is kind of tending to be the case.)

The discussion at the top of HN is pretty good, involving Andrew Ayer and LE's executive director, Josh Aas: https://news.ycombinator.com/item?id=41046956 -- OCSP is not sufficiently replaced by CRLs, and the CABF ballot results are unfortunate.

In the absence of ARI (which is the vast majority of CAs and certs), OCSP has been a handy way to notify servers which are automating certificates that a certificate is being revoked; they have until the end of the OCSP response validity period to replace it. Caddy has made very successful use of this technique to keep sites and services online when others go down.

The privacy arguments against OCSP are unconvincing. OCSP is not a privacy problem. End-user clients querying OCSP on every page load is. That is a client behavioral issue, not an issue with OCSP. With stapling, OCSP is a fantastic boon to privacy, and removing OCSP infrastructure eliminates this privacy boon for millions of users.

Additionally, parsing CRLs is not feasible on most servers because of the size of the lists. There is no implementation of streaming CRL parsing in the standard library or the /x/ libs.

Let's Encrypt does not even know yet what to do about Must-Staple; removing support for that will break all the automated servers that require it. (You can't safely silently issue a cert that was requested with Must-Staple without one.)

OCSP is probably needed until cert lifetimes are down to <= 7 days.

But if changing the tide is impossible, I'd really like to see a good, standard, high-level CRL streaming implementation that can be used to see if a certificate is on the list without loading the whole thing into memory.

rolandshoemaker commented 4 months ago

I've soured considerably on OCSP over the years, and having worked on Let's Encrypts OCSP responder when I worked there I can understand why they'd want to move away from supporting it as soon as they can. While I understand @AGWA's point that it is an extremely useful tool to keep an eye on CA's, that is clearly not what it is supposed to be, and not really something that should influence our plans.

With shortening certificate lifespans, stapling in particular feels like a waste of time. It clearly reduces load on responders, and puts the impetus on the server to fetch and serve responses, but because of the incredibly patchy (and often times broken) support for must-staple, many clients fail open and simply ignore must-staple when present in absence of a stapled response. At this point it seems like we should just be moving to certificates with the same lifespan as an OCSP response has today, and be done with all of this unnecessary complexity.

For Go, I don't think implementing robust OCSP support at this point really makes sense. I am personally not planning on working on this proposal anymore in the future. There are more pain points that adding it introduces than pain points that introducing it solves, and it feels as if the industry is moving towards something else (although it's not clear what that is, if not shorter certificate lifespans).

Adding better tooling for fast and memory efficient CRL parsing seems plausible, and could be relatively easily integrated in the current chain building logic. If we are going to implement any revocation detection logic into crypto/x509, I think that would be it.

Avamander commented 4 months ago

With shortening certificate lifespans, stapling in particular feels like a waste of time.

Recent CA/B Forum ballots for shortening lifetimes haven't exactly passed with overwhelming majority. Isn't it a bit premature to discard OCSP before this actually succeeds? (You might have seen me mention this in other threads on this topic as well.)

aarongable commented 4 months ago

Recent CA/B Forum ballots for shortening lifetimes haven't exactly passed with overwhelming majority.

This is a weird point to debate, but I truly don't know what you mean by this. Ballot SC-063 v4 (the ballot which enshrined short-lived certs, made CRLs mandatory, and made OCSP optional) passed with 31 Yes votes and only 1 No. That's the third highest participation and the third most Yes votes of all CA/BF Server Certificate Working Groups ballots from SC-50 (November 2021) through SC-75 (June 2024), the only higher ballots being SC-62v2 (Certificate Profiles) and SC-70 (Clarifying DTPs). If you want to characterize this ballot as not having an overwhelming majority, then you're going to have to characterize all CA/BF ballots that way, and I'm not sure what point that makes.

Avamander commented 4 months ago

@aarongable

This is a weird point to debate, but I truly don't know what you mean by this

Maybe I skipped a few sentences and assumed too much shared context, sorry. The commonly shared line of thought is that the industry would rather prefer to move towards shorter certificate lifespans. Indeed, in that case OCSP becomes less useful. However, max lifespan has not yet been reduced that much.

When we look at max lifetime specifically, ballot SC-22 failed with 20 No and 11 Yes votes and that was just reducing the max lifetime to a year. We only got it later on because Apple unilaterally decided so. It's really unlikely max lifetime (for the majority) will be reduced any time soon to the extent to obsolete OCSP.

While other ballots may have passed (including the ballot to enshrine short-lived certificates, require CRLs and make OCSP optional), the max lifetime is currently still too long. Long enough that OCSP will make sense for a while, for a lot of certificates.

donbowman commented 4 months ago

Although browsers are heading towards shorter, what about code-signing EV certificates? some of them use OCSP today.