Skip to content

Commit 258a591

Browse files
committed
Add protocol host name and SNI host name matching
Add strictSNI attribute on the Connector to control it.
1 parent 644232e commit 258a591

13 files changed

Lines changed: 255 additions & 4 deletions

File tree

java/org/apache/coyote/http11/AbstractHttp11Protocol.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,10 @@ public void setSniParseLimit(int sniParseLimit) {
759759
}
760760

761761

762+
public boolean checkSni(String sniHostName, String protocolHostName) {
763+
return getEndpoint().checkSni(sniHostName, protocolHostName);
764+
}
765+
762766
// ------------------------------------------------------------- Common code
763767

764768
@Override

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

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

783+
// Match host name with SNI if required
784+
if (!protocol.checkSni(socketWrapper.getSniHostName(), request.serverName().toString())) {
785+
badRequest("http11processor.request.sni");
786+
}
787+
783788
if (!getErrorState().isIoAllowed()) {
784789
getAdapter().log(request, response, 0);
785790
}

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
@@ -1859,6 +1859,10 @@ public ServletConnection getServletConnection() {
18591859
}
18601860
}
18611861

1862+
String getSniHostName() {
1863+
return socketWrapper.getSniHostName();
1864+
}
1865+
18621866
protected class PingManager {
18631867

18641868
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
@@ -105,6 +105,7 @@ stream.header.te=Connection [{0}], Stream [{1}], HTTP header [te] is not permitt
105105
stream.header.unexpectedPseudoHeader=Connection [{0}], Stream [{1}], Pseudo header [{2}] received after a regular header
106106
stream.header.unknownPseudoHeader=Connection [{0}], Stream [{1}], Unknown pseudo header [{2}] received
107107
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}]
108+
stream.host.sni=Connection [{0}], Stream [{1}], The host header [{2}] does not match the SNI host [{3}]
108109
stream.inputBuffer.copy=Copying [{0}] bytes from inBuffer to outBuffer
109110
stream.inputBuffer.dispatch=Data added to inBuffer when read interest is registered. Triggering a read dispatch
110111
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
@@ -505,6 +505,11 @@ private void parseAuthority(String value, boolean host) throws HpackException {
505505
} else {
506506
coyoteRequest.serverName().setString(value);
507507
}
508+
// Match host name with SNI if required
509+
if (!handler.getProtocol().getHttp11Protocol().checkSni(handler.getSniHostName(), coyoteRequest.serverName().getString())) {
510+
throw new HpackException(sm.getString("stream.host.sni", getConnectionId(), getIdAsString(), value,
511+
handler.getSniHostName()));
512+
}
508513
}
509514

510515

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,17 @@ public void setSniParseLimit(int sniParseLimit) {
257257
}
258258

259259

260+
private boolean strictSni = true;
261+
262+
public boolean getStrictSni() {
263+
return strictSni;
264+
}
265+
266+
public void setStrictSni(boolean strictSni) {
267+
this.strictSni = strictSni;
268+
}
269+
270+
260271
private String defaultSSLHostConfigName = SSLHostConfig.DEFAULT_SSL_HOST_NAME;
261272

262273
/**
@@ -714,6 +725,22 @@ protected SSLHostConfig getSSLHostConfig(String sniHostName) {
714725
}
715726

716727

728+
/**
729+
* Check if two host names share the same SSLHostConfig.
730+
*
731+
* @param sniHostName the host name from SNI, null if SNI is not in use
732+
* @param protocolHostName the host name from the protocol
733+
* @return true if SNI is not checked, if the SNI host name matches the protocol host name,
734+
* if both host names use the same SSLHostConfig configuration, if there is no SNI and the
735+
* protocol host name uses the default SSLHostConfig configuration, and false otherwise
736+
*/
737+
public boolean checkSni(String sniHostName, String protocolHostName) {
738+
return (!strictSni || !isSSLEnabled()
739+
|| (sniHostName != null && sniHostName.equalsIgnoreCase(protocolHostName))
740+
|| getSSLHostConfig(sniHostName) == getSSLHostConfig(protocolHostName));
741+
}
742+
743+
717744
/**
718745
* Has the user requested that send file be used where possible?
719746
*/

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ protected int processSNI() throws IOException {
279279
switch (extractor.getResult()) {
280280
case COMPLETE:
281281
hostName = extractor.getSNIValue();
282+
socketWrapper.setSniHostName(hostName);
282283
clientRequestedApplicationProtocols = extractor.getClientRequestedApplicationProtocols();
283284
//$FALL-THROUGH$ to set the client requested ciphers
284285
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
@@ -85,6 +85,8 @@ public abstract class SocketWrapperBase<E> {
8585
protected int remotePort = -1;
8686
protected volatile ServletConnection servletConnection = null;
8787

88+
protected String sniHostName = null;
89+
8890
/**
8991
* Used to record the first IOException that occurs during non-blocking read/writes that can't be usefully
9092
* propagated up the stack since there is no user code or appropriate container code in the stack to handle it.
@@ -208,6 +210,20 @@ public void setNegotiatedProtocol(String negotiatedProtocol) {
208210
this.negotiatedProtocol = negotiatedProtocol;
209211
}
210212

213+
/**
214+
* @return the sniHostName
215+
*/
216+
public String getSniHostName() {
217+
return this.sniHostName;
218+
}
219+
220+
/**
221+
* @param sniHostName the SNI host name to set
222+
*/
223+
public void setSniHostName(String sniHostName) {
224+
this.sniHostName = sniHostName;
225+
}
226+
211227
/**
212228
* Set the timeout for reading. Values of zero or less will be changed to -1.
213229
*

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,18 +205,28 @@ public String getRedirectUri() {
205205
return redirectUri;
206206
}
207207

208-
public void connect(int connectTimeout, int soTimeout)
209-
throws UnknownHostException, IOException {
208+
public void connect(Socket socket, int connectTimeout, int soTimeout, boolean connect) throws UnknownHostException, IOException {
210209
SocketAddress addr = new InetSocketAddress("localhost", port);
211-
socket = new Socket();
210+
this.socket = socket;
212211
socket.setSoTimeout(soTimeout);
213-
socket.connect(addr,connectTimeout);
212+
if (connect) {
213+
socket.connect(addr, connectTimeout);
214+
}
214215
OutputStream os = createOutputStream(socket);
215216
writer = new OutputStreamWriter(os, requestBodyEncoding);
216217
InputStream is = socket.getInputStream();
217218
Reader r = new InputStreamReader(is, responseBodyEncoding);
218219
reader = new BufferedReader(r);
219220
}
221+
222+
public void connect(int connectTimeout, int soTimeout) throws UnknownHostException, IOException {
223+
connect(new Socket(), 10000, 10000, true);
224+
}
225+
226+
public void connect(Socket socket) throws UnknownHostException, IOException {
227+
connect(socket, 10000, 10000, false);
228+
}
229+
220230
public void connect() throws UnknownHostException, IOException {
221231
connect(10000, 10000);
222232
}

0 commit comments

Comments
 (0)