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:35:56 UTC 2018


Thanks for the quick reply, Jaikiran!

Per your diff code, it sounds like a crypto provider implementation 
bugs.  JDK is using a lazy initialization so that the right provider get 
used.  Third party's provider may not do this way.   Would you please 
help to verify if the parameters get used in BouncyCastle if it is set 
after the init method?

Thanks,
Xuelei

On 9/20/2018 8:22 AM, Jaikiran Pai wrote:
> Hello Xuelei,
> 
> It doesn't happen if both the server side and client side use the JDK
> crypto provider. However if any one side uses a different crypto
> provider (bouncycastle in this case) then it throws this exception.
> 
> -Jaikiran
> 
> 
> On 20/09/18 8:37 PM, Xuelei Fan wrote:
>> 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