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 >>> >>> >>> >>> >>> >>