-
Notifications
You must be signed in to change notification settings - Fork 30
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
JSSEngine - Initial ALPN support #526
base: master
Are you sure you want to change the base?
Conversation
209030d
to
971ffdd
Compare
@edewata Note that ALPN support also gives us the ability to do TLS-ALPN-01 challenges with ACME if we wanted to. |
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 see my comments below.
3bfccd3
to
b159a1d
Compare
I've rebased this to drop the JDK compatibility interface. Hopefully a high enough version of the JDK has shipped everywhere we need it to for this feature. |
@edewata Looks like Fedora 32 still doesn't have ALPN support in JDK8. Thoughts? We might need to re-introduce compatibility testing. This would also affect RHEL... |
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.
Not familiar with ALPN, but it looks fine to me, just some minor comments. Feel free to merge.
public byte[] getALPNWireData() { | ||
int length = 0; | ||
for (String protocol : alpn_protocols) { | ||
length += 1 + protocol.getBytes().length; |
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.
Do we need to use UTF-8 specifically? Otherwise getBytes()
will use the system's default encoding. Or if the protocol names are ASCII anyway then it doesn't matter.
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.
Officially an ALPN protocol ID is a sequence of bytes and they are registered with IANA as such; see https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids. Therefore I recommend the JSS interface deal with protocol IDs as byte[]
the whole way through, and defer conversion to/from other types (such as String
) to the library user. This is what I did in my other ALPN PR #640.
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.
This isn't actually possible. This PR is against the JCA-standard javax.net.ssl
implementation that I've added to JSS. It resides under the org.mozilla.jss.ssl.javax
package namespace to avoid confusing it with the custom, legacy "SSLSocket" implementation under org.mozilla.jss.ssl
.
In particular, looking at the interface we're given:
- We get the output as a
String
:SSLSocket.getApplicationProtocol()
,SSLEngine.getApplicationProtocol()
- We get the "input"/protocol choices as a
String
:SSLEngine.setHandshakeApplicationProtocolSelector(...)
So we either choose our own encoding and force it throughout, or let the user control it via system default.
It looks like the JDK11 always forces UTF-8:
https://github.com/openjdk/jdk11u-dev/blob/master/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java#L100
So perhaps this will be good enough for us.
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.
It will be good enough for us. But it is seriously flawed w.r.t. how the ALPN extension and protocol ID is actually specified. Java's fault, we just have to put up with it.
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.
I mean, I don't disagree:
byte[] a = new byte[]{ (byte) 0xFA, (byte) 0xFA };
String b = new String(a, StandardCharsets.UTF_8);
byte[] c = b.getBytes(StandardCharsets.UTF_8);
System.out.println(a.length + " " + a[0] + " " +a[1]);
System.out.println(b);
System.out.println(c.length + " " + c[0] + " " + c[1]);
Outputs:
2 -6 -6
��
6 -17 -65 ...
But also.. If you and the server both negotiate GREASE'd values... What does that even mean? What protocol are you actually negotiating there? It really isn't well defined. So they just hope the rest of the (valid) IANA protocol values are otherwise valid.
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.
(I say negotiate because NSS and the protocol doesn't give the client a way to see what the server has for ALPN data priorities, and for that matter, the String comparison all happens on the server-side anyways, so we should probably coerce byte -> String and use String.equals(...)
, rather than trying to go from String -> Byte and compare byte arrays... That ensure we probably have the same encoded representation rather than dealing with differences there...)
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.
Well, there's nothing we can do about it but to descend slightly further into this rabbit hole:
- You don't negotiate GREASE (servers MUST ignore GREASE values). But if you want to send GREASE in your ClientHello, 15 of the 16 defined values are impossible to send.
- And if any other ALPN values get defined that are not the UTF-8 encoding of some string, then you can't send those either.
On your second comment, no, the string comparison is essential on the client side too: the client will usually need to check what protocol was negotiated.
As for how to compare, given the interface imposed on us I have no objection to String.equals()
.
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.
I suppose... This is why we have JSSEngine and JSSParameters after all... We could add a custom getter/setter which returns the raw byte values... Thoughts?
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.
@frasertweedale -- I've brought this up for discussion on the OpenJDK security-dev
mailing list and CC'd you. That was the approach suggested by #java
.
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.
Yes, we could implement our own methods that deal with byte[]
, if the requirement arises.
I don't think the need is there right now (I do not need to send grease or handle a non-UTF-8 protocol ID).
I'm just annoyed at such a mistake in a foundational API. So easliy avoided, if they had just read the spec and implemented what is says.
b159a1d
to
9d336c7
Compare
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.
I don't have any objection, as long as it passes CI feel free to merge it.
Is there a real application that can use this now, or do we need to
implement tls-alpn-01 ACME challenge in order to truly test it?
We have two options. I'm inclined to leave it open a little longer and implement server support. Or we could point a small client socket at something like |
And note that this is again, only client-side checking. The server does not use def SSLNextProtoCallback(caller_arg: Any, fd: NSPR.PRFileDesc<NSS.SSL>, peer_protos: List[str], max_ret_length: int) -> str:
pass and there are no other helper functions which return the server-side configured list of protocols. So there is no way for the server to take that into account, if set. Additionally, it is in the wrong format: we have to encode the list of protocols ourselves, so we'd have to re-parse that into a useful value in order to make sense of them in the callback handler. However, NSS already does this for us with the client's list of protocols. Indeed, you can see that in the gtests here: https://github.com/nss-dev/nss/blob/master/gtests/ssl_gtest/tls_connect.cc#L627 It does its own parsing of the server-provided "arg" value (which in turn is its own list of values) and uses that to determine which of the protos to return. |
I admit I haven't tested it, but let me refer to the NSS code that leads me to the opposite conclusion: As you can see, this sets the protocol list data In this way, |
Aha, that's interesting. Then there must be some other bug in this code, I'll have to take a look more closely. I was looking here: https://github.com/nss-dev/nss/blob/master/lib/ssl/ssl3exthandle.c#L325-L334 I missed that |
f181c23
to
9c3f195
Compare
@@ -347,8 +347,17 @@ private void createBufferFD() throws SSLException { | |||
throw new SSLException("Unable to enable SSL Handshake Callback on this SSLFDProxy instance."); | |||
} | |||
|
|||
// Pass this ssl_fd to the session object so that we can use | |||
// SSL methods to invalidate the session. | |||
if (alpn_protocols != null) { |
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.
@cipherboy I've tested this PR and it's almost there. We really do need this length check though.
Feel free to just apply this patch or modify the relevant commit: whichever you prefer:
commit 5e09241e923b426733c444ccb1ed9295726c3080
Author: Fraser Tweedale <[email protected]>
Date: Tue Oct 13 12:45:46 2020 +1100
ALPN: guard against empty array of protocol IDs
javax.net.ssl.SSLParameters sets default ALPN protocol IDs to empty
array. When downcasting to JSSParameters, we take the value as-is,
so we could also end up with an empty array. Therefore when
deciding whether to create the ALPN extension the non-null check is
not sufficient. Also check that the array is non-empty.
diff --git a/org/mozilla/jss/ssl/javax/JSSEngineReferenceImpl.java b/org/mozilla/jss/ssl/javax/JSSEngineReferenceImpl.java
index 93fe703c..f84ae800 100644
--- a/org/mozilla/jss/ssl/javax/JSSEngineReferenceImpl.java
+++ b/org/mozilla/jss/ssl/javax/JSSEngineReferenceImpl.java
@@ -347,7 +347,7 @@ public class JSSEngineReferenceImpl extends JSSEngine {
throw new SSLException("Unable to enable SSL Handshake Callback on this SSLFDProxy instance.");
}
- if (alpn_protocols != null) {
+ if (alpn_protocols != null && alpn_protocols.length > 0) {
byte[] wire_data = getALPNWireData();
if (wire_data == null) {
throw new RuntimeException("JSSEngine.init(): ALPN wire data is NULL but alpn_protocols is non-NULL.");
@frasertweedale If you have cycles, do you mind looking at jdk8 failure? This started recently and didn't happen on the initial code, so I'm suspicious it was something simple. If you patch the Dockerfile: diff --git a/tools/Dockerfiles/ubuntu_jdk8 b/tools/Dockerfiles/ubuntu_jdk8
index 48cc3720..64a561fe 100644
--- a/tools/Dockerfiles/ubuntu_jdk8
+++ b/tools/Dockerfiles/ubuntu_jdk8
@@ -24,11 +24,12 @@ COPY . /home/sandbox/jss
# Perform the actual build
WORKDIR /home/sandbox/jss
-CMD true \
+RUN true \
&& rm -rf build \
&& mkdir build \
&& cd build \
&& CFLAGS="-Wall -Wextra -Werror" cmake .. \
&& make all \
- && ctest --output-on-failure \
&& true
+
+CMD /bin/bash It will let you run but I don't know how to enable gdb inside a container. Perhaps I just need to build and run it as root. |
I don't have cycles to deeply investigate. I created another PR to test this one alongside the PR #642. Note the container is a based on a rolling release so maybe something changed in the base image? We could also "bisect" the commits in this PR to narrow down the change that actually triggers the failure. |
@cipherboy hm, yeah it sure is weird. Only Ubuntu, only FIPS, only when ALPN is used. I note that ubuntu "focal" is at NSS v3.49 - https://packages.ubuntu.com/source/focal/nss. But Centos 7 is at 3.44 and f31 and f32 are at 3.53 and 3.55 respectively, so the version doesn't seem to be the issue. Looking further, it is the server I'd have a look at what is enabled/disabled in FIPS mode on Ubuntu; might give a clue. |
Traceback:
|
Possibly relevant: Ubuntu patches nss to always return FALSE from NSS "is fips enabled?" subroutines: https://git.launchpad.net/ubuntu/+source/nss/tree/debian/patches/disable_fips_enabled_read.patch?id=af745d02d46dd21c52abe39093188ac6e0918df3 |
Hmmm ok. I'll try the bisect method later. Curious. :-) |
Signed-off-by: Alexander Scheel <[email protected]>
Signed-off-by: Alexander Scheel <[email protected]>
Signed-off-by: Alexander Scheel <[email protected]>
Signed-off-by: Alexander Scheel <[email protected]>
Signed-off-by: Alexander Scheel <[email protected]>
Signed-off-by: Alexander Scheel <[email protected]>
ALPN support merged in JDK9 and will eventually be backported into JDK8. However, not every consumer of JSS will necessarily upgrade to a newer JDK8 version. While support has landed upstream, Fedora 32 and Fedora 33 haven't yet seen a backport yet. This likely means that RHEL also won't see a backport. Introduce a small reflection-based compatibility layer to see if the class supports ALPN and if so, use the value from it. Signed-off-by: Alexander Scheel <[email protected]>
It looks like UTF-8 is the desired encoding for ALPN data for compatibility with what the JCA and SunJSSE does: https://github.com/openjdk/jdk11u-dev/blob/master/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java#L100 Therefore we should make sure we encode our protocols the same way. Signed-off-by: Alexander Scheel <[email protected]>
Since setting parameters via JSSParameters was already supported, technically no work is necessary to support ALPN in JSSSocket. However, we should extend JSSSocket with the new methods added to JSSEngine and implement them via calls to the underlying engine. Signed-off-by: Alexander Scheel <[email protected]>
This will let GREASE'd ALPN values be handled correctly in JSS, despite the JDK not working well. Additionally, handle NSS's quirks w.r.t. ALPN protocol preference. Signed-off-by: Alexander Scheel <[email protected]>
Signed-off-by: Alexander Scheel <[email protected]>
a6c2f8c
to
f50bf1c
Compare
This pull requests introduces support for ALPN negotiation via a fixed list of next protocol identifiers.
Technically this support was introduced in the native SSLEngine in JDK 9, but support in Tomcat exists for older versions when advertised.
ALPN is a method of negotiating the next protocol after TLS. Most of the time this will be
http/1.1
, but could beh2
orspdy/2
depending on browser and server support. Without this, Tomcat is limited tohttp/1.1
and cannot speak e.g.,http/2
.--
Also, having a ALPN-capable implementation means we can go implement TLS-ALPN-01 / RFC 8737 if we wanted to.