Java 11 - SSL handshake for ECDH cipher suites trigger Invalid ECDH ServerKeyExchange with non-default security provider

Xuelei Fan xuelei.fan at oracle.com
Thu Sep 20 15:07:02 UTC 2018


Hi Jaikiran,

Does it happen if using JDK crypto provider?

Thanks,
Xuelei

On 9/20/2018 6:16 AM, Jaikiran Pai wrote:
> Just checking - does this look like a genuine issue? Anything else I can
> provide to help reproduce this?
> 
> -Jaikiran
> 
> 
> On 18/09/18 7:06 PM, Jaikiran Pai wrote:
>> I have been testing some projects that I know of, with Java 11 RC.
>> There's one specific test that has been failing for me, for a while now,
>> during SSL handshake. Took me a while to narrow it down given the size
>> of the application and the nature of the exception. The exception occurs
>> during SSL handshake between a client and a server (both Java and both
>> using Java 11 RC) and it kept throwing:
>>
>> Exception in thread "main" javax.net.ssl.SSLHandshakeException: Invalid
>> ECDH ServerKeyExchange signature
>>      at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:128)
>>      at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
>>      at
>> java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:308)
>>      at
>> java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:264)
>>      at
>> java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:255)
>>      at
>> java.base/sun.security.ssl.ECDHServerKeyExchange$ECDHServerKeyExchangeMessage.<init>(ECDHServerKeyExchange.java:329)
>>      at
>> java.base/sun.security.ssl.ECDHServerKeyExchange$ECDHServerKeyExchangeConsumer.consume(ECDHServerKeyExchange.java:535)
>>      at
>> java.base/sun.security.ssl.ServerKeyExchange$ServerKeyExchangeConsumer.consume(ServerKeyExchange.java:103)
>>      at
>> java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
>>      at
>> java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444)
>>      at
>> java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
>>      at
>> java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:178)
>>      at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
>>      at
>> java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1152)
>>      at
>> java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1063)
>>      at
>> java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:402)
>>      at
>> java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:567)
>>      at
>> java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
>>      at
>> java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1581)
>>      at
>> java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1509)
>>      at
>> java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:245)
>> ...
>>
>> This is consistently reproducible if, in the scheme of things:
>>
>>   1. the cipher suite selected is a ECDHE one. For example
>> TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384. The TLS version itself is TLSv1.2
>> (both server and client).
>>
>>   2. One side of the client/server, is backed by BouncyCastle as the
>> security provider (very specifically for SignatureSpi).
>>
>> Assuming the server side is using BouncyCastle provider, what's
>> happening is that during the handshake, the server side uses the
>> RSASSA-PSS algorithm, backed by this provider and writes out the
>> signature. The client side uses SunRsaSign (default provider) and tries
>> to verify this RSASSA-PSS signature and fails with that above exception.
>> FWIW, I don't believe the signature algorithm matters as long as the
>> cipher is ECDHE backed and the client and server side use a different
>> security provider.
>>
>> All this works perfectly fine when both the sides use the default
>> provider and bouncycastle isn't involved.
>>
>> I was able to get this reproducible in a very simple server/client
>> standalone program. I think this can even be demonstrated in a jtreg
>> test but I don't have enough experience with it to see how to trigger a
>> server in a separate JVM and then use a client for testing. The
>> reproducer code (Server.java and Client.java) is at the end of this mail
>> along with instructions on how to reproduce it.
>>
>> I was also able to narrow down this issue down to a specific part of the
>> JDK code. sun.security.ssl.SignatureScheme#getSignature[1] inits the
>> Signature instance for either signing or verifying. However it sets up
>> the signature parameters _after_ the init is done and in fact, there's
>> an explicit note[2] stating what/why it's doing it there. I admit, I
>> don't have much knowledge of the Java SSL parts and none in these
>> internal details and don't understand the details of that implementation
>> notes. However, just to try it out, I switched the order of it by using
>> this local patch:
>>
>> diff -r fbb71a7edc1a
>> src/java.base/share/classes/sun/security/ssl/SignatureScheme.java
>> ---
>> a/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java
>> Sat Aug 25 20:16:43 2018 +0530
>> +++
>> b/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java
>> Tue Sep 18 18:47:52 2018 +0530
>> @@ -467,18 +467,16 @@
>>           }
>>   
>>           Signature signer = JsseJce.getSignature(algorithm);
>> +        if (signAlgParameter != null) {
>> +            signer.setParameter(signAlgParameter);
>> +        }
>> +
>>           if (key instanceof PublicKey) {
>>               signer.initVerify((PublicKey)(key));
>>           } else {
>>               signer.initSign((PrivateKey)key);
>>           }
>>   
>> -        // Important note:  Please don't set the parameters before
>> signature
>> -        // or verification initialization, so that the crypto provider can
>> -        // be selected properly.
>> -        if (signAlgParameter != null) {
>> -            signer.setParameter(signAlgParameter);
>> -        }
>>   
>>           return signer;
>>       }
>>
>> Built this version and gave it a try with the sample code below (and
>> also against the actual application). Both worked fine. I tried cases:
>>
>>   - where one side had default provider and other side had bouncycastle.
>>
>>   - where both sides were default provider
>>
>>
>> Any thoughts on this issue? Here's the code to reproduce it:
>>
>> Server.java
>>
>> -----------
>>
>> import java.io.*;
>> import java.net.*;
>> import javax.net.ssl.*;
>> import java.util.*;
>> import java.util.concurrent.*;
>> import java.security.*;
>> import com.sun.net.httpserver.*;
>> import java.security.cert.*;
>>
>>
>> public class Server {
>>      private static final String keyFilename = "keystore";
>>      private static final String keystorePass = "passphrase";
>>      private static final String WEB_APP_CONTEXT = "/test";
>>
>>      public static void main(final String[] args) throws Exception {
>>          if (args.length == 1 && args[0].equals("--use-bouncy-castle")) {
>>              // enable bouncycastle
>>              Security.insertProviderAt(new
>> org.bouncycastle.jce.provider.BouncyCastleProvider(), 2);
>>              System.out.println("Using bouncycastle provider");
>>          } else {
>>              System.out.println("Using JRE security provider");
>>          }
>>
>>          final int port = 12345;
>>          // start the server
>>          final HttpsServer server = startServer("localhost", port);
>>          // stop server on shutdown
>>          Runtime.getRuntime().addShutdownHook(new Thread(() -> {
>>              if (server != null) {
>>                  server.stop(0);
>>                  System.out.println("Stopped server");
>>              }
>>          }));
>>      }
>>
>>      private static HttpsServer startServer(final String host, final int
>> port) throws Exception {
>>          final HttpsServer server = HttpsServer.create(new
>> InetSocketAddress(host, port), 0);
>>          final Thread t = new Thread(() -> {
>>              try {
>>                  final SSLContext sslctx = SSLContext.getInstance("TLSv1.2");
>>                  final KeyManagerFactory kmf =
>> KeyManagerFactory.getInstance("SunX509");
>>                  final KeyStore ks = KeyStore.getInstance("JKS");
>>                  try (final FileInputStream fis = new
>> FileInputStream(keyFilename)) {
>>                      ks.load(fis, keystorePass.toCharArray());
>>                  }
>>                  kmf.init(ks, keystorePass.toCharArray());
>>                  sslctx.init(kmf.getKeyManagers(), null, null);
>>                  final SSLParameters sslParameters =
>> sslctx.getDefaultSSLParameters();
>>                  sslParameters.setProtocols(new String[] { "TLSv1.2" });
>>                  // use ECDHE specific ciphersuite
>>                  sslParameters.setCipherSuites(new String[] {
>> "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"});
>>                  server.setHttpsConfigurator(new HttpsConfigurator(sslctx) {
>>                      @Override
>>                      public void configure(final HttpsParameters params) {
>>                          params.setSSLParameters(sslParameters);
>>                      }
>>                  });
>>                  server.createContext(WEB_APP_CONTEXT, (exchange)->
>> exchange.sendResponseHeaders(200, -1));
>>                  server.start();
>>                  System.out.println("Started server at " +
>> server.getAddress());
>>              } catch(Exception e) {
>>                  throw new RuntimeException(e);
>>              }
>>          });
>>          t.start();
>>          return server;
>>      }
>> }
>>
>> To run this:
>>
>> (you'll need bouncycastle jar in your classpath. you can get it from[3])
>>
>> java -cp bcprov-jdk15on-1.58.jar Server.java --use-bouncy-castle
>>
>> You should see output like:
>>
>> Using bouncycastle provider
>> Started server at /127.0.0.1:12345
>>
>> (not passing --use-bouncy-castle will start the server with the regular
>> default JRE provided security provider).
>>
>> The server is now up and running and ready to accept the request. See
>> how to run the client below. This server code uses a keystore file which
>> is part of the OpenJDK repo and can be obtained from [4] and stored in
>> the current working directory.
>>
>> Client.java
>>
>> ------------
>>
>> import java.io.*;
>> import java.net.*;
>> import javax.net.ssl.*;
>> import java.util.*;
>> import java.util.concurrent.*;
>> import java.security.*;
>> import com.sun.net.httpserver.*;
>> import java.security.cert.*;
>>
>>
>> public class Client {
>>      private static final String WEB_APP_CONTEXT = "/test";
>>
>>      public static void main(final String[] args) throws Exception {
>>          HttpsURLConnection.setDefaultHostnameVerifier((h, s) -> {return
>> true;});
>>         
>>          final int port = 12345;
>>          final URL targetURL = new URL("https://localhost:" + port +
>> WEB_APP_CONTEXT);
>>          final HttpsURLConnection conn = (HttpsURLConnection)
>> targetURL.openConnection();
>>         
>>          // use a SSLSocketFactory which "trusts all"
>>          final SSLContext sslctx = SSLContext.getInstance("TLSv1.2");
>>          sslctx.init(null, new TrustManager[] {new TrustAll()}, null);
>>          conn.setSSLSocketFactory(sslctx.getSocketFactory());
>>
>>          // read
>>          try (final InputStream is = conn.getInputStream()) {
>>              is.read();
>>          }
>>          System.out.println("Received status code " +
>> conn.getResponseCode());
>>      }
>>
>>      private static class TrustAll implements X509TrustManager {
>>
>>          @Override
>>          public void checkClientTrusted(X509Certificate[] chain, String
>> authType) throws CertificateException {
>>              return;
>>          }
>>
>>          @Override
>>          public void checkServerTrusted(X509Certificate[] chain, String
>> authType) throws CertificateException {
>>              return;
>>          }
>>
>>          @Override
>>          public X509Certificate[] getAcceptedIssuers() {
>>              return new X509Certificate[0];
>>          }
>>      }
>> }
>>
>> To run the client:
>>
>> java Client.java
>>
>> A successful execution will show:
>>
>> Received status code 200
>>
>> whereas a failed execution should throw the exception shown previously
>> in the mail.
>>
>> [1]
>> http://hg.openjdk.java.net/jdk/jdk/file/fbec908e2783/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java#l463
>>
>> [2]
>> http://hg.openjdk.java.net/jdk/jdk/file/fbec908e2783/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java#l476
>>
>> [3]
>> http://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.58/bcprov-jdk15on-1.58.jar
>>
>> [4]
>> http://hg.openjdk.java.net/jdk/jdk/file/fbec908e2783/test/jdk/javax/net/ssl/etc/keystore
>>
>> -Jaikiran
>>
>>
>>
>>
>>
> 


More information about the security-dev mailing list