Skip to content

Commit 152c148

Browse files
committed
Add protocol host name and SNI host name matching
Add strictSNI attribute on the Connector to control it.
1 parent d373cfb commit 152c148

13 files changed

Lines changed: 255 additions & 4 deletions

File tree

java/org/apache/coyote/AbstractProtocol.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,10 @@ public int getWaitingProcessorCount() {
490490
}
491491

492492

493+
public boolean checkSni(String sniHostName, String protocolHostName) {
494+
return getEndpoint().checkSni(sniHostName, protocolHostName);
495+
}
496+
493497
// ----------------------------------------------- Accessors for sub-classes
494498

495499
protected AbstractEndpoint<S,?> getEndpoint() {

java/org/apache/coyote/http11/Http11Processor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,11 @@ private void prepareRequest() throws IOException {
791791
// Validate host name and extract port if present
792792
parseHost(hostValueMB);
793793

794+
// Match host name with SNI if required
795+
if (!protocol.checkSni(socketWrapper.getSniHostName(), request.serverName().toString())) {
796+
badRequest("http11processor.request.sni");
797+
}
798+
794799
if (!getErrorState().isIoAllowed()) {
795800
getAdapter().log(request, response, 0);
796801
}

java/org/apache/coyote/http11/LocalStrings.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ http11processor.request.noHostHeader=The HTTP/1.1 request did not provide a host
3939
http11processor.request.nonNumericContentLength=The request contained a content-length header with a non-numeric value
4040
http11processor.request.prepare=Error preparing request
4141
http11processor.request.process=Error processing request
42+
http11processor.request.sni=The host header does not match the SNI host
4243
http11processor.request.unsupportedEncoding=Error preparing request, unsupported transfer encoding [{0}]
4344
http11processor.request.unsupportedVersion=Error preparing request, unsupported HTTP version [{0}]
4445
http11processor.response.finish=Error finishing response

java/org/apache/coyote/http2/Http2UpgradeHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,6 +1931,10 @@ void replaceStream(AbstractNonZeroStream original, AbstractNonZeroStream replace
19311931
}
19321932

19331933

1934+
String getSniHostName() {
1935+
return socketWrapper.getSniHostName();
1936+
}
1937+
19341938
protected class PingManager {
19351939

19361940
protected boolean initiateDisabled = false;

java/org/apache/coyote/http2/LocalStrings.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ stream.header.te=Connection [{0}], Stream [{1}], HTTP header [te] is not permitt
104104
stream.header.unexpectedPseudoHeader=Connection [{0}], Stream [{1}], Pseudo header [{2}] received after a regular header
105105
stream.header.unknownPseudoHeader=Connection [{0}], Stream [{1}], Unknown pseudo header [{2}] received
106106
stream.host.inconsistent=Connection [{0}], Stream [{1}], The header host header [{2}] is inconsistent with previously provided values for host [{3}] and/or port [{4}]
107+
stream.host.sni=Connection [{0}], Stream [{1}], The host header [{2}] does not match the SNI host [{3}]
107108
stream.inputBuffer.copy=Copying [{0}] bytes from inBuffer to outBuffer
108109
stream.inputBuffer.dispatch=Data added to inBuffer when read interest is registered. Triggering a read dispatch
109110
stream.inputBuffer.empty=The Stream input buffer is empty. Waiting for more data

java/org/apache/coyote/http2/Stream.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,11 @@ private void parseAuthority(String value, boolean host) throws HpackException {
509509
} else {
510510
coyoteRequest.serverName().setString(value);
511511
}
512+
// Match host name with SNI if required
513+
if (!handler.getProtocol().getHttp11Protocol().checkSni(handler.getSniHostName(), coyoteRequest.serverName().getString())) {
514+
throw new HpackException(sm.getString("stream.host.sni", getConnectionId(), getIdAsString(), value,
515+
handler.getSniHostName()));
516+
}
512517
}
513518

514519

java/org/apache/tomcat/util/net/AbstractEndpoint.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,17 @@ public Set<SocketWrapperBase<S>> getConnections() {
232232

233233
// ----------------------------------------------------------------- Properties
234234

235+
private boolean strictSni = true;
236+
237+
public boolean getStrictSni() {
238+
return strictSni;
239+
}
240+
241+
public void setStrictSni(boolean strictSni) {
242+
this.strictSni = strictSni;
243+
}
244+
245+
235246
private String defaultSSLHostConfigName = SSLHostConfig.DEFAULT_SSL_HOST_NAME;
236247

237248
/**
@@ -525,6 +536,22 @@ protected SSLHostConfig getSSLHostConfig(String sniHostName) {
525536
}
526537

527538

539+
/**
540+
* Check if two host names share the same SSLHostConfig.
541+
*
542+
* @param sniHostName the host name from SNI, null if SNI is not in use
543+
* @param protocolHostName the host name from the protocol
544+
* @return true if SNI is not checked, if the SNI host name matches the protocol host name,
545+
* if both host names use the same SSLHostConfig configuration, if there is no SNI and the
546+
* protocol host name uses the default SSLHostConfig configuration, and false otherwise
547+
*/
548+
public boolean checkSni(String sniHostName, String protocolHostName) {
549+
return (!strictSni || !isSSLEnabled()
550+
|| (sniHostName != null && sniHostName.equalsIgnoreCase(protocolHostName))
551+
|| getSSLHostConfig(sniHostName) == getSSLHostConfig(protocolHostName));
552+
}
553+
554+
528555
/**
529556
* Has the user requested that send file be used where possible?
530557
*/

java/org/apache/tomcat/util/net/SecureNioChannel.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ private int processSNI() throws IOException {
281281
switch (extractor.getResult()) {
282282
case COMPLETE:
283283
hostName = extractor.getSNIValue();
284+
socketWrapper.setSniHostName(hostName);
284285
clientRequestedApplicationProtocols = extractor.getClientRequestedApplicationProtocols();
285286
//$FALL-THROUGH$ to set the client requested ciphers
286287
case NOT_PRESENT:

java/org/apache/tomcat/util/net/SocketWrapperBase.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public abstract class SocketWrapperBase<E> {
7272
protected String remoteHost = null;
7373
protected int remotePort = -1;
7474

75+
protected String sniHostName = null;
76+
7577
/**
7678
* Used to record the first IOException that occurs during non-blocking read/writes that can't be usefully
7779
* propagated up the stack since there is no user code or appropriate container code in the stack to handle it.
@@ -234,6 +236,20 @@ public void setNegotiatedProtocol(String negotiatedProtocol) {
234236
this.negotiatedProtocol = negotiatedProtocol;
235237
}
236238

239+
/**
240+
* @return the sniHostName
241+
*/
242+
public String getSniHostName() {
243+
return this.sniHostName;
244+
}
245+
246+
/**
247+
* @param sniHostName the SNI host name to set
248+
*/
249+
public void setSniHostName(String sniHostName) {
250+
this.sniHostName = sniHostName;
251+
}
252+
237253
/**
238254
* Set the timeout for reading. Values of zero or less will be changed to -1.
239255
*

test/org/apache/catalina/startup/SimpleHttpClient.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,19 +197,29 @@ public String getRedirectUri() {
197197
return redirectUri;
198198
}
199199

200-
public void connect(int connectTimeout, int soTimeout)
201-
throws UnknownHostException, IOException {
200+
public void connect(Socket socket, int connectTimeout, int soTimeout, boolean connect) throws UnknownHostException, IOException {
202201
final String encoding = "ISO-8859-1";
203202
SocketAddress addr = new InetSocketAddress("localhost", port);
204-
socket = new Socket();
203+
this.socket = socket;
205204
socket.setSoTimeout(soTimeout);
206-
socket.connect(addr,connectTimeout);
205+
if (connect) {
206+
socket.connect(addr, connectTimeout);
207+
}
207208
OutputStream os = createOutputStream(socket);
208209
writer = new OutputStreamWriter(os, encoding);
209210
InputStream is = socket.getInputStream();
210211
Reader r = new InputStreamReader(is, encoding);
211212
reader = new BufferedReader(r);
212213
}
214+
215+
public void connect(int connectTimeout, int soTimeout) throws UnknownHostException, IOException {
216+
connect(new Socket(), 10000, 10000, true);
217+
}
218+
219+
public void connect(Socket socket) throws UnknownHostException, IOException {
220+
connect(socket, 10000, 10000, false);
221+
}
222+
213223
public void connect() throws UnknownHostException, IOException {
214224
connect(10000, 10000);
215225
}

0 commit comments

Comments
 (0)