Skip to content

Commit a596d64

Browse files
Roman MarchenkoYuri Nesterenko
Roman Marchenko
authored and
Yuri Nesterenko
committed
8065422: Trailing dot in hostname causes TLS handshake to fail with SNI disabled
Backport-of: a95ee5ada230a0177517efd3a417f319066169dd
1 parent 4f5326c commit a596d64

File tree

4 files changed

+616
-3
lines changed

4 files changed

+616
-3
lines changed

src/java.base/share/classes/sun/security/ssl/Utilities.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,19 @@ static List<SNIServerName> addToSNIServerNameList(
101101
* not look like a FQDN
102102
*/
103103
private static SNIHostName rawToSNIHostName(String hostname) {
104-
SNIHostName sniHostName = null;
104+
// Is it a Fully-Qualified Domain Names (FQDN) ending with a dot?
105+
if (hostname != null && hostname.endsWith(".")) {
106+
// Remove the ending dot, which is not allowed in SNIHostName.
107+
hostname = hostname.substring(0, hostname.length() - 1);
108+
}
109+
105110
if (hostname != null && hostname.indexOf('.') > 0 &&
106111
!hostname.endsWith(".") &&
107112
!IPAddressUtil.isIPv4LiteralAddress(hostname) &&
108113
!IPAddressUtil.isIPv6LiteralAddress(hostname)) {
109114

110115
try {
111-
sniHostName = new SNIHostName(hostname);
116+
return new SNIHostName(hostname);
112117
} catch (IllegalArgumentException iae) {
113118
// don't bother to handle illegal host_name
114119
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
@@ -118,7 +123,7 @@ private static SNIHostName rawToSNIHostName(String hostname) {
118123
}
119124
}
120125

121-
return sniHostName;
126+
return null;
122127
}
123128

124129
/**

src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java

+6
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,12 @@ static void checkIdentity(SSLSession session,
404404

405405
boolean identifiable = false;
406406
String peerHost = session.getPeerHost();
407+
// Is it a Fully-Qualified Domain Names (FQDN) ending with a dot?
408+
if (peerHost != null && peerHost.endsWith(".")) {
409+
// Remove the ending dot, which is not allowed in SNIHostName.
410+
peerHost = peerHost.substring(0, peerHost.length() - 1);
411+
}
412+
407413
if (!checkClientTrusted) {
408414
List<SNIServerName> sniNames = getRequestedServerNames(session);
409415
String sniHostName = getHostNameInSNI(sniNames);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
* Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/**
25+
* @test
26+
* @bug 8806542
27+
* @summary Trailing dot in hostname causes TLS handshake to fail
28+
* @library /javax/net/ssl/templates
29+
* @run main/othervm -Djdk.net.hosts.file=hostsForExample EndingDotHostname
30+
*/
31+
32+
import javax.net.ssl.*;
33+
import java.io.IOException;
34+
import java.io.InputStream;
35+
import java.io.OutputStream;
36+
import java.net.*;
37+
import java.util.concurrent.CountDownLatch;
38+
import java.util.concurrent.TimeUnit;
39+
40+
public class EndingDotHostname {
41+
public static void main(String[] args) throws Exception {
42+
System.setProperty("jdk.net.hosts.file", "hostsForExample");
43+
(new EndingDotHostname()).run();
44+
}
45+
46+
public void run() throws Exception {
47+
bootUp();
48+
}
49+
50+
// =================================================
51+
// Stuffs to boot up the client-server mode testing.
52+
private Thread serverThread = null;
53+
private volatile Exception serverException = null;
54+
private volatile Exception clientException = null;
55+
56+
// Is the server ready to serve?
57+
protected final CountDownLatch serverCondition = new CountDownLatch(1);
58+
59+
// Is the client ready to handshake?
60+
protected final CountDownLatch clientCondition = new CountDownLatch(1);
61+
62+
// What's the server port? Use any free port by default
63+
protected volatile int serverPort = 0;
64+
65+
// Boot up the testing, used to drive remainder of the test.
66+
private void bootUp() throws Exception {
67+
Exception startException = null;
68+
try {
69+
startServer();
70+
startClient();
71+
} catch (Exception e) {
72+
startException = e;
73+
}
74+
75+
// Wait for other side to close down.
76+
if (serverThread != null) {
77+
serverThread.join();
78+
}
79+
80+
// The test is pretty much over. Which side threw an exception?
81+
Exception local = clientException;
82+
Exception remote = serverException;
83+
84+
Exception exception = null;
85+
86+
// Check various exception conditions.
87+
if ((local != null) && (remote != null)) {
88+
// If both failed, return the curthread's exception.
89+
local.initCause(remote);
90+
exception = local;
91+
} else if (local != null) {
92+
exception = local;
93+
} else if (remote != null) {
94+
exception = remote;
95+
} else if (startException != null) {
96+
exception = startException;
97+
}
98+
99+
// If there was an exception *AND* a startException, output it.
100+
if (exception != null) {
101+
if (exception != startException && startException != null) {
102+
exception.addSuppressed(startException);
103+
}
104+
throw exception;
105+
}
106+
107+
// Fall-through: no exception to throw!
108+
}
109+
110+
private void startServer() {
111+
serverThread = new Thread(() -> {
112+
try {
113+
doServerSide();
114+
} catch (Exception e) {
115+
// Our server thread just died. Release the client,
116+
// if not active already...
117+
serverException = e;
118+
}
119+
});
120+
121+
serverThread.start();
122+
}
123+
124+
private void startClient() {
125+
try {
126+
doClientSide();
127+
} catch (Exception e) {
128+
clientException = e;
129+
}
130+
}
131+
132+
protected void doServerSide() throws Exception {
133+
// kick off the server side service
134+
SSLContext context = SSLExampleCert.createServerSSLContext();
135+
SSLServerSocketFactory sslssf = context.getServerSocketFactory();
136+
137+
SSLServerSocket sslServerSocket =
138+
(SSLServerSocket)sslssf.createServerSocket();
139+
sslServerSocket.bind(new InetSocketAddress(
140+
InetAddress.getLoopbackAddress(), 0));
141+
serverPort = sslServerSocket.getLocalPort();
142+
143+
// Signal the client, the server is ready to accept connection.
144+
serverCondition.countDown();
145+
146+
// Try to accept a connection in 30 seconds.
147+
SSLSocket sslSocket;
148+
try {
149+
sslServerSocket.setSoTimeout(30000);
150+
sslSocket = (SSLSocket)sslServerSocket.accept();
151+
} catch (SocketTimeoutException ste) {
152+
// Ignore the test case if no connection within 30 seconds.
153+
System.out.println(
154+
"No incoming client connection in 30 seconds. " +
155+
"Ignore in server side.");
156+
return;
157+
} finally {
158+
sslServerSocket.close();
159+
}
160+
161+
// handle the connection
162+
try {
163+
// Is it the expected client connection?
164+
//
165+
// Naughty test cases or third party routines may try to
166+
// connection to this server port unintentionally. In
167+
// order to mitigate the impact of unexpected client
168+
// connections and avoid intermittent failure, it should
169+
// be checked that the accepted connection is really linked
170+
// to the expected client.
171+
boolean clientIsReady =
172+
clientCondition.await(30L, TimeUnit.SECONDS);
173+
174+
if (clientIsReady) {
175+
// Run the application in server side.
176+
runServerApplication(sslSocket);
177+
} else { // Otherwise, ignore
178+
// We don't actually care about plain socket connections
179+
// for TLS communication testing generally. Just ignore
180+
// the test if the accepted connection is not linked to
181+
// the expected client or the client connection timeout
182+
// in 30 seconds.
183+
System.out.println(
184+
"The client is not the expected one or timeout. " +
185+
"Ignore in server side.");
186+
}
187+
} finally {
188+
sslSocket.close();
189+
}
190+
}
191+
192+
// Define the server side application of the test for the specified socket.
193+
protected void runServerApplication(SSLSocket socket) throws Exception {
194+
// here comes the test logic
195+
InputStream sslIS = socket.getInputStream();
196+
OutputStream sslOS = socket.getOutputStream();
197+
198+
sslIS.read();
199+
sslOS.write(85);
200+
sslOS.flush();
201+
}
202+
203+
protected void doClientSide() throws Exception {
204+
// Wait for server to get started.
205+
//
206+
// The server side takes care of the issue if the server cannot
207+
// get started in 90 seconds. The client side would just ignore
208+
// the test case if the serer is not ready.
209+
boolean serverIsReady =
210+
serverCondition.await(90L, TimeUnit.SECONDS);
211+
if (!serverIsReady) {
212+
System.out.println(
213+
"The server is not ready yet in 90 seconds. " +
214+
"Ignore in client side.");
215+
return;
216+
}
217+
218+
SSLContext context = SSLExampleCert.createClientSSLContext();
219+
SSLSocketFactory sslsf = context.getSocketFactory();
220+
221+
try (SSLSocket sslSocket = (SSLSocket)sslsf.createSocket(
222+
"www.example.com.", serverPort)) {
223+
// OK, here the client and server get connected.
224+
SSLParameters sslParameters = sslSocket.getSSLParameters();
225+
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
226+
sslSocket.setSSLParameters(sslParameters);
227+
228+
// Signal the server, the client is ready to communicate.
229+
clientCondition.countDown();
230+
231+
// There is still a chance in theory that the server thread may
232+
// wait client-ready timeout and then quit. The chance should
233+
// be really rare so we don't consider it until it becomes a
234+
// real problem.
235+
236+
// Run the application in client side.
237+
runClientApplication(sslSocket);
238+
}
239+
}
240+
241+
// Define the client side application of the test for the specified socket.
242+
protected void runClientApplication(SSLSocket socket) throws Exception {
243+
InputStream sslIS = socket.getInputStream();
244+
OutputStream sslOS = socket.getOutputStream();
245+
246+
sslOS.write(280);
247+
sslOS.flush();
248+
sslIS.read();
249+
}
250+
}
251+

0 commit comments

Comments
 (0)