8311644: Server should not send bad_certificate alert when the client does not send any certificates

Reviewed-by: djelinski, jjiang, ssahoo
This commit is contained in:
Anthony Scarpino 2024-02-26 16:49:01 +00:00
parent 9a9cfbe0ba
commit f62b5789ad
8 changed files with 246 additions and 79 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -123,13 +123,13 @@ enum Alert {
} }
if (cause instanceof IOException) { if (cause instanceof IOException) {
return new SSLException(reason, cause); return new SSLException("(" + description + ") " + reason, cause);
} else if ((this == UNEXPECTED_MESSAGE)) { } else if ((this == UNEXPECTED_MESSAGE)) {
return new SSLProtocolException(reason, cause); return new SSLProtocolException("(" + description + ") " + reason, cause);
} else if (handshakeOnly) { } else if (handshakeOnly) {
return new SSLHandshakeException(reason, cause); return new SSLHandshakeException("(" + description + ") " + reason, cause);
} else { } else {
return new SSLException(reason, cause); return new SSLException("(" + description + ") " + reason, cause);
} }
} }

View file

@ -385,7 +385,7 @@ final class CertificateMessage {
if (shc.sslConfig.clientAuthType != if (shc.sslConfig.clientAuthType !=
ClientAuthType.CLIENT_AUTH_REQUESTED) { ClientAuthType.CLIENT_AUTH_REQUESTED) {
// unexpected or require client authentication // unexpected or require client authentication
throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
"Empty client certificate chain"); "Empty client certificate chain");
} else { } else {
return; return;
@ -1162,7 +1162,7 @@ final class CertificateMessage {
shc.handshakeConsumers.remove( shc.handshakeConsumers.remove(
SSLHandshake.CERTIFICATE_VERIFY.id); SSLHandshake.CERTIFICATE_VERIFY.id);
if (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED) { if (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED) {
throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, throw shc.conContext.fatal(Alert.CERTIFICATE_REQUIRED,
"Empty client certificate chain"); "Empty client certificate chain");
} else { } else {
// optional client authentication // optional client authentication
@ -1186,7 +1186,7 @@ final class CertificateMessage {
T13CertificateMessage certificateMessage )throws IOException { T13CertificateMessage certificateMessage )throws IOException {
if (certificateMessage.certEntries == null || if (certificateMessage.certEntries == null ||
certificateMessage.certEntries.isEmpty()) { certificateMessage.certEntries.isEmpty()) {
throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, throw chc.conContext.fatal(Alert.DECODE_ERROR,
"Empty server certificate chain"); "Empty server certificate chain");
} }

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @library /javax/net/ssl/templates
* @bug 8311644
* @summary Verify CertificateMessage alerts are correct to the TLS specs
* @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 CertMsgCheck handshake_failure
* @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 CertMsgCheck certificate_required
*
*/
public class CertMsgCheck {
public static void main(String[] args) throws Exception {
// Start server
TLSBase.Server server = new TLSBase.ServerBuilder().setClientAuth(true).
build();
// Initial client session
TLSBase.Client client1 = new TLSBase.Client(true, false);
if (server.getSession(client1).getSessionContext() == null) {
for (Exception e : server.getExceptionList()) {
System.out.println("Looking at " + e.getClass() + " " +
e.getMessage());
if (e.getMessage().contains(args[0])) {
System.out.println("Found correct exception: " + args[0] +
" in " + e.getMessage());
return;
} else {
System.out.println("No \"" + args[0] + "\" found.");
}
}
throw new Exception("Failed to find expected alert: " + args[0]);
}
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -36,15 +36,11 @@
*/ */
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import java.util.HexFormat;
public class CheckSessionContext { public class CheckSessionContext {
static void toHex(byte[] id) { static HexFormat hex = HexFormat.of();
for (byte b : id) {
System.out.printf("%02X ", b);
}
System.out.println();
}
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
TLSBase.Server server = new TLSBase.Server(); TLSBase.Server server = new TLSBase.Server();
@ -52,20 +48,18 @@ public class CheckSessionContext {
// Initial client session // Initial client session
TLSBase.Client client1 = new TLSBase.Client(); TLSBase.Client client1 = new TLSBase.Client();
if (server.getSession(client1).getSessionContext() == null) { if (server.getSession(client1).getSessionContext() == null) {
throw new Exception("Context was null"); throw new Exception("Context was null. Handshake failure.");
} else { } else {
System.out.println("Context was found"); System.out.println("Context was found");
} }
SSLSession ss = server.getSession(client1); SSLSession ss = server.getSession(client1);
System.out.println(ss); System.out.println(ss);
byte[] id = ss.getId(); byte[] id = ss.getId();
System.out.print("id = "); System.out.println("id = " + hex.formatHex(id));
toHex(id);
System.out.println("ss.getSessionContext().getSession(id) = " + ss.getSessionContext().getSession(id)); System.out.println("ss.getSessionContext().getSession(id) = " + ss.getSessionContext().getSession(id));
if (ss.getSessionContext().getSession(id) != null) { if (ss.getSessionContext().getSession(id) != null) {
id = ss.getSessionContext().getSession(id).getId(); id = ss.getSessionContext().getSession(id).getId();
System.out.print("id = "); System.out.println("id = " + hex.formatHex(id));
toHex(id);
} }
server.close(client1); server.close(client1);
client1.close(); client1.close();

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -21,18 +21,15 @@
* questions. * questions.
*/ */
import javax.net.ssl.SSLContext; import javax.net.ssl.*;
import javax.net.ssl.SSLServerSocket; import java.io.*;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
@ -79,30 +76,65 @@ abstract public class TLSBase {
} }
// Base read operation // Base read operation
byte[] read(SSLSocket sock) { byte[] read(SSLSocket sock) throws Exception {
try {
BufferedReader reader = new BufferedReader( BufferedReader reader = new BufferedReader(
new InputStreamReader(sock.getInputStream())); new InputStreamReader(sock.getInputStream()));
String s = reader.readLine(); String s = reader.readLine();
System.err.println("(read) " + name + ": " + s); System.err.println("(read) " + name + ": " + s);
return s.getBytes(); return s.getBytes();
} catch (Exception e) {
e.printStackTrace();
}
return null;
} }
// Base write operation // Base write operation
public void write(SSLSocket sock, byte[] data) { public void write(SSLSocket sock, byte[] data) throws Exception {
try {
PrintWriter out = new PrintWriter( PrintWriter out = new PrintWriter(
new OutputStreamWriter(sock.getOutputStream())); new OutputStreamWriter(sock.getOutputStream()));
out.println(new String(data)); out.println(new String(data));
out.flush(); out.flush();
System.err.println("(write)" + name + ": " + new String(data)); System.err.println("(write)" + name + ": " + new String(data));
} catch (Exception e) {
e.printStackTrace();
} }
private static KeyManager[] getKeyManager(boolean empty) throws Exception {
FileInputStream fis = null;
if (!empty) {
fis = new FileInputStream(System.getProperty("test.src", "./") + "/" + pathToStores +
"/" + keyStoreFile);
}
// Load the keystore
char[] pwd = passwd.toCharArray();
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(fis, pwd);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(ks, pwd);
return kmf.getKeyManagers();
}
private static TrustManager[] getTrustManager(boolean empty) throws Exception {
FileInputStream fis = null;
if (!empty) {
fis = new FileInputStream(System.getProperty("test.src", "./") + "/" + pathToStores +
"/" + trustStoreFile);
}
// Load the keystore
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(fis, passwd.toCharArray());
PKIXBuilderParameters pkixParams =
new PKIXBuilderParameters(ks, new X509CertSelector());
// Explicitly set revocation based on the command-line
// parameters, default false
pkixParams.setRevocationEnabled(false);
// Register the PKIXParameters with the trust manager factory
ManagerFactoryParameters trustParams =
new CertPathTrustManagerParameters(pkixParams);
// Create the Trust Manager Factory using the PKIX variant
// and initialize it with the parameters configured above
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(trustParams);
return tmf.getTrustManagers();
} }
/** /**
@ -117,14 +149,63 @@ abstract public class TLSBase {
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
boolean exit = false; boolean exit = false;
Thread t; Thread t;
List<Exception> exceptionList = new ArrayList<>();
Server() { Server(ServerBuilder builder) {
super(); super();
name = "server"; name = "server";
try { try {
sslContext = SSLContext.getDefault(); sslContext = SSLContext.getInstance("TLS");
sslContext.init(TLSBase.getKeyManager(builder.km), TLSBase.getTrustManager(builder.tm), null);
fac = sslContext.getServerSocketFactory(); fac = sslContext.getServerSocketFactory();
ssock = (SSLServerSocket) fac.createServerSocket(0); ssock = (SSLServerSocket) fac.createServerSocket(0);
ssock.setNeedClientAuth(builder.clientauth);
serverPort = ssock.getLocalPort();
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
}
// Thread to allow multiple clients to connect
t = new Thread(() -> {
try {
while (true) {
System.err.println("Server ready on port " +
serverPort);
SSLSocket c = (SSLSocket)ssock.accept();
clientMap.put(c.getPort(), c);
try {
write(c, read(c));
} catch (Exception e) {
e.printStackTrace();
exceptionList.add(e);
}
}
} catch (Exception ex) {
System.err.println("Server Down");
ex.printStackTrace();
}
});
t.start();
}
Server() {
this(new ServerBuilder());
}
/**
* @param km - true for an empty key manager
* @param tm - true for an empty trust manager
*/
Server(boolean km, boolean tm) {
super();
name = "server";
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(TLSBase.getKeyManager(km), TLSBase.getTrustManager(tm), null);
fac = sslContext.getServerSocketFactory();
ssock = (SSLServerSocket) fac.createServerSocket(0);
ssock.setNeedClientAuth(true);
serverPort = ssock.getLocalPort(); serverPort = ssock.getLocalPort();
} catch (Exception e) { } catch (Exception e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
@ -166,7 +247,7 @@ abstract public class TLSBase {
} }
// Read from the client // Read from the client
byte[] read(Client client) { byte[] read(Client client) throws Exception {
SSLSocket s = clientMap.get(Integer.valueOf(client.getPort())); SSLSocket s = clientMap.get(Integer.valueOf(client.getPort()));
if (s == null) { if (s == null) {
System.err.println("No socket found, port " + client.getPort()); System.err.println("No socket found, port " + client.getPort());
@ -175,13 +256,13 @@ abstract public class TLSBase {
} }
// Write to the client // Write to the client
void write(Client client, byte[] data) { void write(Client client, byte[] data) throws Exception {
write(clientMap.get(client.getPort()), data); write(clientMap.get(client.getPort()), data);
} }
// Server writes to the client, then reads from the client. // Server writes to the client, then reads from the client.
// Return true if the read & write data match, false if not. // Return true if the read & write data match, false if not.
boolean writeRead(Client client, String s) { boolean writeRead(Client client, String s) throws Exception{
write(client, s.getBytes()); write(client, s.getBytes());
return (Arrays.compare(s.getBytes(), client.read()) == 0); return (Arrays.compare(s.getBytes(), client.read()) == 0);
} }
@ -197,30 +278,61 @@ abstract public class TLSBase {
SSLSocket s = clientMap.get(Integer.valueOf(c.getPort())); SSLSocket s = clientMap.get(Integer.valueOf(c.getPort()));
s.close(); s.close();
} }
List<Exception> getExceptionList() {
return exceptionList;
}
} }
static class ServerBuilder {
boolean km = false, tm = false, clientauth = false;
ServerBuilder setKM(boolean b) {
km = b;
return this;
}
ServerBuilder setTM(boolean b) {
tm = b;
return this;
}
ServerBuilder setClientAuth(boolean b) {
clientauth = b;
return this;
}
Server build() {
return new Server(this);
}
}
/** /**
* Client side will establish a connection from the constructor and wait. * Client side will establish a connection from the constructor and wait.
* It must be run after the Server constructor is called. * It must be run after the Server constructor is called.
*/ */
static class Client extends TLSBase { static class Client extends TLSBase {
SSLSocket sock; SSLSocket sock;
boolean km, tm;
Client() { Client() {
super(); this(false, false);
try {
sslContext = SSLContext.getDefault();
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
} }
/**
* @param km - true sets an empty key manager
* @param tm - true sets an empty trust manager
*/
Client(boolean km, boolean tm) {
super();
this.km = km;
this.tm = tm;
connect(); connect();
} }
// Connect to server. Maybe runnable in the future // Connect to server. Maybe runnable in the future
public SSLSocket connect() { public SSLSocket connect() {
try { try {
sslContext = SSLContext.getDefault(); sslContext = SSLContext.getInstance("TLS");
sslContext.init(TLSBase.getKeyManager(km), TLSBase.getTrustManager(tm), null);
sock = (SSLSocket)sslContext.getSocketFactory().createSocket(); sock = (SSLSocket)sslContext.getSocketFactory().createSocket();
sock.connect(new InetSocketAddress("localhost", serverPort)); sock.connect(new InetSocketAddress("localhost", serverPort));
System.err.println("Client connected using port " + System.err.println("Client connected using port " +
@ -235,21 +347,21 @@ abstract public class TLSBase {
} }
// Read from the client socket // Read from the client socket
byte[] read() { byte[] read() throws Exception {
return read(sock); return read(sock);
} }
// Write to the client socket // Write to the client socket
void write(byte[] data) { void write(byte[] data) throws Exception {
write(sock, data); write(sock, data);
} }
void write(String s) { void write(String s) throws Exception {
write(sock, s.getBytes()); write(sock, s.getBytes());
} }
// Client writes to the server, then reads from the server. // Client writes to the server, then reads from the server.
// Return true if the read & write data match, false if not. // Return true if the read & write data match, false if not.
boolean writeRead(Server server, String s) { boolean writeRead(Server server, String s) throws Exception {
write(s.getBytes()); write(s.getBytes());
return (Arrays.compare(s.getBytes(), server.read(this)) == 0); return (Arrays.compare(s.getBytes(), server.read(this)) == 0);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -51,7 +51,7 @@ public class LegacyDHEKeyExchange extends SSLSocketTemplate{
throw new Exception("Legacy DH keys (< 1024) should be restricted"); throw new Exception("Legacy DH keys (< 1024) should be restricted");
} catch (SSLHandshakeException she) { } catch (SSLHandshakeException she) {
String expectedExMsg = "Received fatal alert: insufficient_security"; String expectedExMsg = "Received fatal alert: insufficient_security";
if (!expectedExMsg.equals(she.getMessage())) { if (!she.getMessage().endsWith(expectedExMsg)) {
throw she; throw she;
} }
System.out.println("Expected exception thrown in server"); System.out.println("Expected exception thrown in server");
@ -77,7 +77,7 @@ public class LegacyDHEKeyExchange extends SSLSocketTemplate{
} catch (SSLHandshakeException she) { } catch (SSLHandshakeException she) {
String expectedExMsg = "DH ServerKeyExchange does not comply to" + String expectedExMsg = "DH ServerKeyExchange does not comply to" +
" algorithm constraints"; " algorithm constraints";
if (!expectedExMsg.equals(she.getMessage())) { if (!she.getMessage().endsWith(expectedExMsg)) {
throw she; throw she;
} }
System.out.println("Expected exception thrown in client"); System.out.println("Expected exception thrown in client");

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (C) 2021, 2024 THL A29 Limited, a Tencent company. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -122,7 +123,7 @@ public class SigAlgosExtTestWithTLS12 extends SSLEngineTemplate {
"Expected SSLHandshakeException wasn't thrown"); "Expected SSLHandshakeException wasn't thrown");
} }
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
if (EXPECT_FAIL && e.getMessage().equals( if (EXPECT_FAIL && e.getMessage().endsWith(
"No supported signature algorithm")) { "No supported signature algorithm")) {
System.out.println("Expected SSLHandshakeException"); System.out.println("Expected SSLHandshakeException");
} else { } else {

View file

@ -1,4 +1,5 @@
/* /*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
@ -78,7 +79,7 @@ public class SigAlgosExtTestWithTLS13 extends SSLSocketTemplate {
"Expected SSLHandshakeException wasn't thrown"); "Expected SSLHandshakeException wasn't thrown");
} }
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
if (expectFail && e.getMessage().equals( if (expectFail && e.getMessage().endsWith(
"No supported signature algorithm")) { "No supported signature algorithm")) {
System.out.println("Expected SSLHandshakeException"); System.out.println("Expected SSLHandshakeException");
} else { } else {