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