JDK-8216039 : TLS with BC and RSASSA-PSS breaks ECDHServerKeyExchange
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.net.ssl
  • Affected Version: 11.0.1,12
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: linux
  • CPU: x86
  • Submitted: 2018-12-21
  • Updated: 2020-06-09
  • Resolved: 2019-04-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 13 JDK 8 Other
11.0.5-oracleFixed 13 b16Fixed 8u251Fixed openjdk8u252Fixed
Related Reports
Duplicate :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
OS: any

$ java -version
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)

A DESCRIPTION OF THE PROBLEM :
The method SignatureScheme.getSignature() breaks third-party providers like Bouncy Castle (BC) because the order of invoking 
signer.initSign()
and
signer.setParameter()
is not properly aligned. The parameters are set AFTER the actual initialization was called.

This causes TLS connection with RSASSA-PSS signature schemes to fail. In contrast RSA-PKCS1 works fine, because no additional parameters are set (signAlgParameter is null).

The setParameter() call must be invoked before the actual initSign() or initVerify() initialization (SignatureScheme.java, line 480).

References:
[1] http://mail.openjdk.java.net/pipermail/security-dev/2018-September/018265.html
[2] http://bouncy-castle.1462172.n4.nabble.com/JDK11-amp-BC-amp-TLS-with-RSASSA-PSS-signature-tt4659518.html

REGRESSION : Last worked in version 10.0.2

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run an SSL server and connect with Chrome or sample client

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Successful ECDH key exchange (as it does with --use-sun)
ACTUAL -
javax.net.ssl.SSLHandshakeException: Invalid ECDH ServerKeyExchange signature

---------- BEGIN SOURCE ----------
Server.java:

import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;

import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;


public class Server {
  private static final String keystorePass = "changeit";
  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-sun")) {
      System.out.println("Using SunRsaSign security provider");
    } else {
      // enable bouncycastle
      Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(), 2);
      System.out.println("Using bouncycastle provider");
    }

    startServer("localhost", 443);
  }

  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");

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        Certificate[] chain = {generateCertificate("cn=Unknown", keyPair, "SHA256withRSA")};
        ks.load(null, null);
        ks.setKeyEntry("main", keyPair.getPrivate(), keystorePass.toCharArray(), chain);

        kmf.init(ks, keystorePass.toCharArray());

        sslctx.init(kmf.getKeyManagers(), null, new SecureRandom());
        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;
  }

  public static X509Certificate generateCertificate(String dn, KeyPair keyPair, String algorithm) throws CertificateException {
    try {
      AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(algorithm);
      AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
      AsymmetricKeyParameter privateKeyAsymKeyParam = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
      SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
      ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privateKeyAsymKeyParam);
      Date from = new Date();
      Date to = new Date(from.getTime() + 365 * 86400000L);
      X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(new X500Name(dn), new BigInteger("111"), from, to, new X500Name(dn), subPubKeyInfo);
      X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
      return new JcaX509CertificateConverter().getCertificate(certificateHolder);
    } catch (CertificateException ce) {
      throw ce;
    } catch (Exception e) {
      throw new CertificateException(e);
    }
  }

}


Client.java

import java.io.InputStream;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;


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 = 443;
        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];
    }
  }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
disable RSASSA-PSS signature schemes and fallback to RSA-PKCS1

FREQUENCY : always



Comments
Fix request (jdk11u): Requesting backport of this item to JDK 11 updates to keep paritiy with Oracle 11.0.5. JDK-8215694 is a prerequisite for this change. Furthermore, because a backport of JDK-8211122 wasn't approved, this patch has to be edited to fit into jdk11u. I posted the changes and started a review discussion: https://mail.openjdk.java.net/pipermail/jdk-updates-dev/2019-July/001403.html Tests pass at SAP.
04-07-2019

Verified by running the attached test with jdk13+27. Note that this test depends on bcpkix-jdk15on-1.58.jar [1] and bcprov-ext-jdk15on-1.58.jar [2]. And it would be better to change the port, exactly 443, to another one, like 9443. Because 443 may be not permitted or be occupied. [1] https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.58/bcpkix-jdk15on-1.58.jar [2] https://repo1.maven.org/maven2/org/bouncycastle/bcprov-ext-jdk15on/1.58/bcprov-ext-jdk15on-1.58.jar
01-07-2019

Deferral request approved.
24-01-2019

Deferral Request RSASSA-PSS signature impl from BouncyCastle provider does not function as expected when setParameters() is called after initSign/initVerify(). Users of BouncyCastle providers have observed interoperability issues as a result. There are several ways of fixing this, but each comes with their pros and cons. Due to the limited time left (RPD2 for JDK 12) and scope (RSASSA-PSS signature of BouncyCastle provider), we want to defer this to 13. Note that this issue does not cause any regression test failure, nor TCK test failures.
23-01-2019

Adding a link to JDK-8146293 as this was the issue that changed the code that caused this issue for BouncyCastle.
23-01-2019

java.security.Signature class did not mandate the call ordering of setParameter() must proceed initSign/initVerify(). The current comments about calling initSign/initVerify() first is due to the JCA delayed provider selection functionality. For example, opaque un-extractable hardware keys would require using the corresponding hardware (through PKCS11 provider). Calling setParameter() before initSign/initVerify() would bind the Signature object to the first provider which can process the supplied parameters instead of the only provider which the key requires. Changing to call setParameter() first leads to regression test failure on Windows as SunMSCAPI provider supports PSS too. Guess we will have to add some not-so-pretty workaround for this.
18-01-2019

To reproduce the issue, run the attached test case. When using the bouncycastle provider, here are the results: JDK 10.0.2 - Pass JDK 11.0.1- Fail JDK 12-ea+21 - Fail 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:448) at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:425) 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:1151) at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1062) 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:187) 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:246) at jI9058642.Client.main(Client.java:30)
03-01-2019