Added SSL-certificate-based client authentication.

This commit is contained in:
Hermann Höhne 2017-08-29 21:24:32 +02:00
parent 5e19bc3f96
commit 090eaa09ee
3 changed files with 54 additions and 6 deletions

View File

@ -81,3 +81,23 @@ class ExpectOrigin(object):
origin = headers.get('Origin', None)
if origin is None or origin not in self.source:
raise InvalidOriginError(expected=self.source, actual=origin)
class ClientCertAuth(object):
"""Verifies client by SSL certificate. Specify src as whitespace separated list of common names."""
def __init__(self, src=None):
if src is None:
self.source = []
else:
self.source = src.split()
def authenticate(self, headers, target_host, target_port):
try:
if (headers.get('SSL_CLIENT_S_DN_CN') not in self.source):
raise AuthenticationError(response_code=403)
except AuthenticationError:
# re-raise AuthenticationError (raised by common name not in configured source list)
raise
except:
# deny access in case any error occurs (i.e. no data provided)
raise AuthenticationError(response_code=403)

View File

@ -60,6 +60,20 @@ Traffic Legend:
self.server.target_port = port
if self.server.auth_plugin:
try:
# get client certificate data
client_cert_data = self.request.getpeercert()
# extract subject information
client_cert_subject = client_cert_data['subject']
# flatten data structure
client_cert_subject = dict([x[0] for x in client_cert_subject])
# add common name to headers (apache +StdEnvVars style)
self.headers['SSL_CLIENT_S_DN_CN'] = client_cert_subject['commonName']
except:
# not a SSL connection or client presented no certificate with valid data
pass
try:
self.server.auth_plugin.authenticate(
headers=self.headers, target_host=self.server.target_host,
@ -392,6 +406,12 @@ def websockify_init():
help="disallow non-encrypted client connections")
parser.add_option("--ssl-target", action="store_true",
help="connect to SSL target as SSL client")
parser.add_option("--verify-client", action="store_true",
help="require encrypted client to present a valid certificate")
parser.add_option("--cafile", metavar="FILE",
help="file of concatenated certificates of authorities trusted "
"for validating clients (only effective with --verify-client). "
"If omitted, system default list of CAs is used.")
parser.add_option("--unix-target",
help="connect to unix socket target", metavar="FILE")
parser.add_option("--inetd",

View File

@ -319,6 +319,7 @@ class WebSockifyServer(object):
def __init__(self, RequestHandlerClass, listen_fd=None,
listen_host='', listen_port=None, source_is_ipv6=False,
verbose=False, cert='', key='', ssl_only=None,
verify_client=False, cafile=None,
daemon=False, record='', web='',
file_only=False,
run_once=False, timeout=0, idle_timeout=0, traffic=False,
@ -333,6 +334,7 @@ class WebSockifyServer(object):
self.listen_port = listen_port
self.prefer_ipv6 = source_is_ipv6
self.ssl_only = ssl_only
self.verify_client = verify_client
self.daemon = daemon
self.run_once = run_once
self.timeout = timeout
@ -352,13 +354,15 @@ class WebSockifyServer(object):
# Make paths settings absolute
self.cert = os.path.abspath(cert)
self.key = self.web = self.record = ''
self.key = self.web = self.record = self.cafile = ''
if key:
self.key = os.path.abspath(key)
if web:
self.web = os.path.abspath(web)
if record:
self.record = os.path.abspath(record)
if cafile:
self.cafile = os.path.abspath(cafile)
if self.web:
os.chdir(self.web)
@ -518,7 +522,6 @@ class WebSockifyServer(object):
"""
ready = select.select([sock], [], [], 3)[0]
if not ready:
raise self.EClose("ignoring socket not ready")
# Peek, but do not read the data so that we have a opportunity
@ -538,11 +541,16 @@ class WebSockifyServer(object):
% self.cert)
retsock = None
try:
retsock = ssl.wrap_socket(
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile=self.cert, keyfile=self.key)
if self.verify_client:
context.verify_mode = ssl.CERT_REQUIRED
context.set_default_verify_paths()
if self.cafile:
context.load_verify_locations(cafile=self.cafile)
retsock = context.wrap_socket(
sock,
server_side=True,
certfile=self.cert,
keyfile=self.key)
server_side=True)
except ssl.SSLError:
_, x, _ = sys.exc_info()
if x.args[0] == ssl.SSL_ERROR_EOF: