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.
*
* This code is free software; you can redistribute it and/or modify it
@ -123,13 +123,13 @@ enum Alert {
}
if (cause instanceof IOException) {
return new SSLException(reason, cause);
return new SSLException("(" + description + ") " + reason, cause);
} else if ((this == UNEXPECTED_MESSAGE)) {
return new SSLProtocolException(reason, cause);
return new SSLProtocolException("(" + description + ") " + reason, cause);
} else if (handshakeOnly) {
return new SSLHandshakeException(reason, cause);
return new SSLHandshakeException("(" + description + ") " + reason, cause);
} 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 !=
ClientAuthType.CLIENT_AUTH_REQUESTED) {
// unexpected or require client authentication
throw shc.conContext.fatal(Alert.BAD_CERTIFICATE,
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
"Empty client certificate chain");
} else {
return;
@ -1162,7 +1162,7 @@ final class CertificateMessage {
shc.handshakeConsumers.remove(
SSLHandshake.CERTIFICATE_VERIFY.id);
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");
} else {
// optional client authentication
@ -1186,7 +1186,7 @@ final class CertificateMessage {
T13CertificateMessage certificateMessage )throws IOException {
if (certificateMessage.certEntries == null ||
certificateMessage.certEntries.isEmpty()) {
throw chc.conContext.fatal(Alert.BAD_CERTIFICATE,
throw chc.conContext.fatal(Alert.DECODE_ERROR,
"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.
*
* This code is free software; you can redistribute it and/or modify it
@ -36,15 +36,11 @@
*/
import javax.net.ssl.SSLSession;
import java.util.HexFormat;
public class CheckSessionContext {
static void toHex(byte[] id) {
for (byte b : id) {
System.out.printf("%02X ", b);
}
System.out.println();
}
static HexFormat hex = HexFormat.of();
public static void main(String[] args) throws Exception {
TLSBase.Server server = new TLSBase.Server();
@ -52,20 +48,18 @@ public class CheckSessionContext {
// Initial client session
TLSBase.Client client1 = new TLSBase.Client();
if (server.getSession(client1).getSessionContext() == null) {
throw new Exception("Context was null");
throw new Exception("Context was null. Handshake failure.");
} else {
System.out.println("Context was found");
}
SSLSession ss = server.getSession(client1);
System.out.println(ss);
byte[] id = ss.getId();
System.out.print("id = ");
toHex(id);
System.out.println("id = " + hex.formatHex(id));
System.out.println("ss.getSessionContext().getSession(id) = " + ss.getSessionContext().getSession(id));
if (ss.getSessionContext().getSession(id) != null) {
id = ss.getSessionContext().getSession(id).getId();
System.out.print("id = ");
toHex(id);
System.out.println("id = " + hex.formatHex(id));
}
server.close(client1);
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.
*
* This code is free software; you can redistribute it and/or modify it
@ -21,18 +21,15 @@
* questions.
*/
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
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 javax.net.ssl.*;
import java.io.*;
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.List;
import java.util.concurrent.ConcurrentHashMap;
/**
@ -67,11 +64,11 @@ abstract public class TLSBase {
TLSBase() {
String keyFilename =
System.getProperty("test.src", "./") + "/" + pathToStores +
"/" + keyStoreFile;
System.getProperty("test.src", "./") + "/" + pathToStores +
"/" + keyStoreFile;
String trustFilename =
System.getProperty("test.src", "./") + "/" + pathToStores +
"/" + trustStoreFile;
System.getProperty("test.src", "./") + "/" + pathToStores +
"/" + trustStoreFile;
System.setProperty("javax.net.ssl.keyStore", keyFilename);
System.setProperty("javax.net.ssl.keyStorePassword", passwd);
System.setProperty("javax.net.ssl.trustStore", trustFilename);
@ -79,30 +76,65 @@ abstract public class TLSBase {
}
// Base read operation
byte[] read(SSLSocket sock) {
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(sock.getInputStream()));
String s = reader.readLine();
System.err.println("(read) " + name + ": " + s);
return s.getBytes();
} catch (Exception e) {
e.printStackTrace();
}
return null;
byte[] read(SSLSocket sock) throws Exception {
BufferedReader reader = new BufferedReader(
new InputStreamReader(sock.getInputStream()));
String s = reader.readLine();
System.err.println("(read) " + name + ": " + s);
return s.getBytes();
}
// Base write operation
public void write(SSLSocket sock, byte[] data) {
try {
PrintWriter out = new PrintWriter(
new OutputStreamWriter(sock.getOutputStream()));
out.println(new String(data));
out.flush();
System.err.println("(write)" + name + ": " + new String(data));
} catch (Exception e) {
e.printStackTrace();
public void write(SSLSocket sock, byte[] data) throws Exception {
PrintWriter out = new PrintWriter(
new OutputStreamWriter(sock.getOutputStream()));
out.println(new String(data));
out.flush();
System.err.println("(write)" + name + ": " + new String(data));
}
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,17 @@ abstract public class TLSBase {
new ConcurrentHashMap<>();
boolean exit = false;
Thread t;
List<Exception> exceptionList = new ArrayList<>();
Server() {
Server(ServerBuilder builder) {
super();
name = "server";
try {
sslContext = SSLContext.getDefault();
sslContext = SSLContext.getInstance("TLS");
sslContext.init(TLSBase.getKeyManager(builder.km), TLSBase.getTrustManager(builder.tm), null);
fac = sslContext.getServerSocketFactory();
ssock = (SSLServerSocket) fac.createServerSocket(0);
ssock.setNeedClientAuth(builder.clientauth);
serverPort = ssock.getLocalPort();
} catch (Exception e) {
System.err.println(e.getMessage());
@ -136,13 +171,14 @@ abstract public class TLSBase {
try {
while (true) {
System.err.println("Server ready on port " +
serverPort);
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) {
@ -153,6 +189,51 @@ abstract public class TLSBase {
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();
} 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();
}
}
} catch (Exception ex) {
System.err.println("Server Down");
ex.printStackTrace();
}
});
t.start();
}
// Exit test to quit the test. This must be called at the end of the
// test or the test will never end.
void done() {
@ -166,7 +247,7 @@ abstract public class TLSBase {
}
// Read from the client
byte[] read(Client client) {
byte[] read(Client client) throws Exception {
SSLSocket s = clientMap.get(Integer.valueOf(client.getPort()));
if (s == null) {
System.err.println("No socket found, port " + client.getPort());
@ -175,13 +256,13 @@ abstract public class TLSBase {
}
// Write to the client
void write(Client client, byte[] data) {
void write(Client client, byte[] data) throws Exception {
write(clientMap.get(client.getPort()), data);
}
// Server writes to the client, then reads from the client.
// 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());
return (Arrays.compare(s.getBytes(), client.read()) == 0);
}
@ -197,30 +278,61 @@ abstract public class TLSBase {
SSLSocket s = clientMap.get(Integer.valueOf(c.getPort()));
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.
* It must be run after the Server constructor is called.
*/
static class Client extends TLSBase {
SSLSocket sock;
boolean km, tm;
Client() {
this(false, false);
}
/**
* @param km - true sets an empty key manager
* @param tm - true sets an empty trust manager
*/
Client(boolean km, boolean tm) {
super();
try {
sslContext = SSLContext.getDefault();
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
}
this.km = km;
this.tm = tm;
connect();
}
// Connect to server. Maybe runnable in the future
public SSLSocket connect() {
try {
sslContext = SSLContext.getDefault();
sslContext = SSLContext.getInstance("TLS");
sslContext.init(TLSBase.getKeyManager(km), TLSBase.getTrustManager(tm), null);
sock = (SSLSocket)sslContext.getSocketFactory().createSocket();
sock.connect(new InetSocketAddress("localhost", serverPort));
System.err.println("Client connected using port " +
@ -235,21 +347,21 @@ abstract public class TLSBase {
}
// Read from the client socket
byte[] read() {
byte[] read() throws Exception {
return read(sock);
}
// Write to the client socket
void write(byte[] data) {
void write(byte[] data) throws Exception {
write(sock, data);
}
void write(String s) {
void write(String s) throws Exception {
write(sock, s.getBytes());
}
// Client writes to the server, then reads from the server.
// 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());
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.
*
* 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");
} catch (SSLHandshakeException she) {
String expectedExMsg = "Received fatal alert: insufficient_security";
if (!expectedExMsg.equals(she.getMessage())) {
if (!she.getMessage().endsWith(expectedExMsg)) {
throw she;
}
System.out.println("Expected exception thrown in server");
@ -77,7 +77,7 @@ public class LegacyDHEKeyExchange extends SSLSocketTemplate{
} catch (SSLHandshakeException she) {
String expectedExMsg = "DH ServerKeyExchange does not comply to" +
" algorithm constraints";
if (!expectedExMsg.equals(she.getMessage())) {
if (!she.getMessage().endsWith(expectedExMsg)) {
throw she;
}
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.
*
* 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");
}
} catch (SSLHandshakeException e) {
if (EXPECT_FAIL && e.getMessage().equals(
if (EXPECT_FAIL && e.getMessage().endsWith(
"No supported signature algorithm")) {
System.out.println("Expected SSLHandshakeException");
} 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.
* 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");
}
} catch (SSLHandshakeException e) {
if (expectFail && e.getMessage().equals(
if (expectFail && e.getMessage().endsWith(
"No supported signature algorithm")) {
System.out.println("Expected SSLHandshakeException");
} else {