summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNobuaki Sukegawa <nsuke@apache.org>2016-02-04 15:09:41 +0900
committerNobuaki Sukegawa <nsuke@apache.org>2016-02-04 22:20:00 +0900
commitf39f7dbd26fe090f0fc6566c100ca7adc9ace714 (patch)
tree1c9d7094eeb14b226c8b33027543dd3f4c43aaa3
parent25536ad83a85cfda6d5388278e4e378f2d4df73e (diff)
downloadthrift-f39f7dbd26fe090f0fc6566c100ca7adc9ace714.tar.gz
THRIFT-3599 Validate client IP address against cert's SubjectAltName
-rw-r--r--lib/py/src/transport/TSSLSocket.py20
-rw-r--r--lib/py/test/test_sslsocket.py33
-rwxr-xr-xtest/keys/README.md20
-rw-r--r--test/keys/client_v3.crt24
-rw-r--r--test/keys/client_v3.key27
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-----