nova/nova/console/rfb/authvencrypt.py

151 lines
5.2 KiB
Python

# Copyright (c) 2014-2016 Red Hat, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import enum
import ssl
import struct
from oslo_config import cfg
from oslo_log import log as logging
from nova.console.rfb import auth
from nova import exception
from nova.i18n import _
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class AuthVeNCryptSubtype(enum.IntEnum):
"""Possible VeNCrypt subtypes.
From https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst
"""
PLAIN = 256
TLSNONE = 257
TLSVNC = 258
TLSPLAIN = 259
X509NONE = 260
X509VNC = 261
X509PLAIN = 262
X509SASL = 263
TLSSASL = 264
class RFBAuthSchemeVeNCrypt(auth.RFBAuthScheme):
"""A security proxy helper which uses VeNCrypt.
This security proxy helper uses the VeNCrypt security
type to achieve SSL/TLS-secured VNC. It supports both
standard SSL/TLS encryption and SSL/TLS encryption with
x509 authentication.
Refer to https://www.berrange.com/~dan/vencrypt.txt for
a brief overview of the protocol.
"""
def security_type(self):
return auth.AuthType.VENCRYPT
def security_handshake(self, compute_sock):
def recv(num):
b = compute_sock.recv(num)
if len(b) != num:
reason = _("Short read from compute socket, wanted "
"%(wanted)d bytes but got %(got)d") % {
'wanted': num, 'got': len(b)}
raise exception.RFBAuthHandshakeFailed(reason=reason)
return b
# get the VeNCrypt version from the server
maj_ver = ord(recv(1))
min_ver = ord(recv(1))
LOG.debug("Server sent VeNCrypt version "
"%(maj)s.%(min)s", {'maj': maj_ver, 'min': min_ver})
if maj_ver != 0 or min_ver != 2:
reason = _("Only VeNCrypt version 0.2 is supported by this "
"proxy, but the server wanted to use version "
"%(maj)s.%(min)s") % {'maj': maj_ver, 'min': min_ver}
raise exception.RFBAuthHandshakeFailed(reason=reason)
# use version 0.2
compute_sock.sendall(b"\x00\x02")
can_use_version = ord(recv(1))
if can_use_version > 0:
reason = _("Server could not use VeNCrypt version 0.2")
raise exception.RFBAuthHandshakeFailed(reason=reason)
# get the supported sub-auth types
sub_types_cnt = ord(recv(1))
sub_types_raw = recv(sub_types_cnt * auth.SUBTYPE_LENGTH)
sub_types = struct.unpack('!' + str(sub_types_cnt) + 'I',
sub_types_raw)
LOG.debug("Server supports VeNCrypt sub-types %s", sub_types)
# We use X509None as we're only seeking to encrypt the channel (ruling
# out PLAIN) and prevent MITM (ruling out TLS*, which uses trivially
# MITM'd Anonymous Diffie Hellmann (DH) cyphers)
if AuthVeNCryptSubtype.X509NONE not in sub_types:
reason = _("Server does not support the x509None (%s) VeNCrypt"
" sub-auth type") % \
AuthVeNCryptSubtype.X509NONE
raise exception.RFBAuthHandshakeFailed(reason=reason)
LOG.debug("Attempting to use the x509None (%s) auth sub-type",
AuthVeNCryptSubtype.X509NONE)
compute_sock.sendall(struct.pack(
'!I', AuthVeNCryptSubtype.X509NONE))
# NB(sross): the spec is missing a U8 here that's used in
# multiple implementations (e.g. QEMU, GTK-VNC). 1 means
# acceptance, 0 means failure (unlike the rest of RFB)
auth_accepted = ord(recv(1))
if auth_accepted == 0:
reason = _("Server didn't accept the requested auth sub-type")
raise exception.RFBAuthHandshakeFailed(reason=reason)
LOG.debug("Server accepted the requested sub-auth type")
if (CONF.vnc.vencrypt_client_key and
CONF.vnc.vencrypt_client_cert):
client_key = CONF.vnc.vencrypt_client_key
client_cert = CONF.vnc.vencrypt_client_cert
else:
client_key = None
client_cert = None
try:
wrapped_sock = ssl.wrap_socket(
compute_sock,
keyfile=client_key,
certfile=client_cert,
server_side=False,
cert_reqs=ssl.CERT_REQUIRED,
ca_certs=CONF.vnc.vencrypt_ca_certs)
LOG.info("VeNCrypt security handshake accepted")
return wrapped_sock
except ssl.SSLError as e:
reason = _("Error establishing TLS connection to server: %s") % (
str(e))
raise exception.RFBAuthHandshakeFailed(reason=reason)