Added SSL-certificate-based client authentication.
This commit is contained in:
parent
5e19bc3f96
commit
090eaa09ee
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue