New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue #3863 - Enforce use of SNI #4085
Conversation
Introduced SslContextFactory.rejectUnmatchedSNIHost (default false) so that if no SNI is sent, or SNI does not match a certificate, then the TLS handshake is aborted. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
@gregw the logic is now that if there is no SNI or the SNI does not match, we abort the TLS handshake. I don't know if it's worth to split those two cases. While seems evident that if we connect to So perhaps 2 booleans or an enum? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After discussion, I think we need to do more here... including in documentation on how to use these modes.
The issue is that we need to consider various situations with the keystore as well:
- Keystore only has only one cert, which has SNI - simple decision for connections without SNI - match on cypher or fail with no SNI.
- Keystore has multiple certs all with SNI and no other certs - I think we must always fail a connection with no SNI in this case
- Keystore has multiple certs, sone with SNI and some without - A connection without SNI should only match certificates without SNI
So perhaps a single boolean is enough... but the name might not be right?
@gregw you got it incorrect. Client sends SNI and it's a single string. Server certs may have a CN (Common Name) and possibly SANs (Subject Alternative Names). So we have a table with clients with 3 states: no SNI, a matching SNI and a non-matching SNI. And the server has states depending on the keystore: 1 exact SAN, 1 wild SAN, N exact SAN, N wild SAN + M exact SAN. Let's build the table and see how many cases we have. |
@sbordet I'm not sure that it matters if a cert match is a CN, SAN or wild-SAN match - that counts as a name match. I'm just saying that we need to simplify and consider just if we have a name match or not - regardless of the type of name match. So firstly let's work out what information we have. I think it is correct that when we call
We then get a set of aliases that have matching cyphers, which I guess we could break into those 3 sets: those with no-SAN, those with matching SAN and those without matching SAN. So can we break it does like this:
So we only need to consider configuration for 1.c and maybe 3.a. The current proposed option is called Or we could have two booleans? |
@gregw I don't understand what you mean by "noSAN cert". Every cert has a SAN (either only CN or CN+SAN).
So we have 2 cases: So perhaps we have one 3 state enum (REJECT_TLS, REJECT_HTTP, ALLOW), and 2 configurations (missingSNI, unmatchedSNI). |
@sbordet I was thinking that we should consider certificates with CN but no SAN differently to ones with CN and SAN... but thinking about that now, I see no real reason to do so. So reducing the choices we need to consider even more.... we need to work out what to do when we have no SNI match. We have no SNI match in two cases: 1. there was no SNI sent ; 2. there was an SNI sent, but it didn't match. For case 1, I can see reason to not reject, but the question is which certificates should we allow a match with: Any? ; only ones without SAN?; only ones that are somehow otherwise tagged as default certificates? For case 2. I really don't see a good reason for ever not rejecting... perhaps other than debugging?? But perhaps we can have a configuration of Perhaps the configuration should not be a boolean? Perhaps we need to configure a set of aliases that are permissible to use for no SNI provided and for an SNI mismatch? So lets say we configure with:
We would then get
Now this handling kind of assumes that the request inside a connection will actually have a host header that matches. We do have a |
@gregw we need to hangout about this to find the common ground. I don't think we need to add more alias configurations to This leaves us with 2 booleans: Alternatively, we need an Enum for each of those properties, where the Enum is: enum RejectType { NO_REJECT, REJECT_TLS, REJECT_HTTP } and the properties are now not booleans by |
@sbordet Sure let's hangout. |
Updates after review. Introduced SslContextFactory.SNISelector to allow application to write their custom logic to select a certificate based on SNI information. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost a LGTM. Mostly just comments and a few style suggestions for clarity.
jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SniX509ExtendedKeyManager.java
Outdated
Show resolved
Hide resolved
jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
Outdated
Show resolved
Hide resolved
jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SniX509ExtendedKeyManager.java
Outdated
Show resolved
Hide resolved
jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SniX509ExtendedKeyManager.java
Outdated
Show resolved
Hide resolved
jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
Outdated
Show resolved
Hide resolved
@gregw after our review I'm still not satisfied and many tests fail out of the box, so while the tests may have assumptions and the keystores are old and not really production-precise, I guess it may break people. Old behaviorAt The logic for choosing the server alias was:
Returning This logic was also paired with the A call with no SNI would have had no X509, would have returned New behaviorWe still have the wrapping of key managers, but now it's not really needed because we don't delegate anymore. The logic for choosing the server alias was:
The difference is that now Handling of multiple calls from the JDKThere is no way to know whether the call to Now we have I see 2 ways:
The difficult case would be a keystore with an EC certificate for @gregw other thoughts? |
@sbordet good analysis. I think that we have to assume that the JVM will call us in some priority order - probably the clients preferred order, so the intention of the API is that we return an alias as soon as there is an acceptable certificate. The problem in our heads is the what-if we are called with in a way that a no-SNI default weak alias can be returned, but that if we didn't then we would be called again with params that can match strong SNI alias. I think we can't worry about that... not least because I don't think that technically there is anything we can do, but mostly because if the API calls us in an order then that is the order and the first acceptable certificate will win. So I think:
|
Added two sniRequired fields - one at SslContextLevel and the other at the SecureRequestCustomizer. This allows rejection either at TLS handshake or by 400 response. Signed-off-by: Greg Wilkins <gregw@webtide.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes look plausible, I have a few coding quibbles, but generally fine.
jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
Show resolved
Hide resolved
cleanups from review Signed-off-by: Greg Wilkins <gregw@webtide.com>
improved comments Signed-off-by: Greg Wilkins <gregw@webtide.com>
syntax sugar Signed-off-by: Greg Wilkins <gregw@webtide.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a test for SniSelector
that tests this: rejects no SNI at TLS and rejects existing wrong SNI with 400.
Possibly the custom SniSelector
should call the default implementation in SslContextFactory
, don't copy/paste.
Introduced SslContextFactory.rejectUnmatchedSNIHost (default false)
so that if no SNI is sent, or SNI does not match a certificate,
then the TLS handshake is aborted.
Signed-off-by: Simone Bordet simone.bordet@gmail.com