JDK-8220723 : SSLSession.getPeerCertificates() throws SSLPeerUnverifiedException if a pre_shared_key was used
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.net.ssl
  • Affected Version: 11.0.2
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: os_x
  • CPU: x86
  • Submitted: 2019-03-11
  • Updated: 2019-03-15
  • Resolved: 2019-03-15
Related Reports
Duplicate :  
Description
ADDITIONAL SYSTEM INFORMATION :
jdk-11.0.2

A DESCRIPTION OF THE PROBLEM :
The pre_shared_key extension is enabled by default in Java 11. This is great!

But it means that new TLS connections may lack peer certificates, even though such certificates were available in previous releases. A good fix would be to provide the peer certificates from the previous handshake.

REGRESSION : Last worked in version 8u202

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Use one SSLContext to make two TLS connections to the same TLS server. If configured correctly the 2nd connection will use the pre_shared_key extension (visible with -Djavax.net.debug=all). This improves the number of roundtrips on the connection but it prevents the peer certificates from being available.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Calling `SSLSession.getPeerCertificates()` returns the certificates that were originally used to authenticate the remote server.
ACTUAL -
Calling SSLSession.getPeerCertificates() throws a SSLPeerUnverifiedException.

Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
	at java.base/sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:526)
	at PreSharedKeyTest.clientHandshake(PreSharedKeyTest.java:89)
	at PreSharedKeyTest.runClient(PreSharedKeyTest.java:79)
	at PreSharedKeyTest.main(PreSharedKeyTest.java:41)


---------- BEGIN SOURCE ----------
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Collection;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

/**
 * This test demonstrates that {@link SSLSession#getPeerCertificates} throws on Java 11 when a
 * handshake was negotiated with the pre_shared_key extension.
 *
 * <p>This test completes normally on Java 8 but crashes on Java 11.
 */
public final class PreSharedKeyTest {
  private char[] password = "password".toCharArray(); // Any password will work.

  public static void main(String[] args) throws Exception {
    PreSharedKeyTest preSharedKeyTest = new PreSharedKeyTest();
    int port = preSharedKeyTest.runServer();
    preSharedKeyTest.runClient(port);
  }

  int runServer() throws Exception {
    SSLSocketFactory sslSocketFactory = newSslSocketFactory(true);
    ServerSocket serverSocket = new ServerSocket();
    serverSocket.setReuseAddress(true);
    serverSocket.bind(new InetSocketAddress("localhost", 0), 50);

    new Thread("server") {
      @Override public void run() {
        try {
          for (int i = 0; i < 2; i++) {
            serverHandshake(sslSocketFactory, serverSocket);
          }
          serverSocket.close();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }.start();

    return serverSocket.getLocalPort();
  }

  void serverHandshake(
      SSLSocketFactory sslSocketFactory, ServerSocket serverSocket) throws Exception {
    try (Socket rawSocket = serverSocket.accept();
         SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
             rawSocket, null, true)) {
      sslSocket.setUseClientMode(false);
      sslSocket.startHandshake();
    }
  }

  void runClient(int port) throws Exception {
    SSLSocketFactory sslSocketFactory = newSslSocketFactory(false);
    clientHandshake(sslSocketFactory, "localhost", port);
    clientHandshake(sslSocketFactory, "localhost", port);
  }

  void clientHandshake(SSLSocketFactory sslSocketFactory, String host, int port) throws Exception {
    try (Socket rawSocket = SocketFactory.getDefault().createSocket(host, port);
         SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
             rawSocket, host, port, true)) {
      sslSocket.startHandshake();
      SSLSession session = sslSocket.getSession();
      sslSocket.getInputStream().read();
      Certificate[] peerCertificates = session.getPeerCertificates(); // Crash here on Java 11.
      System.out.println("handshake success: peer="
          + ((X509Certificate) peerCertificates[0]).getSubjectX500Principal().getName());
    }
  }

  SSLSocketFactory newSslSocketFactory(boolean server) throws Exception {
    Certificate certificate = decodePem(""
        + "-----BEGIN CERTIFICATE-----\n"
        + "MIIBMjCB2qADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMTCWxvY2FsaG9z\n"
        + "dDAgFw0xOTAzMTEwMDU3MzVaGA8yMTE5MDIxNTAwNTczNVowFDESMBAGA1UEAxMJ\n"
        + "bG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFmkkkDjWmtErc3NI\n"
        + "7WnUXDHz8TuBPHVyk1OPEexcQhIFaabFCqO6CLwwiEm3Nmcbkw2c1Y0MKQXSLyPd\n"
        + "uBF4EaMbMBkwFwYDVR0RAQH/BA0wC4IJbG9jYWxob3N0MAoGCCqGSM49BAMCA0cA\n"
        + "MEQCIC/3TRDB7PUtDo685hjNhWMrOZADvl8ZXFxTLBWoeRmwAiBVkoKiATUShtqo\n"
        + "aMx+zeozvmB//hu5LL2Ll/WbbWSWVQ==\n"
        + "-----END CERTIFICATE-----");
    X509TrustManager trustManager = newTrustManager(certificate);

    KeyManager[] keyManagers = null;
    if (server) {
      PrivateKey privateKey = decodePkcs8("MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJz"
          + "AlAgEBBCBOV/y8HlypWRB6axJz9uoevM+Oe/8IzTBW/OzCPNy04A==");
      keyManagers = new KeyManager[] { newKeyManager(certificate, privateKey) };
    }

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagers, new TrustManager[] {trustManager}, null);
    return sslContext.getSocketFactory();
  }

  Certificate decodePem(String pem) throws CertificateException {
    Collection<? extends Certificate> certificates = CertificateFactory.getInstance("X.509")
        .generateCertificates(new ByteArrayInputStream(pem.getBytes(StandardCharsets.US_ASCII)));
    return certificates.iterator().next();
  }

  PrivateKey decodePkcs8(String base64pkcs8) throws Exception {
    byte[] privateKeyBytes = Base64.getDecoder().decode(base64pkcs8);
    return KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
  }

  X509KeyManager newKeyManager(Certificate certificate, PrivateKey privateKey) throws Exception {
    KeyStore keyStore = newEmptyKeyStore();
    keyStore.setKeyEntry("private", privateKey, password, new Certificate[] {certificate});

    KeyManagerFactory factory = KeyManagerFactory.getInstance(
        KeyManagerFactory.getDefaultAlgorithm());
    factory.init(keyStore, password);
    return (X509KeyManager) factory.getKeyManagers()[0];
  }

  X509TrustManager newTrustManager(Certificate certificate) throws Exception {
    KeyStore keyStore = newEmptyKeyStore();
    keyStore.setCertificateEntry("localhost", certificate);

    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
        KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(keyStore, password);

    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
        TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(keyStore);

    return (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
  }

  KeyStore newEmptyKeyStore() throws Exception {
    try {
      KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
      keyStore.load(null, password);
      return keyStore;
    } catch (IOException e) {
      throw new AssertionError(e);
    }
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Force TLSv1.2 to prevent the pre_shared_key extension from activating.

      sslSocket.setEnabledProtocols(new String[] { "TLSv1.2" });


FREQUENCY : always



Comments
This seems to have been fixed with JDK-8212885. Following were the test results when run with different versions of JDK: JDK 8u201- Pass JDK 11.0.2 - Fail JDK 12-ea+33 - Pass JDK 13-ea+12 - Pass As this is fixed in JDK 12 which will be GA soon and backported to JDK 11.0.3, so closing this issue as duplicate.
15-03-2019