1+ # This integration test verifies that TLS hostname verification is enforced
2+ # when connecting to AWS IoT Core.
3+ #
4+ # It tests two scenarios:
5+ # 1. A connection to the correct endpoint hostname succeeds (hostname matches certificate).
6+ # 2. A connection using the endpoint's IP address (hostname mismatch) fails with an SSL error
7+ # because the certificate's CN/SAN contains the DNS name but not the IP address.
8+ #
9+ # Without the hostname verification fix, scenario 2 would have succeeded because
10+ # only the CA chain was validated, not the hostname. With the fix, it correctly rejects.
11+
12+
13+ import random
14+ import string
15+ import time
16+ import ssl
17+ import socket
18+ import sys
19+ sys .path .insert (0 , "./test-integration/IntegrationTests/TestToolLibrary" )
20+ sys .path .insert (0 , "./test-integration/IntegrationTests/TestToolLibrary/SDKPackage" )
21+
22+ import TestToolLibrary .checkInManager as checkInManager
23+ import TestToolLibrary .MQTTClientManager as MQTTClientManager
24+ from TestToolLibrary .skip import skip_when_match
25+ from TestToolLibrary .skip import ModeIsALPN
26+ from TestToolLibrary .skip import Python2VersionLowerThan
27+ from TestToolLibrary .skip import Python3VersionLowerThan
28+
29+
30+ CLIENT_ID = "integrationTestHostnameVerification_" + "" .join (random .choice (string .ascii_lowercase ) for i in range (4 ))
31+
32+
33+ ############################################################################
34+ # Main #
35+ # Check inputs
36+ myCheckInManager = checkInManager .checkInManager (2 )
37+ myCheckInManager .verify (sys .argv )
38+
39+ host = myCheckInManager .host
40+ rootCA = "./test-integration/Credentials/rootCA.crt"
41+ certificate = "./test-integration/Credentials/certificate.pem.crt"
42+ privateKey = "./test-integration/Credentials/privateKey.pem.key"
43+ mode = myCheckInManager .mode
44+
45+ ############################################################################
46+ # Test 1: Connection to correct hostname should SUCCEED
47+ ############################################################################
48+ print ("=" * 60 )
49+ print ("Test 1: Connection to correct hostname should SUCCEED" )
50+ print ("=" * 60 )
51+
52+ myMQTTClientManager = MQTTClientManager .MQTTClientManager ()
53+ client = myMQTTClientManager .create_connected_mqtt_core (CLIENT_ID , host , rootCA , certificate , privateKey , mode = mode )
54+
55+ if client is None :
56+ print ("FAILED: Could not connect to correct hostname: " + host )
57+ exit (4 )
58+
59+ print ("PASSED: Successfully connected to: " + host )
60+ client .disconnect ()
61+ time .sleep (1 )
62+
63+ ############################################################################
64+ # Test 2: Connection using IP address (hostname mismatch) should FAIL
65+ ############################################################################
66+ print ("" )
67+ print ("=" * 60 )
68+ print ("Test 2: Connection using IP address (hostname mismatch) should FAIL" )
69+ print ("=" * 60 )
70+
71+ # Resolve the real endpoint to its IP address
72+ try :
73+ real_ip = socket .gethostbyname (host )
74+ print ("Resolved " + host + " to IP: " + real_ip )
75+ except socket .gaierror :
76+ print ("SKIPPED: Could not resolve hostname." )
77+ exit (0 )
78+
79+ # Create an AWSIoTMQTTClient using the IP address as the endpoint.
80+ # The server's certificate SAN has the DNS name (e.g., *.iot.us-east-1.amazonaws.com)
81+ # but NOT the IP address.
82+ # With hostname verification (our fix): TLS rejects because IP is not in certificate SAN.
83+ # Without hostname verification (old bug): TLS accepts because only CA chain is checked.
84+ mismatchClient = myMQTTClientManager .create_nonconnected_mqtt_core (CLIENT_ID , real_ip , rootCA , certificate , privateKey , mode = mode )
85+
86+ connection_failed = False
87+ try :
88+ mismatchClient .connect (60 )
89+ print ("FAILED: Connection using IP address should have been rejected by hostname verification!" )
90+ mismatchClient .disconnect ()
91+ exit (4 )
92+ except ssl .SSLCertVerificationError as e :
93+ print ("PASSED: Connection correctly rejected with SSLCertVerificationError: " + str (e ))
94+ connection_failed = True
95+ except ssl .SSLError as e :
96+ print ("PASSED: Connection correctly rejected with SSLError: " + str (e ))
97+ connection_failed = True
98+ except socket .error as e :
99+ if "SSL" in str (e ) or "certificate" in str (e ).lower () or "hostname" in str (e ).lower ():
100+ print ("PASSED: Connection correctly rejected with socket error: " + str (e ))
101+ connection_failed = True
102+ else :
103+ print ("FAILED: Unexpected socket error (not SSL-related): " + str (e ))
104+ exit (4 )
105+ except Exception as e :
106+ # The SDK wraps SSL errors in its own exception types
107+ error_msg = str (e ).lower ()
108+ if "ssl" in error_msg or "certificate" in error_msg or "hostname" in error_msg or "tls" in error_msg :
109+ print ("PASSED: Connection correctly rejected: " + str (type (e ).__name__ ) + ": " + str (e ))
110+ connection_failed = True
111+ else :
112+ print ("FAILED: Unexpected exception: " + str (type (e ).__name__ ) + ": " + str (e ))
113+ exit (4 )
114+
115+ if not connection_failed :
116+ print ("FAILED: Connection using IP address was not rejected by hostname verification!" )
117+ exit (4 )
118+
119+ print ("" )
120+ print ("=" * 60 )
121+ print ("ALL TESTS PASSED: Hostname verification is working correctly." )
122+ print ("=" * 60 )
0 commit comments