Sure, I'll run some tests tomorrow and get more details on whether/how
bouncycastle uses these parameters if they are set after the init
verify/sign calls.

-Jaikiran


On 20/09/18 9:05 PM, Xuelei Fan wrote:
> 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
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>
>>

Reply via email to