Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* add more hostname checking options (defaults to off now)
* add "ssl-options" (defaults to "ALL,NO_COMPRESSION")
* always use SSLContext if available, and cache it (cached by the function returned by ssl_wrap_socket_fn)

git-svn-id: https://xpra.org/svn/Xpra/trunk@13114 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jul 28, 2016
1 parent f4aa0e1 commit 9376cc0
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 78 deletions.
20 changes: 15 additions & 5 deletions src/etc/xpra/conf.d/12_ssl.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
#ssl-cert = /path/to/cert

# Specifies which version of the SSL protocol to use:
#ssl-version = SSLv23
#ssl-version = TLSv1_2
#ssl-protocol = SSLv23
#ssl-protocol = TLSv1_2

# The ca_certs file contains a set of concatenated 'certification authority' certificates:
#ssl-ca-certs = /path/to/cacerts
#ssl-ca-certs = default
#ssl-ca-certs = /path/to/cacertfile
#ssl-ca-certs = /path/to/cacertsdir/

# Sets the available ciphers for this SSL object:
#ssl-ciphers = ALL
Expand All @@ -40,7 +42,15 @@ ssl-server-verify-mode = required
#ssl-verify-flags = DEFAULT
#ssl-verify-flags = CRL_CHECK_LEAF
#ssl-verify-flags = CHECK_CHAIN
ssl-verify-flags = CHECK_CHAIN,X509_STRICT
ssl-verify-flags = CRL_CHECK_CHAIN,X509_STRICT

# Wether to match the peer cert's hostname:
ssl-check-hostname = on
ssl-check-hostname = off

# Server hostname to check for:
ssl-server-hostname = localhost

# Set of SSL options enabled on this context:
#ssl-options = ALL
#ssl-options = OP_NO_TLSv1,NO_COMPRESSION
ssl-options = ALL,NO_COMPRESSION
8 changes: 5 additions & 3 deletions src/man/xpra.1
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ xpra \- viewer for remote, persistent X applications
[\fB\-\-ssl\fP=\fIon\fP|\fIauto\fP|\fIoff\fP]
[\fB\-\-ssl-key\fP=\fIKEYFILE\fP]
[\fB\-\-ssl-cert\fP=\fCERTFILE\fP]
[\fB\-\-ssl-version\fP=PROTOCOLVERSION\fP]
[\fB\-\-ssl-protocol\fP=PROTOCOLVERSION\fP]
[\fB\-\-ssl-ca-certs\fP=CACERTSFILE\fP]
[\fB\-\-ssl-ciphers\fP=CIPHERS\fP]
[\fB\-\-ssl-client-verify-mode\fP=\fInone\fP|\fIoptional\fP|\fIrequired\fP]
Expand Down Expand Up @@ -168,7 +168,7 @@ xpra \- viewer for remote, persistent X applications
[\fB\-\-ssl\fP=\fIon\fP|\fIauto\fP|\fIoff\fP]
[\fB\-\-ssl-key\fP=\fIKEYFILE\fP]
[\fB\-\-ssl-cert\fP=\fCERTFILE\fP]
[\fB\-\-ssl-version\fP=PROTOCOLVERSION\fP]
[\fB\-\-ssl-protocol\fP=PROTOCOLVERSION\fP]
[\fB\-\-ssl-ca-certs\fP=CACERTSFILE\fP]
[\fB\-\-ssl-ciphers\fP=CIPHERS\fP]
[\fB\-\-ssl-client-verify-mode\fP=\fInone\fP|\fIoptional\fP|\fIrequired\fP]
Expand Down Expand Up @@ -1283,7 +1283,7 @@ Summary:
The key file to use.
\fB\-\-ssl-cert\fP=\fCERTFILE\fP
Certifcate file to use, required for server SSL support.
\fB\-\-ssl-version\fP=PROTOCOLVERSION\fP
\fB\-\-ssl-protocol\fP=PROTOCOLVERSION\fP
Specifies which version of the SSL protocol to use.
\fB\-\-ssl-ca-certs\fP=CACERTSFILE\fP
The ca_certs file contains a set of concatenated 'certification
Expand All @@ -1298,6 +1298,8 @@ Whether to try to verify the server's certificates and how to behave if verifica
The flags for certificate verification operations.
\fB\-\-ssl-check-hostname\fP=\fIyes\fP|\fIno\fP
Wether to match the peer cert's hostname.
\fB\-\-ssl-options\fP=\fIoptions\fP
Set of SSL options enabled on this context.

.\" --------------------------------------------------------------------
.SH ENVIRONMENT
Expand Down
8 changes: 6 additions & 2 deletions src/xpra/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,13 +345,15 @@ def read_xpra_defaults():
"ssl" : str,
"ssl-key" : str,
"ssl-cert" : str,
"ssl-version" : str,
"ssl-protocol" : str,
"ssl-ca-certs" : str,
"ssl-ciphers" : str,
"ssl-client-verify-mode" : str,
"ssl-server-verify-mode" : str,
"ssl-verify-flags" : str,
"ssl-check-hostname": bool,
"ssl-server-hostname" : str,
"ssl-options" : str,
#int options:
"quality" : int,
"min-quality" : int,
Expand Down Expand Up @@ -541,13 +543,15 @@ def addtrailingslash(v):
"ssl" : "auto",
"ssl-key" : "",
"ssl-cert" : "",
"ssl-version" : "TLSv1_2",
"ssl-protocol" : "TLSv1_2",
"ssl-ca-certs" : "default",
"ssl-ciphers" : "DEFAULT",
"ssl-client-verify-mode" : "optional",
"ssl-server-verify-mode" : "required",
"ssl-verify-flags" : "CHECK_CHAIN,X509_STRICT",
"ssl-check-hostname": True,
"ssl-server-hostname": "localhost",
"ssl-options" : "ALL,NO_COMPRESSION",
"quality" : 0,
"min-quality" : 30,
"speed" : 0,
Expand Down
117 changes: 99 additions & 18 deletions src/xpra/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -693,8 +693,8 @@ def ignore(defaults):
group.add_option("--ssl-cert", action="store",
dest="ssl_cert", default=defaults.ssl_cert,
help="Certifcate file to use. Default: '%default'.")
group.add_option("--ssl-version", action="store",
dest="ssl_version", default=defaults.ssl_version,
group.add_option("--ssl-protocol", action="store",
dest="ssl_protocol", default=defaults.ssl_protocol,
help="Specifies which version of the SSL protocol to use. Default: '%default'.")
group.add_option("--ssl-ca-certs", action="store",
dest="ssl_ca_certs", default=defaults.ssl_ca_certs,
Expand All @@ -713,7 +713,13 @@ def ignore(defaults):
help="The flags for certificate verification operations. Default: '%default'.")
group.add_option("--ssl-check-hostname", action="store", metavar="yes|no",
dest="ssl_check_hostname", default=defaults.ssl_check_hostname,
help="Wether to match the peer cert's hostname. Default: '%s'." % enabled_str(defaults.windows))
help="Wether to match the peer cert's hostname. Default: '%s'." % enabled_str(defaults.ssl_check_hostname))
group.add_option("--ssl-server-hostname", action="store", metavar="hostname",
dest="ssl_server_hostname", default=defaults.ssl_server_hostname,
help="The server hostname to match. Default: '%default'.")
group.add_option("--ssl-options", action="store", metavar="options",
dest="ssl_options", default=defaults.ssl_options,
help="Set of SSL options enabled on this context. Default: '%default'.")

group = optparse.OptionGroup(parser, "Advanced Options",
"These options apply to both client and server. Please refer to the man page for details.")
Expand Down Expand Up @@ -1541,35 +1547,110 @@ def sockpathfail_cb(msg):
sock.settimeout(SOCKET_TIMEOUT)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, TCP_NODELAY)
if dtype == "ssl":
from xpra.net.bytestreams import init_ssl
ssl = init_ssl()
cert_reqs, ssl_version, ssl_ca_certs = parse_ssl_attributes(opts.ssl_server_verify_mode, opts.ssl_version, opts.ssl_cert)
sock = ssl.wrap_socket(sock, keyfile=opts.ssl_key, certfile=opts.ssl_cert,
server_side=False, cert_reqs=cert_reqs,
ssl_version=ssl_version, ca_certs=ssl_ca_certs,
do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=opts.ssl_ciphers)

wrap_socket = ssl_wrap_socket_fn(opts, server_side=False)
sock = wrap_socket(sock)
tcp_endpoint = (display_desc["host"], display_desc["port"])
conn = _socket_connect(sock, tcp_endpoint, display_name, dtype)
conn.timeout = SOCKET_TIMEOUT
else:
assert False, "unsupported display type in connect: %s" % dtype
return conn

def parse_ssl_attributes(verify_mode, ssl_version_str, ca_certs):

def ssl_wrap_socket_fn(opts, server_side=True):
#deal with older versions of python without SSLContext:
if server_side and not opts.ssl_cert:
raise InitException("you must specify an 'ssl-cert' file to use 'bind-ssl' sockets")
import ssl
if server_side:
verify_mode = opts.ssl_client_verify_mode
else:
verify_mode = opts.ssl_server_verify_mode
ssl_protocol, ssl_cert_reqs, ssl_verify_flags, ssl_options, ssl_ca_certs = parse_ssl_attributes(opts.ssl_protocol, verify_mode, opts.ssl_verify_flags, opts.ssl_options, opts.ssl_ca_certs)
kwargs = {
"server_side" : server_side,
"do_handshake_on_connect" : True,
"suppress_ragged_eofs" : True,
}
SSLContext = getattr(ssl, "SSLContext", None)
if SSLContext:
context = SSLContext(ssl_protocol)
context.set_ciphers(opts.ssl_ciphers)
context.verify_mode = ssl_cert_reqs
context.verify_flags = ssl_verify_flags
context.options = ssl_options
if opts.ssl_cert or opts.ssl_key:
context.load_cert_chain(certfile=opts.ssl_cert or None, keyfile=opts.ssl_key or None, password=None)
#SSLContext.wrap_socket(sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None)
if ssl_cert_reqs!=ssl.CERT_NONE:
if not server_side:
context.check_hostname = opts.ssl_check_hostname
if context.check_hostname:
if not opts.ssl_server_hostname:
raise InitException("ssl error: check-hostname is set but server-hostname is not")
kwargs["server_hostname"] = opts.ssl_server_hostname
#we will need ca data to verify the client:
context.load_default_certs()
if not opts.ssl_ca_certs or opts.ssl_ca_certs.lower()=="default":
#raise InitException("ssl-ca-certs is required for client verify mode %s" % opts.ssl_client_verify_mode)
context.set_default_verify_paths()
elif not os.path.exists(opts.ssl_ca_certs):
raise InitException("invalid ssl-ca-certs file or directory: %s" % opts.ssl_ca_certs)
elif os.path.isdir(opts.ssl_ca_certs):
context.load_verify_locations(capath=opts.ssl_ca_certs)
else:
context.load_verify_locations(cafile=opts.ssl_ca_certs)
elif opts.ssl_check_hostname and not server_side:
raise InitException("cannot check hostname with verify mode %s" % verify_mode)
wrap_socket = context.wrap_socket
else:
if sys.version_info[:2]>=(2, 7):
kwargs["ciphers"] = opts.ssl_ciphers
kwargs.update({
"cert_reqs" : ssl_cert_reqs,
"ssl_version" : ssl_protocol,
"ca_certs" : ssl_ca_certs,
"keyfile" : opts.ssl_key,
"certfile" : opts.ssl_cert,
})
#ssl.wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None)¶
wrap_socket = ssl.wrap_socket
del opts
#ensure we handle ssl exceptions as we should from now on:
from xpra.net.bytestreams import init_ssl
init_ssl()
def do_wrap_socket(tcp_socket):
return wrap_socket(tcp_socket, **kwargs)
return do_wrap_socket

def parse_ssl_attributes(protocol, verify_mode, verify_flags, options, ca_certs):
import ssl
cert_reqs = getattr(ssl, "CERT_%s" % verify_mode.upper(), None)
if cert_reqs is None:
ssl_cert_reqs = getattr(ssl, "CERT_%s" % verify_mode.upper(), None)
if ssl_cert_reqs is None:
values = [k[len("CERT_"):].lower() for k in dir(ssl) if k.startswith("CERT_")]
raise InitException("invalid ssl-server-verify-mode '%s', must be one of: %s" % (verify_mode, csv(values)))
ssl_version = getattr(ssl, "PROTOCOL_%s" % ssl_version_str, None)
if ssl_version is None:
ssl_protocol = getattr(ssl, "PROTOCOL_%s" % protocol, None)
if ssl_protocol is None:
values = [k[len("PROTOCOL_"):] for k in dir(ssl) if k.startswith("PROTOCOL_")]
raise InitException("invalid ssl-version '%s', must be one of: %s" % (ssl_version_str, csv(values)))
raise InitException("invalid ssl-version '%s', must be one of: %s" % (protocol, csv(values)))
ssl_verify_flags = 0
for x in verify_flags.split(","):
x = x.strip()
v = getattr(ssl, "VERIFY_"+x.upper(), None)
if v is None:
raise InitException("invalid ssl verify-flag: %s" % x)
ssl_verify_flags |= v
ssl_options = 0
for x in options.split(","):
x = x.strip()
v = getattr(ssl, "OP_"+x.upper(), None)
if v is None:
raise InitException("invalid ssl option: %s" % x)
ssl_options |= v
ssl_ca_certs = ca_certs
if ssl_ca_certs=="default":
ssl_ca_certs = None
return cert_reqs, ssl_version, ssl_ca_certs
return ssl_protocol, ssl_cert_reqs, ssl_verify_flags, ssl_options, ssl_ca_certs


def get_sockpath(display_desc, error_cb):
Expand Down
45 changes: 19 additions & 26 deletions src/xpra/scripts/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import traceback

from xpra.scripts.main import TCP_NODELAY, warn, no_gtk, validate_encryption
from xpra.scripts.config import InitException
from xpra.scripts.config import InitException, parse_bool
from xpra.os_util import SIGNAMES
from xpra.platform.dotxpra import DotXpra, norm_makepath, osexpand

Expand Down Expand Up @@ -362,24 +362,6 @@ def cleanup_tcp_socket():
_cleanups.append(cleanup_tcp_socket)
return "tcp", tcp_socket, (host, iport)

def setup_ssl_socket(opts, host, iport):
_, tcp_socket, host_port = setup_tcp_socket(host, iport, "SSL")
ssl_sock = do_setup_ssl_socket(opts, tcp_socket)
return "SSL", ssl_sock, host_port

def do_setup_ssl_socket(opts, tcp_socket):
if not opts.ssl_cert:
raise InitException("you must specify an 'ssl-cert' file to use 'bind-ssl' sockets")
import ssl
from xpra.scripts.main import parse_ssl_attributes
cert_reqs, ssl_version, ssl_ca_certs = parse_ssl_attributes(opts.ssl_client_verify_mode, opts.ssl_version, opts.ssl_cert)
kwargs = {}
if sys.version_info[:2]>=(2, 7):
kwargs["ciphers"] = opts.ssl_ciphers
return ssl.wrap_socket(tcp_socket, keyfile=opts.ssl_key, certfile=opts.ssl_cert,
server_side=True, cert_reqs=cert_reqs,
ssl_version=ssl_version, ca_certs=ssl_ca_certs,
do_handshake_on_connect=True, suppress_ragged_eofs=True, **kwargs)

def parse_bind_tcp(bind_tcp):
tcp_sockets = set()
Expand Down Expand Up @@ -1060,6 +1042,21 @@ def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None

mdns_recs = []
sockets = []

#SSL sockets:
wrap_socket_fn = None
ssl_opt = parse_bool("ssl", opts.ssl)
if ssl_opt is True or bind_ssl or (ssl_opt is None and opts.bind_tcp and opts.ssl_cert):
from xpra.scripts.main import ssl_wrap_socket_fn
wrap_socket_fn = ssl_wrap_socket_fn(opts, server_side=True)
for host, iport in bind_ssl:
_, tcp_socket, host_port = setup_tcp_socket(host, iport, "SSL")
socket = ("SSL", wrap_socket_fn(tcp_socket), host_port)
sockets.append(socket)
if opts.mdns:
rec = "ssl", [(host, iport)]
mdns_recs.append(rec)

# Initialize the TCP sockets before the display,
# That way, errors won't make us kill the Xvfb
# (which may not be ours to kill at that point)
Expand All @@ -1070,13 +1067,7 @@ def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None
rec = "tcp", [(host, iport)]
mdns_recs.append(rec)

for host, iport in bind_ssl:
socket = setup_ssl_socket(opts, host, iport)
sockets.append(socket)
if opts.mdns:
rec = "ssl", [(host, iport)]
mdns_recs.append(rec)

# VSOCK:
for cid, iport in bind_vsock:
socket = setup_vsock_socket(cid, iport)
sockets.append(socket)
Expand Down Expand Up @@ -1270,6 +1261,7 @@ def close_display():
_cleanups.append(close_display)

try:
app._ssl_wrap_socket = wrap_socket_fn
app.original_desktop_display = desktop_display
app.exec_cwd = cwd
app.init(opts)
Expand All @@ -1295,6 +1287,7 @@ def close_display():
app.start_on_connect = opts.start_on_connect
app.start_child_on_connect = opts.start_child_on_connect
app.exec_start_commands()
del opts

log("%s(%s)", app.init_sockets, sockets)
app.init_sockets(sockets)
Expand Down
25 changes: 1 addition & 24 deletions src/xpra/server/server_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,33 +204,9 @@ def init(self, opts):
self.server_idle_timeout = opts.server_idle_timeout
self.readonly = opts.readonly

self.init_ssl(opts)
self.init_html_proxy(opts)
self.init_auth(opts)

def init_ssl(self, opts):
v = parse_bool("ssl", opts.ssl)
if v is False or not opts.bind_tcp:
netlog("init_ssl() disabled: ssl=%s, bind-tcp=%s", v, opts.bind_tcp)
return
try:
from xpra.net.bytestreams import init_ssl
init_ssl()
from xpra.scripts.server import do_setup_ssl_socket
def ssl_wrap_socket(tcp_socket):
netlog("ssl_wrap_socket(%s)", tcp_socket)
return do_setup_ssl_socket(opts, tcp_socket)
self._ssl_wrap_socket = ssl_wrap_socket
netlog.info("SSL available on %i TCP socket%s", len(opts.bind_tcp), engs(opts.bind_tcp))
except Exception as e:
netlog("init_ssl() failed", exc_info=True)
if v is None:
#auto mode: just log it
netlog.info("SSL disabled: %s", e)
else:
#we wanted to have it, log this as an error:
netlog.error("Error: cannot enable SSL sockets:")
netlog.error(" %s", e)

def init_html_proxy(self, opts):
self._tcp_proxy = opts.tcp_proxy
Expand Down Expand Up @@ -598,6 +574,7 @@ def run_websockify():
try:
sock = self._ssl_wrap_socket(sock)
except IOError as e:
netlog("ssl wrap socket failed", exc_info=True)
conn_err(str(e))
return True
conn = SocketConnection(sock, sockname, address, target, socktype)
Expand Down

0 comments on commit 9376cc0

Please sign in to comment.