Skip to content
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

Single-binary ECC (ECDSA/ECDHE) TLS support for SubstrateVM #1336

Closed
lvh opened this issue May 26, 2019 · 9 comments
Assignees

Comments

@lvh
Copy link

@lvh lvh commented May 26, 2019

#951 addresses the issue that SunEC becomes mandatory for GraalVM native-image binaries doing TLS. While this is fine for some uses of native-image, people sometimes want good TLS support, including ECC, in a single binary. My example in point for this is shipping CLI tools.

In #951, a workaround was discovered that easily disables the SunEC provider. A downside is that only DHE/RSA is supported. The purpose of this ticket is to examine how to get single binary that still supports ECDHE.

One way that this could be resolved is if native-image supported e.g. Sulong's LLVM implementation for System.loadLibrary, or native-image learned how to System.loadLibrary libraries that are actually embedded in the image. I have no idea if that's possible or realistic: but I'm not an Oracle employee so deep engineering on GraalVM itself feels a little scary :) I'm calling this approach "loading embedded dynamic libraries".

Another way to hack this together is to add libsunec.so (or .dylib or .dll) as an embedded resource, set java.library.path to a few well-known locations that are likely to be writable across platforms (presumably more than one such location), and write the dylib out at runtime (and delete it when you're done). I'm assuming the runtime impact of that is not too terrible, but the implementation. I'm calling this approach "loading libraries shipped as resources". Because the code that calls System.loadLibrary is generated by SubstrateVM, it's not clear to me if I can make that use System.load with an explicit path, or if you have to modify java.library.path.

Another way that perhaps doesn't require as much hacking on Graal internals is to use a different provider. The best (on the metric of "satisfying my proclivities as a cryptographic engineer") provider available is probably conscrypt, a provider published by Google using BoringSSL. I believe it's the default in modern Android. Unfortunately because BoringSSL itself is a C product, you end up with exactly the same problem that the SunEC provider has: a runtime dependency on a dynamic library.

While I generally don't love BouncyCastle (I am singularly convinced my dinky HTTPS-speaking CLI tool should not have a qTESLA implementation...) it is a pure-Java implementation and therefore "easy" for GraalVM to get working. For the rest of this ticket I'll be focusing on BouncyCastle.

To get replace the SunEC provider, two things need to happen:

  1. Add an override that prevents SunEC from loading in the first place with -J-Djava.security.properties=java.security.overrides, where java.security.overrides has lines like security.provider.3=something (3 is the default index of SunEC)
  2. Add the replacement provider in a static block (or just as a toplevel form of a namespace, because I'm running Clojure) using Security/addProvider or Security/insertProviderAt.

I created a Clojure program that just downloads https://www.latacora.com via a URL opener. I wrote [nscap][nscap], a tool to take a piece of code and run it in a Linux network namespace together with tcpdump, enabling capture of just the packets from 1 process. I then analyze the resulting dumps with ssldump to see what TLS conversations actually take place. (Eventually I want to do this for macOS and Windows too but I'm not sure how to capture packets from one process across platforms; suggestions welcome.) Finally I summarize the results in the following table:

Experiment name Successful download? SunEC warning? SunEC idx BC idx BCJSSE idx Best advertised handshake Client -> server records Server -> client records Runtime exception?
nooverrides+noinjects ⚠️ 3 🚫 🚫 💥 💥 💥
nooverrides+addbcprov ⚠️ 3 10 🚫 💥 💥 💥
nooverrides+addbctls ⚠️ 3 🚫 10 💥 💥 💥
nooverrides+addbcprov&addbctls ⚠️ 3 10 11 💥 💥 💥
3=bcprov+noinjects ✔️ 🚫 🚫 🚫 DHE 4 4
3=bcprov+addbcprov 🚫 9 🚫 💥 💥 💥 Caused by: java.lang.RuntimeException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: EC, provider: BC, class: org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi)
3=bcprov+addbctls ✔️ 🚫 🚫 9 DHE 2 4
3=bcprov+addbcprov&addbctls 🚫 9 10 ECDHE 1 💥 Caused by: java.lang.RuntimeException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: EC, provider: BC, class: org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi)
3=bctls+noinjects ✔️ 🚫 🚫 🚫 DHE 2 4
3=bctls+addbcprov 🚫 9 🚫 💥 💥 💥 Caused by: java.lang.RuntimeException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: EC, provider: BC, class: org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi)
3=bctls+addbctls ✔️ 🚫 🚫 9 DHE 1 4
3=bctls+addbcprov&addbctls 🚫 9 10 💥 💥 💥 Caused by: java.lang.RuntimeException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: EC, provider: BC, class: org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi)
3=bcprov&4=bctls+noinjects 🚫 3 🚫 💥 💥 💥 Caused by: java.security.NoSuchAlgorithmException: Default SSLContext not available
3=bcprov&4=bctls+addbcprov 🚫 8 🚫 💥 💥 💥 Caused by: java.security.NoSuchAlgorithmException: Default SSLContext not available
3=bcprov&4=bctls+addbctls 🚫 🚫 8 💥 💥 💥 Caused by: java.security.NoSuchAlgorithmException: Unable to invoke creator for DEFAULT: Default key/trust managers unavailable
3=bcprov&4=bctls+addbcprov&addbctls 🚫 8 9 💥 💥 💥 Caused by: java.security.NoSuchAlgorithmException: Unable to invoke creator for DEFAULT: Default key/trust managers unavailable
3=empty+noinjects 🚫 🚫 🚫 💥 💥 💥 Caused by: java.security.NoSuchAlgorithmException: Default SSLContext not available
3=empty+addbcprov 🚫 3 🚫 💥 💥 💥 Caused by: java.security.NoSuchAlgorithmException: Default SSLContext not available
3=empty+addbctls 🚫 🚫 3 💥 💥 💥 Caused by: java.security.NoSuchAlgorithmException: Unable to invoke creator for DEFAULT: Default key/trust managers unavailable
3=empty+addbcprov&addbctls 🚫 3 4 💥 💥 💥 Caused by: java.security.NoSuchAlgorithmException: Unable to invoke creator for DEFAULT: Default key/trust managers unavailable
3=nil+noinjects ✔️ 🚫 🚫 🚫 DHE 5 6
3=nil+addbcprov 🚫 9 🚫 💥 💥 💥 Caused by: java.lang.RuntimeException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: EC, provider: BC, class: org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi)
3=nil+addbctls ✔️ 🚫 🚫 9 DHE 4 4
3=nil+addbcprov&addbctls 🚫 9 10 ECDHE 3 4 Caused by: java.lang.RuntimeException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: EC, provider: BC, class: org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi)

The name column is "overrides+injects". Overrides are statements in java.security.overrides. Injects are parsed as follows: if there's an addbcprov, the code includes (Security/addProvider (BouncyCastleProvider.)), if there's an addbctls, the code includes (Security/addProvider (BouncyCastleJsseProvider.)). In all cases, both providers are available on the classpath (but presumably, if they're not being statically loaded they're not otherwise in use, and possibly elided from the final image).

"Successful download?" checks if I could find the <title> tag of the HTML page I'm downloading in the output. "SunEC warning?" shows if the runtime warns it couldn't find the SunEC library, which I'm using as a proxy to see if SunEC is being loaded at all. (SunEC|BC|BCJSSE) idx shows if a provider is in the security providers list at runtime and what index it's at. Best advertised handshake shows "ECDHE" if the client advertises ECDHE, otherwise DHE if it advertises that, otherwise nothing if only RSA suites are supported. This allows me to distinguish between "provider claims to support ECC, but doesn't actually do it" and "no provider with ECC is loaded at all". C->S/S->C records is the number of records sent back and forth (these are records in the TLS protocol sense), runtime exception is what it sounds like.

My interpretation of these results is that if I want TLS to work reliably and I don't care about ECDHE I should just disable the SunEC provider via override, which was the conclusion of #951. Effectively, the builtin TLS support is fine, so bctls is a red herring; we just need an ECC provider implementation. BC does not actually work in any of these cases, which is evidenced by the fact that the table only shows DHE successful attempts.

I think the next step is to try and debug why the native image can't create an instance of bcprov.

Things that would be useful to me:

  • Any indication from someone working on SubstrateVM that either I'm on the right path or horribly on the wrong path -- if the answer is "you should forget about this for 3 months and then SubstrateVM will just do the thing you want", that's great!
  • Anyone who wants to try the alternative approaches
  • Anyone who can help me get bcprov working :)
rmaucher added a commit to apache/tomcat that referenced this issue Jul 10, 2019
TLS doesn't really work though
(oracle/graal#951 and
oracle/graal#1336).
Also Tomcat Native fails to load with an initial JNI error.
Update some version numbers.
@nhoughto

This comment has been minimized.

Copy link

@nhoughto nhoughto commented Jul 20, 2019

Not relevant to the discussion, but I’m impressed with your level of detail and testing process 👍🏼

Amazing level of detail for anyone coming here for help. Really great contribution to people (like me!) testing out graal, thanks!

@yschimke

This comment has been minimized.

Copy link

@yschimke yschimke commented Jul 27, 2019

Have you got this test working without graal? That seems to me to be the first step. n.b. inserting the BC providers at the top of the list.

https://github.com/bcgit/bc-java/blob/master/tls/src/test/java/org/bouncycastle/jsse/provider/test/BCJSSEClientTest.java

      Security.removeProvider("SunEC")
      Security.removeProvider("SunJSSE")

      Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
      Security.insertProviderAt(BouncyCastleProvider(), 1)
      Security.removeProvider(BouncyCastleJsseProvider.PROVIDER_NAME)
      Security.insertProviderAt(BouncyCastleJsseProvider(), 2)

You shouldn't need to disable the SunEC provider, but if you do, you should presumably also disable SunJSSE at the same time when using bctls.

At this point I've got it working outside of Graal, so I'll switch to Graal next.

With BC:

BC
BCJSSE
SUN
SunRsaSign
SunEC
SunJSSE
SunJCE
SunJGSS
SunSASL
XMLDSig
SunPCSC
JdkLDAP
JdkSASL
Apple
SunPKCS11

class org.bouncycastle.jsse.provider.ProvSSLSocketWrap_9
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256

Without

SUN
SunRsaSign
SunEC
SunJSSE
SunJCE
SunJGSS
SunSASL
XMLDSig
SunPCSC
JdkLDAP
JdkSASL
Apple
SunPKCS11
class sun.security.ssl.SSLSocketImpl
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
@yschimke

This comment has been minimized.

Copy link

@yschimke yschimke commented Jul 27, 2019

Now hitting first this with Graal - #378

Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
	at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
	at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
	at java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
	at org.bouncycastle.jsse.provider.ProvX509TrustManager.<init>(Unknown Source)

Then after fixing above

java.lang.IllegalStateException: unable to create TlsCrypto: DEFAULT SecureRandom not available
	at org.bouncycastle.tls.crypto.impl.jcajce.Exceptions.illegalStateException(Unknown Source)
	at org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider.create(Unknown Source)
	at org.bouncycastle.jsse.provider.ProvSSLContextSpi.engineInit(Unknown Source)
	at javax.net.ssl.SSLContext.init(SSLContext.java:282)
	at okhttp3.OkHttpClient$Companion.newSslSocketFactory(OkHttpClient.kt:956)
	at okhttp3.OkHttpClient$Companion.access$newSslSocketFactory(OkHttpClient.kt:947)
	at okhttp3.OkHttpClient.<init>(OkHttpClient.kt:213)
	at okhttp3.OkHttpClient$Builder.build(OkHttpClient.kt:944)
Caused by: java.security.NoSuchAlgorithmException: DEFAULT SecureRandom not available
	at sun.security.jca.GetInstance.getInstance(GetInstance.java:159)
	at java.security.SecureRandom.getInstance(SecureRandom.java:288)
	... 17 more

Annoyingly, the Security feature seems really tied to SunEC - https://github.com/oracle/graal/blob/9ce4ed93a2797e922ff3a364e1f7b192984b9537/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java

But it feels close...

@lvh

This comment has been minimized.

Copy link
Author

@lvh lvh commented Jul 27, 2019

Right: you've hit why I disabled SunEC for Graal :)

@yschimke

This comment has been minimized.

Copy link

@yschimke yschimke commented Jul 27, 2019

@lvh OK, makes sense. That wasn't clear that you were also hitting it when just trying to use SecureRandom with bctls.

@yschimke

This comment has been minimized.

Copy link

@yschimke yschimke commented Jul 27, 2019

@lvh on further testing outside of Graal, I can't get bctls working with JDK8. Works fine with JDK11, just switching the execution JVM for running the test, nothing else. But I suspect the issues I'm seeing with Graal are now related to that instead. So I'm not going to spend more time getting bctls working with Graal.

square/okhttp#5309

@lvh

This comment has been minimized.

Copy link
Author

@lvh lvh commented Jul 27, 2019

Do you have a reproducer project? Most of the reason BCTLS exists is good crypto on old JDKs (and I’ve certainly used BCTLS on JDK8, at least I think?) so that’s a very surprising statement to me :)

@yschimke

This comment has been minimized.

Copy link

@yschimke yschimke commented Jul 27, 2019

@lvh I've linked above. OkHttp tries to drive the selection of which cipher suites to use. It's possible that this is what's causing the issue, negotiating JceChaCha20Poly1305 on JDK 8.

@lvh

This comment has been minimized.

Copy link
Author

@lvh lvh commented Nov 24, 2019

19.3.0 fixes this issue by introducing libsunec from the JDK whenever appropriate. Hooray!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants
You can’t perform that action at this time.