diff options
author | Nobuaki Sukegawa <nsuke@apache.org> | 2016-02-04 15:09:41 +0900 |
---|---|---|
committer | Nobuaki Sukegawa <nsuke@apache.org> | 2016-02-04 22:20:00 +0900 |
commit | f39f7dbd26fe090f0fc6566c100ca7adc9ace714 (patch) | |
tree | 1c9d7094eeb14b226c8b33027543dd3f4c43aaa3 | |
parent | 25536ad83a85cfda6d5388278e4e378f2d4df73e (diff) | |
download | thrift-f39f7dbd26fe090f0fc6566c100ca7adc9ace714.tar.gz |
THRIFT-3599 Validate client IP address against cert's SubjectAltName
-rw-r--r-- | lib/py/src/transport/TSSLSocket.py | 20 | ||||
-rw-r--r-- | lib/py/test/test_sslsocket.py | 33 | ||||
-rwxr-xr-x | test/keys/README.md | 20 | ||||
-rw-r--r-- | test/keys/client_v3.crt | 24 | ||||
-rw-r--r-- | test/keys/client_v3.key | 27 |
5 files changed, 118 insertions, 6 deletions
diff --git a/lib/py/src/transport/TSSLSocket.py b/lib/py/src/transport/TSSLSocket.py index dfaa5dbe8..763e5338a 100644 --- a/lib/py/src/transport/TSSLSocket.py +++ b/lib/py/src/transport/TSSLSocket.py @@ -372,6 +372,11 @@ class TSSLServerSocket(TSocket.TServerSocket, TSSLBase): Alternative keyword arguments: (Python 2.7.9 or later) ``ssl_context``: ssl.SSLContext to be used for SSLContext.wrap_socket ``server_hostname``: Passed to SSLContext.wrap_socket + + Common keyword argument: + ``validate_callback`` (cert, hostname) -> None: + Called after SSL handshake. Can raise when hostname does not + match the cert. """ if args: if len(args) > 3: @@ -389,6 +394,8 @@ class TSSLServerSocket(TSocket.TServerSocket, TSSLBase): kwargs['certfile'] = 'cert.pem' unix_socket = kwargs.pop('unix_socket', None) + self._validate_callback = \ + kwargs.pop('validate_callback', match_hostname) TSSLBase.__init__(self, True, None, kwargs) TSocket.TServerSocket.__init__(self, host, port, unix_socket) @@ -419,6 +426,19 @@ class TSSLServerSocket(TSocket.TServerSocket, TSSLBase): # Instead, return None, and let the TServer instance deal with it in # other exception handling. (but TSimpleServer dies anyway) return None + + if self._should_verify: + client.peercert = client.getpeercert() + try: + self._validate_callback(client.peercert, addr[0]) + client.is_valid = True + except Exception: + logger.warn('Failed to validate client certificate address', + exc_info=True) + client.close() + plain_client.close() + return None + result = TSocket.TSocket() result.setHandle(client) return result diff --git a/lib/py/test/test_sslsocket.py b/lib/py/test/test_sslsocket.py index fe03961a2..98d47ae31 100644 --- a/lib/py/test/test_sslsocket.py +++ b/lib/py/test/test_sslsocket.py @@ -35,8 +35,11 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(SCRIPT_DIR))) SERVER_PEM = os.path.join(ROOT_DIR, 'test', 'keys', 'server.pem') SERVER_CERT = os.path.join(ROOT_DIR, 'test', 'keys', 'server.crt') SERVER_KEY = os.path.join(ROOT_DIR, 'test', 'keys', 'server.key') -CLIENT_CERT = os.path.join(ROOT_DIR, 'test', 'keys', 'client.crt') -CLIENT_KEY = os.path.join(ROOT_DIR, 'test', 'keys', 'client.key') +CLIENT_CERT_NO_IP = os.path.join(ROOT_DIR, 'test', 'keys', 'client.crt') +CLIENT_KEY_NO_IP = os.path.join(ROOT_DIR, 'test', 'keys', 'client.key') +CLIENT_CERT = os.path.join(ROOT_DIR, 'test', 'keys', 'client_v3.crt') +CLIENT_KEY = os.path.join(ROOT_DIR, 'test', 'keys', 'client_v3.key') +CLIENT_CA = os.path.join(ROOT_DIR, 'test', 'keys', 'CA.pem') TEST_PORT = 23458 TEST_ADDR = '/tmp/.thrift.domain.sock.%d' % TEST_PORT @@ -188,6 +191,24 @@ class TSSLSocketTest(unittest.TestCase): server = TSSLServerSocket( port=TEST_PORT, cert_reqs=ssl.CERT_REQUIRED, keyfile=SERVER_KEY, certfile=SERVER_CERT, ca_certs=CLIENT_CERT) + client = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_NONE, certfile=SERVER_CERT, keyfile=SERVER_KEY) + self._assert_connection_failure(server, client) + + server = TSSLServerSocket( + port=TEST_PORT, cert_reqs=ssl.CERT_REQUIRED, keyfile=SERVER_KEY, + certfile=SERVER_CERT, ca_certs=CLIENT_CA) + client = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_NONE, certfile=CLIENT_CERT_NO_IP, keyfile=CLIENT_KEY_NO_IP) + self._assert_connection_failure(server, client) + + server = TSSLServerSocket( + port=TEST_PORT, cert_reqs=ssl.CERT_REQUIRED, keyfile=SERVER_KEY, + certfile=SERVER_CERT, ca_certs=CLIENT_CA) + client = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_NONE, certfile=CLIENT_CERT, keyfile=CLIENT_KEY) + self._assert_connection_success(server, client) + + server = TSSLServerSocket( + port=TEST_PORT, cert_reqs=ssl.CERT_OPTIONAL, keyfile=SERVER_KEY, + certfile=SERVER_CERT, ca_certs=CLIENT_CA) client = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_NONE, certfile=CLIENT_CERT, keyfile=CLIENT_KEY) self._assert_connection_success(server, client) @@ -264,14 +285,16 @@ class TSSLSocketTest(unittest.TestCase): return server_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) server_context.load_cert_chain(SERVER_CERT, SERVER_KEY) - server_context.load_verify_locations(CLIENT_CERT) + server_context.load_verify_locations(CLIENT_CA) + server_context.verify_mode = ssl.CERT_REQUIRED + server = TSSLServerSocket(port=TEST_PORT, ssl_context=server_context) client_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) client_context.load_cert_chain(CLIENT_CERT, CLIENT_KEY) client_context.load_verify_locations(SERVER_CERT) - - server = TSSLServerSocket(port=TEST_PORT, ssl_context=server_context) + client_context.verify_mode = ssl.CERT_REQUIRED client = TSSLSocket('localhost', TEST_PORT, ssl_context=client_context) + self._assert_connection_success(server, client) if __name__ == '__main__': diff --git a/test/keys/README.md b/test/keys/README.md index 15faa5106..eb67fd810 100755 --- a/test/keys/README.md +++ b/test/keys/README.md @@ -50,6 +50,24 @@ export certificate in PEM format for OpenSSL usage openssl pkcs12 -in client.p12 -out client.pem -clcerts +### create client key and certificate with altnames + +copy openssl.cnf from your system e.g. /etc/ssl/openssl.cnf and append following to the end of [ v3_req ] + + subjectAltName=@alternate_names + + [ alternate_names ] + IP.1=127.0.0.1 + IP.2=::1 + +create a signing request: + + openssl req -new -key client_v3.key -out client_v3.csr -config openssl.cnf \ + -subj "/C=US/ST=Maryland/L=Forest Hill/O=The Apache Software Foundation/OU=Apache Thrift/CN=localhost" -extensions v3_req + +sign the client certificate with the server.key + + openssl x509 -req -days 3000 -in client_v3.csr -CA CA.pem -CAkey server.key -set_serial 01 -out client_v3.crt -extensions v3_req -extfile openssl.cnf ## Java key and certificate import Java Test Environment uses key and trust store password "thrift" without the quotes @@ -65,7 +83,7 @@ list truststore entries delete an entry - keytool -delete -storepass thrift -keystore ../../lib/java/test/.truststore -alias ssltest + keytool -delete -storepass thrift -keystore ../../lib/java/test/.truststore -alias ssltest import certificate into truststore diff --git a/test/keys/client_v3.crt b/test/keys/client_v3.crt new file mode 100644 index 000000000..6703c7a98 --- /dev/null +++ b/test/keys/client_v3.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID9jCCAt6gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE1hcnlsYW5kMRQwEgYDVQQHDAtGb3Jlc3QgSGlsbDEnMCUGA1UE +CgweVGhlIEFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW9uMRYwFAYDVQQLDA1BcGFj +aGUgVGhyaWZ0MRIwEAYDVQQDDAlsb2NhbGhvc3QxJDAiBgkqhkiG9w0BCQEWFWRl +dkB0aHJpZnQuYXBhY2hlLm9yZzAeFw0xNjAyMDMxOTAwMDlaFw0yNDA0MjExOTAw +MDlaMIGLMQswCQYDVQQGEwJVUzERMA8GA1UECAwITWFyeWxhbmQxFDASBgNVBAcM +C0ZvcmVzdCBIaWxsMScwJQYDVQQKDB5UaGUgQXBhY2hlIFNvZnR3YXJlIEZvdW5k +YXRpb24xFjAUBgNVBAsMDUFwYWNoZSBUaHJpZnQxEjAQBgNVBAMMCWxvY2FsaG9z +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZ0wiQnXg5QMZZWugd/ +O3woatyHuczJuFSmYiRGWLr3PugB+xtvjy0rTcE2MNx/bdsVxrapCKA+tMFORbEl +sF6jk0H+B7BzGoIwHr6N8GP1VOoA2esrhsNEz22aJI00VaFTFE8G/qgFcihyaVWH +ZsLa3MakOzFUmOBaV2tLBjCjaznqXw3eo3XwUI0BkgS9b9vqXjScmfWXDw5+1is4 +bCgumG2zj9EpLypc9qCGNKFBO2YIg0XsIIJ8RprlianjL6P4MfC6GPOyW4NbZaLd +ESv/bumpVyuV/C/xqkPahvOwBuPE1loxZZPx6Qv368qn7SVNVZOLyX722spooA5G +6csCAwEAAaM9MDswCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwIQYDVR0RBBowGIcE +fwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAMigk3sHI +nDY9E0V5zaZpN4Y8NoxaSSPN/hJ1abae2cp5v/dpsVAn9KgRLt4YEcaxmShblx7j +g3/8Bk18H9UXNKimfw27oWGFLgqv72rJrF4KfLQLR0PH3d44qmgbX8K204YtVQu2 +rp/3uMTnptkuAkSyA8hsFG7Y1p/wR3I57SENt2xB1f2nxyQ5vrdqbEXdKbORasM5 +hn0irWempVVd28RfXQAmJXhhHDrtIbYrSYUw7TKcG9Kl+VeGf+A+sk4ewB4wB5yU +Pgg57hzq0DW5BwMCl5UroY+umzB3FSngWxiEzEdLn6PkzZavnRndwXD/ZcCN4qSm +4jry8siM9ttT2g== +-----END CERTIFICATE----- diff --git a/test/keys/client_v3.key b/test/keys/client_v3.key new file mode 100644 index 000000000..b989f738e --- /dev/null +++ b/test/keys/client_v3.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtnTCJCdeDlAxlla6B387fChq3Ie5zMm4VKZiJEZYuvc+6AH7 +G2+PLStNwTYw3H9t2xXGtqkIoD60wU5FsSWwXqOTQf4HsHMagjAevo3wY/VU6gDZ +6yuGw0TPbZokjTRVoVMUTwb+qAVyKHJpVYdmwtrcxqQ7MVSY4FpXa0sGMKNrOepf +Dd6jdfBQjQGSBL1v2+peNJyZ9ZcPDn7WKzhsKC6YbbOP0SkvKlz2oIY0oUE7ZgiD +RewggnxGmuWJqeMvo/gx8LoY87Jbg1tlot0RK/9u6alXK5X8L/GqQ9qG87AG48TW +WjFlk/HpC/fryqftJU1Vk4vJfvbaymigDkbpywIDAQABAoIBAQCJpyUhaaIIYnBG +4D+RkGgsj8Gvh6ah3j53ft/kRj6DMC4BlB0C4fO/PEB5WI0cjfcvpwo4nOapHyX4 +ATmLIMgjXn2m+CSM9wo01mEbmrKWd20M7n96cWhGwg9MvVJ+RdGk2K0lwj02PoWW +Blt576GTuNN/+j++Q/jiqsXxaLTO0/Wj+4b2gQh3n8I0u6bkolDLoERKIdrLGHH+ +FU3sk8bpUhHmeiUTfwwci+juhtOY9e30AEst6xakCHbq1lRRyEYPtWL7oLds6yv0 +UAKP7wS9Yl6dcekXSF1RZpB+fovTW+qPYn8aEuksaMz0wK96FCOjVNGYxMp+Xnvl +sKx63UZBAoGBAOCbCbJtO0HsgIauvCvGZ50aZ1vDvQReCwri4ioutEg4JCAXHEsX ++axz2J5j3UEQhGKr0EX9BG6YbxGW0Mmjf3QxeRB+0WLpMMY2SFt93oC2R1AX9l0I +h50O6tYv5SXm96pKxwRz01d84mCJgwn/G+cZ/EJj4rfZsNbQst6JQFvzAoGBAM/1 +gLVQt5l+IK+6s68EnADI66i7cKe6sj3rFRTahZJxL2vY28J9EB2mF/XEgARSNJQV +X/H9zDrwKm9MX87/eCH2nEbc+5qSGpDPQm482C9DqsMitxCKD8bble1BlpjFb8hr +R0Q3v5q8u5uomLBds5eUBeRKMtu9tOMA9KRSDGjJAoGAF44K2Ux9T2+XFwjSMSEQ +krhHKKeBdijKrayXnWbif0Rr/XWPAQ0VoRFRIWNFu+IYkCSGpiBfy51u4IBZixv7 +bNsXYDR8jwv3koH02qt7nzH+jpbEvoL7fewnkqjZNj1fsds/vebLvjwZnZguRukb +KwRdoTTKfQ92bUDb0VzBhCMCgYB7H+3ObDXoCQctRCsyilYbGNp+EkxG4oC5rD/V +EvRWmfDrt3+VjRpHk5lIB8mLxWgf7O/bhNqwYpWdQ+jN0++6nBo20oudHrff2PaJ +8jhE85lc42bjwfpJUKVZzaVuWicu0GVnfGJTKT8ikBWnBjNYoWlDmrK164H3jQ9L +YtC6EQKBgQCabFXXHx5cIJ2XOm4K/nTOG7ClvD80xapqyGroQd9E/cJUHHPp/wQ4 +c1dMO5EViM7JRsKfxkl9vM5o9IM7swlYh4EMFSLJNjzgOY9XVkvQh0uGbiJOBO4f +inUuWn1YWUj/HFtrT+0No+cYvZVcMKrFAy3K/AwpTbfKCk6roullNA== +-----END RSA PRIVATE KEY----- |