Issues with ALPN implementation in JDK 9
David M. Lloyd
david.lloyd at redhat.com
Tue Jun 14 14:31:57 UTC 2016
I have a few questions, inline.
On 06/14/2016 08:57 AM, Simone Bordet wrote:
> Hi,
>
> I gave a shot at implementing ALPN in JDK 9 in Jetty.
>
> TLDR: I could not find a way to make it work. This email is to discuss
> whether I am off road or discuss possible solutions.
>
> Below my feedback.
>
> * Lack of facilities to convert TLS protocol bytes to protocol strings.
> This class already exist in JDK, sun.security.ssl.ProtocolVersion, it
> would just need to be exposed in javax.net.ssl.
> * Lack of facilities to convert TLS cipher bytes to cipher name strings.
> As above, sun.security.ssl.CipherSuite exists, needs to be exposed publicly.
This would *definitely* be a real nice-to-have. Just having spent way
too long creating our own lookup table...
> Note that for the 2 bullets above, a recent message from Mark Reinhold
> to jdk9-dev confirmed that JDK 9 is *not yet* feature complete, so I
> hope they can be considered for inclusion.
>
> * Server-side Implementation
> I followed the guidelines reported here:
> http://mail.openjdk.java.net/pipermail/security-dev/2015-December/013132.html,
> namely:
>
> 1) Read network bytes after initial connection.
> 2) Parse network bytes, expecting TLS ClientHello message.
> 3) Extract from ClientHello the TLS protocol version, the TLS ciphers,
> the ALPN protocols.
>
> At this point, I should negotiate the application protocol, and it
> must be only one.
>
> Assuming the ClientHello TLS protocol is spoken by both peers, the
> server logic can create pairs (cipher, app_proto) for each of the
> ciphers in common between client and server, and discard the ciphers
> that are not good for any protocol.
>
> At this point, among all the valid pairs, I need to choose a protocol.
> Let's make an example; the pairs are:
>
> (TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, h3)
> (TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, h2)
> (TLS_WHATEVER, http/1.1)
>
> Here "h3" is the future HTTP/3 protocol which I picked as an example
> to show the problem I will encounter.
>
> Because at this point the server logic must choose one protocol only
> (so that it can be returned in the ServerHello), it picks h3, which
> goes along with a ECDSA cipher, so:
>
> sslParams.setApplicationProtocols(new String[]{"h3"});
> sslParams.setCipherSuites(new
> String[]{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", ...);
> sslEngine.setSSLParameters(sslParams);
>
> At this point, the server logic is good to let SSLEngine do the
> unwrap(), and pass the original network bytes to unwrap().
>
> During the unwrap(), the JDK implementation picks a cipher based on
> the JDK logic.
> In particular, in my case, I had a keystore with a certificate that
> was *not* ECDSA.
Could you not use the available keys as an input into your own protocol
selection? Granted you have to know what kind of key works with what
algorithm, but if you already have a table of cipher suites, you might
as well just add it on there...
> If, in the snippet above, I set more than one cipher on the
> SSLParameters, then perhaps a weaker cipher could be negotiated that
> is not good for h3.
> Otherwise, if I set only one cipher, there are no ciphers in common
> and the TLS handshake is terminated with an error.
>
> Bottom line, no negotiation is possible with this approach.
>
> Next attempt I made was that before calling unwrap(), the server code
> opens the keystore, and verifies if the certificate is ECDSA, handles
> SNI, etc.
>
> However, this means duplicating all the JDK logic to make sure that
> the server logic *before* calling unwrap() is the same of the JDK so
> that when unwrap() is called there will be no failures.
I don't think you have to duplicate the exact logic though. It's not
really a "black box": if you know the cipher suites supported by your
available key(s) then you should have enough information to know, based
on the client cipher suites and the per-protocol suites, and the cipher
suites available in the SSL context(s), which protocols can complete.
> I don't think this is maintainable; the JDK is entitled to change the
> logic following CVEs, optimizations and what not, and each such change
> risks to break existing server code.
What kind of change do you anticipate being a breaking change? Are you
thinking of e.g. blacklisting some known-bad cipher suite or something?
(eom)
> I then tried another approach.
>
> In the server code, before calling unwrap(), I would remember the
> pairs (cipher, app_proto), but *not* calling
> SSLParameters.setApplicationProtocol().
>
> I would then call unwrap(), where the JDK would choose the cipher.
> The cipher is chosen in the NEED_TASK step of the unwrap(), so after
> the task is run, the cipher chosen by the JDK is now available to the
> server logic.
>
> At this point I called again the server logic and, given the exact
> cipher, choose the right protocol among the pairs that I have
> previously stored.
> In the example above, the JDK would have chose the RSA cipher because
> the certificate was not ECDSA, and the server logic would have chosen
> h2 as the application protocol:
>
> sslParams.setApplicationProtocol(new String[]{"h2"});
>
> Then let the unwrap()/wrap() code to finish the TLS handshake (in
> particular, generate the ServerHello).
>
> This approach has the benefit of cipher pre-selection done by the
> server logic (it will retain not the intersection of ciphers between
> client and server, but a possibly more restricted set that is valid
> for the application protocols that are supported - imagine when
> http/1.1 is not supported), coupled with JDK logic to interact with
> SNI and certificates, coupled with a "late" selection of the
> application protocol based on the cipher selected by the JDK logic.
>
> Unfortunately, it does not work.
>
> It does not work because the JDK implementation of
> SSLEngine.setSSLParameters() is (more or less):
>
> if (!handshake.started()) {
> handshaker.setApplicationProtocols(applicationProtocols);
> ...
> }
>
> By the time the task is run in the NEED_TASK step,
> handshaker.started==true, so the application protocols are not copied
> into the handshaker and are not used to generate the ServerHello.
>
> Conclusions.
>
> I could not make a reliable ALPN implementation with the current JDK 9.
> If I am off road, then that's good news, and I will be all ears for
> alternative approaches.
>
> If I am correct, I would like to discuss whether it would be possible
> to delay handshaker.started=true to a later time, so that
> SSLParameters can be changed just after the NEED_TASK step, so that
> server applications will be able to interact with the JDK for what
> pertains TLS protocols, ciphers, SNI, etc. without duplicating the
> logic.
>
> Comments welcome.
>
> Thanks !
>
--
- DML
More information about the security-dev
mailing list