Skip to content

Commit 5a87723

Browse files
authored
Merge pull request #3348 from bdarnell/iostream-hostname-test
iostream_test: Test check_hostname functionality.
2 parents a6dfd70 + 2da0a99 commit 5a87723

File tree

4 files changed

+127
-45
lines changed

4 files changed

+127
-45
lines changed

Diff for: tornado/test/iostream_test.py

+82
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,88 @@ def handle_stream(self, stream, address):
11491149
yield handshake_future
11501150

11511151

1152+
class TestIOStreamCheckHostname(AsyncTestCase):
1153+
# This test ensures that hostname checks are working correctly after
1154+
# #3337 revealed that we have no test coverage in this area, and we
1155+
# removed a manual hostname check that was needed only for very old
1156+
# versions of python.
1157+
def setUp(self):
1158+
super().setUp()
1159+
self.listener, self.port = bind_unused_port()
1160+
1161+
def accept_callback(connection, address):
1162+
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
1163+
ssl_ctx.load_cert_chain(
1164+
os.path.join(os.path.dirname(__file__), "test.crt"),
1165+
os.path.join(os.path.dirname(__file__), "test.key"),
1166+
)
1167+
connection = ssl_ctx.wrap_socket(
1168+
connection,
1169+
server_side=True,
1170+
do_handshake_on_connect=False,
1171+
)
1172+
SSLIOStream(connection)
1173+
1174+
netutil.add_accept_handler(self.listener, accept_callback)
1175+
1176+
# Our self-signed cert is its own CA. We have to pass the CA check before
1177+
# the hostname check will be performed.
1178+
self.client_ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
1179+
self.client_ssl_ctx.load_verify_locations(
1180+
os.path.join(os.path.dirname(__file__), "test.crt")
1181+
)
1182+
1183+
def tearDown(self):
1184+
self.io_loop.remove_handler(self.listener.fileno())
1185+
self.listener.close()
1186+
super().tearDown()
1187+
1188+
@gen_test
1189+
async def test_match(self):
1190+
stream = SSLIOStream(socket.socket(), ssl_options=self.client_ssl_ctx)
1191+
await stream.connect(
1192+
("127.0.0.1", self.port),
1193+
server_hostname="foo.example.com",
1194+
)
1195+
stream.close()
1196+
1197+
@gen_test
1198+
async def test_no_match(self):
1199+
stream = SSLIOStream(socket.socket(), ssl_options=self.client_ssl_ctx)
1200+
with ExpectLog(
1201+
gen_log,
1202+
".*alert bad certificate",
1203+
level=logging.WARNING,
1204+
required=platform.system() != "Windows",
1205+
):
1206+
with self.assertRaises(ssl.SSLCertVerificationError):
1207+
with ExpectLog(
1208+
gen_log,
1209+
".*(certificate verify failed: Hostname mismatch)",
1210+
level=logging.WARNING,
1211+
):
1212+
await stream.connect(
1213+
("127.0.0.1", self.port),
1214+
server_hostname="bar.example.com",
1215+
)
1216+
# The server logs a warning while cleaning up the failed connection.
1217+
# Unfortunately there's no good hook to wait for this logging.
1218+
# It doesn't seem to happen on windows; I'm not sure why.
1219+
if platform.system() != "Windows":
1220+
await asyncio.sleep(0.1)
1221+
1222+
@gen_test
1223+
async def test_check_disabled(self):
1224+
# check_hostname can be set to false and the connection will succeed even though it doesn't
1225+
# have the right hostname.
1226+
self.client_ssl_ctx.check_hostname = False
1227+
stream = SSLIOStream(socket.socket(), ssl_options=self.client_ssl_ctx)
1228+
await stream.connect(
1229+
("127.0.0.1", self.port),
1230+
server_hostname="bar.example.com",
1231+
)
1232+
1233+
11521234
@skipIfNonUnix
11531235
class TestPipeIOStream(TestReadWriteMixin, AsyncTestCase):
11541236
@gen.coroutine

Diff for: tornado/test/test.crt

+16-18
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
-----BEGIN CERTIFICATE-----
2-
MIIDWzCCAkOgAwIBAgIUV4spou0CenmvKqa7Hml/MC+JKiAwDQYJKoZIhvcNAQEL
3-
BQAwPTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExGTAXBgNVBAoM
4-
EFRvcm5hZG8gV2ViIFRlc3QwHhcNMTgwOTI5MTM1NjQ1WhcNMjgwOTI2MTM1NjQ1
5-
WjA9MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEZMBcGA1UECgwQ
6-
VG9ybmFkbyBXZWIgVGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
7-
AKT0LdyI8tW5uwP3ahE8BFSz+j3SsKBDv/0cKvqxVVE6sLEST2s3HjArZvIIG5sb
8-
iBkWDrqnZ6UKDvB4jlobLGAkepxDbrxHWxK53n0C28XXGLqJQ01TlTZ5rpjttMeg
9-
5SKNjHbxpOvpUwwQS4br4WjZKKyTGiXpFkFUty+tYVU35/U2yyvreWHmzpHx/25t
10-
H7O2RBARVwJYKOGPtlH62lQjpIWfVfklY4Ip8Hjl3B6rBxPyBULmVQw0qgoZn648
11-
oa4oLjs0wnYBz01gVjNMDHej52SsB/ieH7W1TxFMzqOlcvHh41uFbQJPgcXsruSS
12-
9Z4twzSWkUp2vk/C//4Sz38CAwEAAaNTMFEwHQYDVR0OBBYEFLf8fQ5+u8sDWAd3
13-
r5ZjZ5MmDWJeMB8GA1UdIwQYMBaAFLf8fQ5+u8sDWAd3r5ZjZ5MmDWJeMA8GA1Ud
14-
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADkkm3pIb9IeqVNmQ2uhQOgw
15-
UwyToTYUHNTb/Nm5lzBTBqC8gbXAS24RQ30AB/7G115Uxeo+YMKfITxm/CgR+vhF
16-
F59/YrzwXj+G8bdbuVl/UbB6f9RSp+Zo93rUZAtPWr77gxLUrcwSRzzDwxFjC2nC
17-
6eigbkvt1OQY775RwnFAt7HKPclE0Out+cGJIboJuO1f3r57ZdyFH0GzbZEff/7K
18-
atGXohijWJjYvU4mk0KFHORZrcBpsv9cfkFbmgVmiRwxRJ1tLauHM3Ne+VfqYE5M
19-
4rTStSyz3ASqVKJ2iFMQueNR/tUOuDlfRt+0nhJMuYSSkW+KTgnwyOGU9cv+mxA=
2+
MIIC1TCCAb2gAwIBAgIJAOV36k+idrqDMA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNV
3+
BAMMD2Zvby5leGFtcGxlLmNvbTAeFw0yMzExMTIwMTQ3MzhaFw0zMzExMDkwMTQ3
4+
MzhaMBoxGDAWBgNVBAMMD2Zvby5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
5+
BQADggEPADCCAQoCggEBAKjfAL8hQ1G5yoR29D0NwqhL3EE9RShYLvzKvSNhOceR
6+
e390XJLAi8PN8Xv8LkmoMITaLdRDtBwXcdw+kfHjcfXZ0cORJkxJFdk/38SsiBKV
7+
ZzMO+1PVULfnQa92tHtahNsTGI5367WEALn9UNJLmP+jpX+3zohatUTbhlnRSruH
8+
O/Mo5mLs1XJhQpdvp8BQNksJhiTQ7FsbcjGq6gZ75SnbfUR0PyohY0LTsrql00Tz
9+
hCAEvm2TNiQ5s+PT5fFOg6Jh2ZGj1lYLQY3dDeqt9sdabvj7LANqfygbt2cf9yYn
10+
a25UTRcAN7CNdWwTEfvnOVMITzCE8F2FmKDvJR+TX30CAwEAAaMeMBwwGgYDVR0R
11+
BBMwEYIPZm9vLmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBjKz4gM4Bz
12+
JO7Ny1fwbBtraHCGYnDG8gBID3+/sQlMMFeuquJK+oc+1DOpr9wFlmgih67OszdM
13+
X2Xl/HjtHPKwNqaDHXu5bQPFT5fXzAZ8HHEOXSV9IpHaNyS7TC7bYmD/ClCZeqXU
14+
h7MBe5yPXfCCIqWyjZMZDQfT1v6J+WX3+lO9josMJCfNR5DzvJiPmSTUxrLD5SkT
15+
+7iKxhM6eI83D+I188sGc2IMinkFp8jSRTlaH8WYiOd5QQ2r8GSYNM9M3z1sK7zv
16+
0Bw3hWEQgpFbEaSH0OB72KYkMUZBqK9UoeSZWBrMXHFBNaY23tEKInEwlBGBELGc
17+
acSinK6OBC0z
2018
-----END CERTIFICATE-----

Diff for: tornado/test/test.key

+26-26
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
-----BEGIN PRIVATE KEY-----
2-
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCk9C3ciPLVubsD
3-
92oRPARUs/o90rCgQ7/9HCr6sVVROrCxEk9rNx4wK2byCBubG4gZFg66p2elCg7w
4-
eI5aGyxgJHqcQ268R1sSud59AtvF1xi6iUNNU5U2ea6Y7bTHoOUijYx28aTr6VMM
5-
EEuG6+Fo2Siskxol6RZBVLcvrWFVN+f1Nssr63lh5s6R8f9ubR+ztkQQEVcCWCjh
6-
j7ZR+tpUI6SFn1X5JWOCKfB45dweqwcT8gVC5lUMNKoKGZ+uPKGuKC47NMJ2Ac9N
7-
YFYzTAx3o+dkrAf4nh+1tU8RTM6jpXLx4eNbhW0CT4HF7K7kkvWeLcM0lpFKdr5P
8-
wv/+Es9/AgMBAAECggEABi6AaXtYXloPgB6NgwfUwbfc8OQsalUfpMShd7OdluW0
9-
KW6eO05de0ClIvzay/1EJGyHMMeFQtIVrT1XWFkcWJ4FWkXMqJGkABenFtg8lDVz
10-
X8o1E3jGZrw4ptKBq9mDvL/BO9PiclTUH+ecbPn6AIvi0lTQ7grGIryiAM9mjmLy
11-
jpCwoutF2LD4RPNg8vqWe/Z1rQw5lp8FOHhRwPooHHeoq1bSrp8dqvVAwAam7Mmf
12-
uFgI8jrNycPgr2cwEEtbq2TQ625MhVnCpwT+kErmAStfbXXuqv1X1ZZgiNxf+61C
13-
OL0bhPRVIHmmjiK/5qHRuN4Q5u9/Yp2SJ4W5xadSQQKBgQDR7dnOlYYQiaoPJeD/
14-
7jcLVJbWwbr7bE19O/QpYAtkA/FtGlKr+hQxPhK6OYp+in8eHf+ga/NSAjCWRBoh
15-
MNAVCJtiirHo2tFsLFOmlJpGL9n3sX8UnkJN90oHfWrzJ8BZnXaSw2eOuyw8LLj+
16-
Q+ISl6Go8/xfsuy3EDv4AP1wCwKBgQDJJ4vEV3Kr+bc6N/xeu+G0oHvRAWwuQpcx
17-
9D+XpnqbJbFDnWKNE7oGsDCs8Qjr0CdFUN1pm1ppITDZ5N1cWuDg/47ZAXqEK6D1
18-
z13S7O0oQPlnsPL7mHs2Vl73muAaBPAojFvceHHfccr7Z94BXqKsiyfaWz6kclT/
19-
Nl4JTdsC3QKBgQCeYgozL2J/da2lUhnIXcyPstk+29kbueFYu/QBh2HwqnzqqLJ4
20-
5+t2H3P3plQUFp/DdDSZrvhcBiTsKiNgqThEtkKtfSCvIvBf4a2W/4TJsW6MzxCm
21-
2KQDuK/UqM4Y+APKWN/N6Lln2VWNbNyBkWuuRVKFatccyJyJnSjxeqW7cwKBgGyN
22-
idCYPIrwROAHLItXKvOWE5t0ABRq3TsZC2RkdA/b5HCPs4pclexcEriRjvXrK/Yt
23-
MH94Ve8b+UftSUQ4ytjBMS6MrLg87y0YDhLwxv8NKUq65DXAUOW+8JsAmmWQOqY3
24-
MK+m1BT4TMklgVoN3w3sPsKIsSJ/jLz5cv/kYweFAoGAG4iWU1378tI2Ts/Fngsv
25-
7eoWhoda77Y9D0Yoy20aN9VdMHzIYCBOubtRPEuwgaReNwbUBWap01J63yY/fF3K
26-
8PTz6covjoOJqxQJOvM7nM0CsJawG9ccw3YXyd9KgRIdSt6ooEhb7N8W2EXYoKl3
27-
g1i2t41Q/SC3HUGC5mJjpO8=
2+
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCo3wC/IUNRucqE
3+
dvQ9DcKoS9xBPUUoWC78yr0jYTnHkXt/dFySwIvDzfF7/C5JqDCE2i3UQ7QcF3Hc
4+
PpHx43H12dHDkSZMSRXZP9/ErIgSlWczDvtT1VC350GvdrR7WoTbExiOd+u1hAC5
5+
/VDSS5j/o6V/t86IWrVE24ZZ0Uq7hzvzKOZi7NVyYUKXb6fAUDZLCYYk0OxbG3Ix
6+
quoGe+Up231EdD8qIWNC07K6pdNE84QgBL5tkzYkObPj0+XxToOiYdmRo9ZWC0GN
7+
3Q3qrfbHWm74+ywDan8oG7dnH/cmJ2tuVE0XADewjXVsExH75zlTCE8whPBdhZig
8+
7yUfk199AgMBAAECggEBAIGFmXL/Nj0GvVfgTPBPD5A5rxOyxMpu6IsnjO4H8mMp
9+
KInXW/GLESf7W053W6FPCPe8yA3YZ9pr+P6uVw4qHwwsJwFS4Qb9v25D2YNluXBX
10+
ezHkOcxQ/novO2gzKba69M961Ajh3b35Iv2EV2sUZKMehx9wgU6AFCxeG6vkJOez
11+
UCX0WG467cdo4alfe/oQZLioU3t+GGCb23m13B9xaN2tqONNh2E2yp73MVJ1Q74R
12+
HVBkQxciHd3iJee5/4AGUJl9TLv8wAT1cf3OhcGlvOlcfSYtuNUY32TPWit1Or1y
13+
i9fPkjo8SBw52TN5RRmjIlpNMxeK+G4+XtO1Y47TlZkCgYEA3Y+NK8mz9kTnorkf
14+
R7CSOAaiste8c3CJbbbaTk7oTxK4MISL1RX+sFK65cnV54Wo75hCZxsjV2oEQ+4r
15+
UOGw1JxcV16V6iP/AaQS41xsxZca/xnC//YojBN6kP+OV+/ByF4HNs5eDN6uHg0y
16+
OOfBWi6oc449CFFMxVnrQ0SymaMCgYEAwx7M9xQ1eth902Wq2VNszFIgdgptGYhj
17+
XbWsAFYKII+aUXC92sVL5sO33vNyhBbyMN1aSRXYe8B5EnwsP31o5csrHQw6i/Dp
18+
jUx1AUBYkNPgL9ctqlTQf1nb0LenGlCUBD6jrSrJVHeOF4y+HIZHXNZ++otH7+eu
19+
b3dbHgV/9F8CgYBTopO0utAvH3WdDGqNYk7fzUlvX1ao8Qs/mi2wL8MrzjIvRmmO
20+
h137607X3Sfc3KyXvQ8b4relkMSJbAd34aohp+CHrpHCr9HcKbZjkwkQUWkEcRIW
21+
EzLdJaE3yPBPq5an7y6j9qS0EP8DIxIZPwrS4xf9fuz1DdOAD+BqJS2SJwKBgQCt
22+
zZzTpduxbnA+Qrx503cBVVJ28viVmsiwK2hn8DwbHu9eBegHnGDs0H/Th9UE1g+r
23+
+TA4E85/BUaTcapUb5hlwKDJwh/QkaroYyeCEtgRQbnbw3d41w3Vsqw78atWpFoE
24+
oetYD9nAdLJMReD+NZoRlzsKX9CXYS8fORkf19RPTwKBgQCQdvDMicrtnJ4U2MOA
25+
y+59K7V77VRfHLecjAMGblBGmrtoQSBvQiFznXm0DUOSy3eZRITwe01/t+RUDhx9
26+
MVLlyNzwRCVHzPe7kUI10GM4W5ZKAf8f/t0KjBrQBeYtRUOEI3QVzsVzc1hY8Fv8
27+
YHOhmI8Tdd2biF+lqXKC6vGlvQ==
2828
-----END PRIVATE KEY-----

Diff for: tornado/testing.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,9 @@ def get_ssl_options(self) -> Dict[str, Any]:
517517
def default_ssl_options() -> Dict[str, Any]:
518518
# Testing keys were generated with:
519519
# openssl req -new -keyout tornado/test/test.key \
520-
# -out tornado/test/test.crt -nodes -days 3650 -x509
520+
# -out tornado/test/test.crt \
521+
# -nodes -days 3650 -x509 \
522+
# -subj "/CN=foo.example.com" -addext "subjectAltName = DNS:foo.example.com"
521523
module_dir = os.path.dirname(__file__)
522524
return dict(
523525
certfile=os.path.join(module_dir, "test", "test.crt"),

0 commit comments

Comments
 (0)