-
Bug
-
Resolution: Unresolved
-
P4
-
8, 11, 17, 21, 22
-
generic
-
generic
ADDITIONAL SYSTEM INFORMATION :
❯ java -version
openjdk version "21.0.1" 2023-10-17 LTS
OpenJDK Runtime Environment Zulu21.30+15-CA (build 21.0.1+12-LTS)
OpenJDK 64-Bit Server VM Zulu21.30+15-CA (build 21.0.1+12-LTS, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
This is essentially the same issue asJDK-8282600.
When a TLS connection is closed from a server implemented with Java, a user_cancelled might be sent before the close_notify; apparently as a workaround for some older clients. However, as far as I understand, this is non-compliant behavior as user_cancelled is only allowed during the TLS handshake. Some clients choke on this. InJDK-8282600 this problem was reported for gnutls. However, openssl started to dislike this starting with version 3.2 too.
In response toJDK-8282600 the code was modified, to only send the user_cancelled when the client side hasn't closed it's end yet. However, I think, that user_cancelled should never be sent for TLS 1.3 as it is not always possible to have the client close its side first.
Concretely, I'm experiencing this issue with dmarc-metrics-exporter [1] and Greenmail [2]. Greenmail provides an IMAP server for development purposes and is implemented in Java. dmarc-metrics-exporter, implemented in Python, runs test against this server and connects via TLS. When dmarc-metrics-exporter sends an IMAP "LOGOUT" command, the Greenmail server will end the TLS connection with user_cancelled and close_notify. This causes the Python implementation based on openssl of dmarc-metrics-exporter to throw an exception (if using openssl 3.2). I cannot/don't want to close the connection immediately after sending "LOGOUT" to wait for the servers response confirming the logout.
[1]: https://github.com/jgosmann/dmarc-metrics-exporter
[2]: https://greenmail-mail-test.github.io/greenmail/
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a self-signed certificate:
keytool -genkey -keyalg RSA -alias selfsigned -keystore server_keystore.jks -storepass your_keystore_password -validity 365 -keysize 2048
2. Compile and run the following Java code provided as "Source code for an executable test case".
3. Using openssl 3.2 run the following command:
openssl s_client -msg localhost:9999
4. Type some arbitrary input and press enter. This will cause the server to close the connection.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
OpenSSL will exit with the following line indicating a proper termination of the connection:
closed
ACTUAL -
OpenSSL exits with the following line indicating an improper termination of the connection/error:
read:errno=0
---------- BEGIN SOURCE ----------
import javax.net.ssl.*;
import java.io.*;
import java.security.*;
import java.security.cert.*;
public class TLSServer {
public static void main(String[] args) {
int port = 9999; // Change this to your desired port number
String keystoreFile = "server_keystore.jks"; // Change this to your keystore file
String keystorePassword = "your_keystore_password"; // Change this to your keystore password
try {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(keystoreFile), keystorePassword.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keystorePassword.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
System.out.println("Server started. Listening on port " + port);
while (true) {
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
System.out.println("Client connected");
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String line = in.readLine();
System.out.println("Received from client: " + line);
in.close();
clientSocket.close();
break; // Terminate connection after receiving a line from the client
}
} catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException e) {
e.printStackTrace();
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Either of the following works as a workaround:
- Use OpenSSL 3.1 (or earlier).
- Disable TLS 1.3 on the client.
FREQUENCY : always
❯ java -version
openjdk version "21.0.1" 2023-10-17 LTS
OpenJDK Runtime Environment Zulu21.30+15-CA (build 21.0.1+12-LTS)
OpenJDK 64-Bit Server VM Zulu21.30+15-CA (build 21.0.1+12-LTS, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
This is essentially the same issue as
When a TLS connection is closed from a server implemented with Java, a user_cancelled might be sent before the close_notify; apparently as a workaround for some older clients. However, as far as I understand, this is non-compliant behavior as user_cancelled is only allowed during the TLS handshake. Some clients choke on this. In
In response to
Concretely, I'm experiencing this issue with dmarc-metrics-exporter [1] and Greenmail [2]. Greenmail provides an IMAP server for development purposes and is implemented in Java. dmarc-metrics-exporter, implemented in Python, runs test against this server and connects via TLS. When dmarc-metrics-exporter sends an IMAP "LOGOUT" command, the Greenmail server will end the TLS connection with user_cancelled and close_notify. This causes the Python implementation based on openssl of dmarc-metrics-exporter to throw an exception (if using openssl 3.2). I cannot/don't want to close the connection immediately after sending "LOGOUT" to wait for the servers response confirming the logout.
[1]: https://github.com/jgosmann/dmarc-metrics-exporter
[2]: https://greenmail-mail-test.github.io/greenmail/
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a self-signed certificate:
keytool -genkey -keyalg RSA -alias selfsigned -keystore server_keystore.jks -storepass your_keystore_password -validity 365 -keysize 2048
2. Compile and run the following Java code provided as "Source code for an executable test case".
3. Using openssl 3.2 run the following command:
openssl s_client -msg localhost:9999
4. Type some arbitrary input and press enter. This will cause the server to close the connection.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
OpenSSL will exit with the following line indicating a proper termination of the connection:
closed
ACTUAL -
OpenSSL exits with the following line indicating an improper termination of the connection/error:
read:errno=0
---------- BEGIN SOURCE ----------
import javax.net.ssl.*;
import java.io.*;
import java.security.*;
import java.security.cert.*;
public class TLSServer {
public static void main(String[] args) {
int port = 9999; // Change this to your desired port number
String keystoreFile = "server_keystore.jks"; // Change this to your keystore file
String keystorePassword = "your_keystore_password"; // Change this to your keystore password
try {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(keystoreFile), keystorePassword.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keystorePassword.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
System.out.println("Server started. Listening on port " + port);
while (true) {
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
System.out.println("Client connected");
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String line = in.readLine();
System.out.println("Received from client: " + line);
in.close();
clientSocket.close();
break; // Terminate connection after receiving a line from the client
}
} catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException e) {
e.printStackTrace();
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Either of the following works as a workaround:
- Use OpenSSL 3.1 (or earlier).
- Disable TLS 1.3 on the client.
FREQUENCY : always
- relates to
-
JDK-8282600 SSLSocketImpl should not use user_canceled workaround when not necessary
- Resolved