server,client: server authentication for secured channels.

3 available mechanisms:  by public key, by host name, and by certificate subject name.
In the former method, chain of trust verification is not performed.
The CA certificate files are looked for under <spice-config-dir>/spice_truststore.pem

windows <spice-config-dir>=%APPDATA%\spicec\
linux <spice-config-dir>=$HOME/.spicec/
This commit is contained in:
Yonit Halperin 2010-01-10 09:48:38 +02:00 committed by Yaniv Kamay
parent dcf326cfd5
commit 3eae1c80d9
13 changed files with 741 additions and 52 deletions

View File

@ -51,6 +51,8 @@
#define STICKY_KEY_PIXMAP ALT_IMAGE_RES_ID
#define STICKY_KEY_TIMEOUT 750
#define CA_FILE_NAME "spice_truststore.pem"
#ifdef CAIRO_CANVAS_CACH_IS_SHARED
mutex_t cairo_surface_user_data_mutex;
#endif
@ -1818,6 +1820,11 @@ bool Application::process_cmd_line(int argc, char** argv)
_peer_con_opt[i] = RedPeer::ConnectionOptions::CON_OP_INVALID;
}
_host_auth_opt.type_flags = RedPeer::HostAuthOptions::HOST_AUTH_OP_NAME;
Platform::get_spice_config_dir(_host_auth_opt.CA_file);
_host_auth_opt.CA_file += CA_FILE_NAME;
parser.begin(argc, argv);
char* val;
@ -1836,12 +1843,11 @@ bool Application::process_cmd_line(int argc, char** argv)
break;
}
case SPICE_OPT_SPORT: {
if ((port = str_to_port(val)) == -1) {
if ((sport = str_to_port(val)) == -1) {
std::cout << "invalid secure port " << val << "\n";
_exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
return false;
}
sport = port;
break;
}
case SPICE_OPT_FULL_SCREEN:

View File

@ -187,6 +187,7 @@ public:
void external_show();
void connect();
const PeerConnectionOptMap& get_con_opt_map() {return _peer_con_opt;}
const RedPeer::HostAuthOptions& get_host_auth_opt() { return _host_auth_opt;}
uint32_t get_mouse_mode();
const std::vector<int>& get_canvas_types() { return _canvas_types;}
@ -280,6 +281,7 @@ private:
private:
RedClient _client;
PeerConnectionOptMap _peer_con_opt;
RedPeer::HostAuthOptions _host_auth_opt;
std::vector<bool> _enabled_channels;
std::vector<RedScreen*> _screens;
RedScreen* _main_screen;

View File

@ -48,6 +48,8 @@ public:
static void send_quit_request();
static void get_spice_config_dir(std::string& path);
enum ThreadPriority {
PRIORITY_INVALID,
PRIORITY_TIME_CRITICAL,

View File

@ -89,6 +89,8 @@ void RedChannelBase::link(uint32_t connection_id, const std::string& password)
header.major_version);
}
_remote_minor = header.minor_version;
AutoArray<uint8_t> reply_buf(new uint8_t[header.size]);
recive(reply_buf.get(), header.size);
@ -155,11 +157,11 @@ void RedChannelBase::link(uint32_t connection_id, const std::string& password)
}
void RedChannelBase::connect(const ConnectionOptions& options, uint32_t connection_id,
uint32_t ip, std::string password)
const char* host, std::string password)
{
if (options.allow_unsecure()) {
try {
RedPeer::connect_unsecure(ip, options.unsecure_port);
RedPeer::connect_unsecure(host, options.unsecure_port);
link(connection_id, password);
return;
} catch (...) {
@ -170,16 +172,10 @@ void RedChannelBase::connect(const ConnectionOptions& options, uint32_t connecti
}
}
ASSERT(options.allow_secure());
RedPeer::connect_secure(options, ip);
RedPeer::connect_secure(options, host);
link(connection_id, password);
}
void RedChannelBase::connect(const ConnectionOptions& options, uint32_t connection_id,
const char* host, std::string password)
{
connect(options, connection_id, host_by_name(host), password);
}
void RedChannelBase::set_capability(ChannelCaps& caps, uint32_t cap)
{
uint32_t word_index = cap / 32;
@ -399,7 +395,8 @@ void RedChannel::run()
set_state(CONNECTING_STATE);
ConnectionOptions con_options(_client.get_connection_options(get_type()),
_client.get_port(),
_client.get_sport());
_client.get_sport(),
_client.get_host_auth_options());
RedChannelBase::connect(con_options, _client.get_connection_id(),
_client.get_host().c_str(),
_client.get_password().c_str());

View File

@ -55,14 +55,14 @@ public:
uint8_t get_type() { return _type;}
uint8_t get_id() { return _id;}
void connect(const ConnectionOptions& options, uint32_t connection_id, uint32_t ip,
std::string password);
void connect(const ConnectionOptions& options, uint32_t connection_id, const char *host,
std::string password);
const ChannelCaps& get_common_caps() { return _common_caps;}
const ChannelCaps& get_caps() {return _caps;}
uint32_t get_peer_minor() { return _remote_minor;}
protected:
void set_common_capability(uint32_t cap);
void set_capability(uint32_t cap);
@ -83,6 +83,8 @@ private:
ChannelCaps _remote_common_caps;
ChannelCaps _remote_caps;
uint32_t _remote_minor;
};
class SendTrigger: public EventSources::Trigger {

View File

@ -23,6 +23,15 @@
#include "utils.h"
#include "debug.h"
#ifdef __GNUC__
typedef struct __attribute__ ((__packed__)) OldRedMigrationBegin {
#else
typedef struct __declspec(align(1)) OldRedMigrationBegin {
#endif
uint16_t port;
uint16_t sport;
char host[0];
} OldRedMigrationBegin;
class MouseModeEvent: public Event {
public:
@ -156,18 +165,19 @@ void Migrate::connect_one(MigChannel& channel, const RedPeer::ConnectionOptions&
void Migrate::run()
{
uint32_t connection_id;
RedPeer::ConnectionOptions::Type conn_type;
DBG(0, "");
try {
RedPeer::ConnectionOptions con_opt(_client.get_connection_options(RED_CHANNEL_MAIN),
_port, _port);
conn_type = _client.get_connection_options(RED_CHANNEL_MAIN);
RedPeer::ConnectionOptions con_opt(conn_type, _port, _sport, _auth_options);
MigChannels::iterator iter = _channels.begin();
connection_id = _client.get_connection_id();
connect_one(**iter, con_opt, connection_id);
for (++iter; iter != _channels.end(); ++iter) {
con_opt = RedPeer::ConnectionOptions(
_client.get_connection_options((*iter)->get_type()),
_port, _sport);
conn_type = _client.get_connection_options((*iter)->get_type());
con_opt = RedPeer::ConnectionOptions(conn_type, _port, _sport, _auth_options);
connect_one(**iter, con_opt, connection_id);
}
_connected = true;
@ -199,9 +209,24 @@ void Migrate::start(const RedMigrationBegin* migrate)
{
DBG(0, "");
abort();
_host.assign(migrate->host);
_port = migrate->port ? migrate->port : -1;
_sport = migrate->sport ? migrate->sport : -1;
if ((RED_VERSION_MAJOR == 1) && (_client.get_peer_minor() < 2)) {
LOG_INFO("server minor version incompatible for destination authentication"
"(missing dest pubkey in RedMigrationBegin)");
OldRedMigrationBegin* old_migrate = (OldRedMigrationBegin*)migrate;
_host.assign(old_migrate->host);
_port = old_migrate->port ? old_migrate->port : -1;
_sport = old_migrate->sport ? old_migrate->sport : -1;;
_auth_options = _client.get_host_auth_options();
} else {
_host.assign(((char*)migrate) + migrate->host_offset);
_port = migrate->port ? migrate->port : -1;
_sport = migrate->sport ? migrate->sport : -1;
_auth_options.type_flags = RedPeer::HostAuthOptions::HOST_AUTH_OP_PUBKEY;
_auth_options.host_pubkey.assign(((uint8_t*)migrate)+ migrate->pub_key_offset,
((uint8_t*)migrate)+ migrate->pub_key_offset +
migrate->pub_key_size);
}
_password = _client._password;
Lock lock(_lock);
_running = true;
@ -433,6 +458,8 @@ void RedClient::connect()
for (; iter != end; iter++) {
_con_opt_map[(*iter).first] = (*iter).second;
}
_host_auth_opt = _application.get_host_auth_opt();
RedChannel::connect();
}

View File

@ -76,6 +76,7 @@ private:
std::string _host;
int _port;
int _sport;
RedPeer::HostAuthOptions _auth_options;
Thread* _thread;
Mutex _lock;
Condition _cond;
@ -153,6 +154,7 @@ public:
Application& get_application() { return _application;}
bool is_auto_display_res() { return _auto_display_res;}
RedPeer::ConnectionOptions::Type get_connection_options(uint32_t channel_type);
RedPeer::HostAuthOptions& get_host_auth_options() { return _host_auth_opt;}
void get_sync_info(uint8_t channel_type, uint8_t channel_id, SyncInfo& info);
void wait_for_channels(int wait_list_size, RedWaitForChannel* wait_list);
PixmapCache& get_pixmap_cache() {return _pixmap_cache;}
@ -222,6 +224,7 @@ private:
AutoRef<AgentTimer> _agent_timer;
PeerConnectionOptMap _con_opt_map;
RedPeer::HostAuthOptions _host_auth_opt;
Migrate _migrate;
Mutex _channels_lock;
typedef std::list<ChannelFactory*> Factorys;

View File

@ -16,16 +16,26 @@
*/
#include "common.h"
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include "red.h"
#include "red_peer.h"
#include "utils.h"
#include "debug.h"
#include "platform_utils.h"
typedef struct SslVerifyCbData {
RedPeer::HostAuthOptions info;
const char* host_name;
bool all_preverify_ok;
} SslVerifyCbData;
static void ssl_error()
{
unsigned long last_error = ERR_peek_last_error();
ERR_print_errors_fp(stderr);
THROW_ERR(SPICEC_ERROR_CODE_SSL_ERROR, "SSL Error");
THROW_ERR(SPICEC_ERROR_CODE_SSL_ERROR, "SSL Error:", ERR_error_string(last_error, NULL));
}
RedPeer::RedPeer()
@ -80,13 +90,15 @@ uint32_t RedPeer::host_by_name(const char* host)
return ntohl(return_value);
}
void RedPeer::connect_unsecure(uint32_t ip, int port)
void RedPeer::connect_unsecure(const char* host, int port)
{
struct sockaddr_in addr;
int no_delay;
uint32_t ip;
ASSERT(_ctx == NULL && _ssl == NULL && _peer == INVALID_SOCKET);
try {
ip = host_by_name(host);
addr.sin_port = htons(port);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(ip);
@ -120,15 +132,349 @@ void RedPeer::connect_unsecure(uint32_t ip, int port)
}
}
void RedPeer::connect_unsecure(const char* host, int port)
bool RedPeer::verify_pubkey(X509* cert, const HostAuthOptions::PublicKey& key)
{
connect_unsecure(host_by_name(host), port);
EVP_PKEY* cert_pubkey = NULL;
EVP_PKEY* orig_pubkey = NULL;
BIO* bio = NULL;
uint8_t* c_key = NULL;
int ret = 0;
if (key.empty()) {
return false;
}
ASSERT(cert);
try {
cert_pubkey = X509_get_pubkey(cert);
if (!cert_pubkey) {
THROW("reading public key from certificate failed");
}
c_key = new uint8_t[key.size()];
memcpy(c_key, &key[0], key.size());
bio = BIO_new_mem_buf((void*)c_key, key.size());
if (!bio) {
THROW("creating BIO failed");
}
orig_pubkey = d2i_PUBKEY_bio(bio, NULL);
if (!orig_pubkey) {
THROW("reading pubkey from bio failed");
}
ret = EVP_PKEY_cmp(orig_pubkey, cert_pubkey);
BIO_free(bio);
EVP_PKEY_free(orig_pubkey);
EVP_PKEY_free(cert_pubkey);
delete []c_key;
if (ret == 1) {
DBG(0, "public keys match");
return true;
} else if (ret == 0) {
DBG(0, "public keys mismatch");
return false;
} else {
DBG(0, "public keys types mismatch");
return false;
}
} catch (Exception& e) {
LOG_WARN("%s", e.what());
if (bio) {
BIO_free(bio);
}
if (orig_pubkey) {
EVP_PKEY_free(orig_pubkey);
}
if (cert_pubkey) {
EVP_PKEY_free(cert_pubkey);
}
delete []c_key;
return false;
}
}
// todo: use SSL_CTX_set_cipher_list, SSL_CTX_load_verify_location etc.
void RedPeer::connect_secure(const ConnectionOptions& options, uint32_t ip)
/* From gnutls: compare host_name against certificate, taking account of wildcards.
* return true on success or false on error.
*
* note: cert_name_size is required as X509 certs can contain embedded NULs in
* the strings such as CN or subjectAltName
*/
bool RedPeer::x509_cert_host_name_compare(const char *cert_name, int cert_name_size,
const char *host_name)
{
connect_unsecure(ip, options.secure_port);
/* find the first different character */
for (; *cert_name && *host_name && (toupper(*cert_name) == toupper(*host_name));
cert_name++, host_name++, cert_name_size--);
/* the strings are the same */
if (cert_name_size == 0 && *host_name == '\0')
return true;
if (*cert_name == '*')
{
/* a wildcard certificate */
cert_name++;
cert_name_size--;
while (true)
{
/* Use a recursive call to allow multiple wildcards */
if (RedPeer::x509_cert_host_name_compare(cert_name, cert_name_size, host_name)) {
return true;
}
/* wildcards are only allowed to match a single domain
component or component fragment */
if (*host_name == '\0' || *host_name == '.')
break;
host_name++;
}
return false;
}
return false;
}
/*
* From gnutls_x509_crt_check_hostname - compares the hostname with certificate's hostname
*
* This function will check if the given certificate's subject matches
* the hostname. This is a basic implementation of the matching
* described in RFC2818 (HTTPS), which takes into account wildcards,
* and the DNSName/IPAddress subject alternative name PKIX extension.
*
*/
bool RedPeer::verify_host_name(X509* cert, const char* host_name)
{
GENERAL_NAMES* subject_alt_names;
bool found_dns_name = false;
struct in_addr addr;
int addr_len = 0;
bool cn_match = false;
ASSERT(cert);
// only IpV4 supported
if (inet_aton(host_name, &addr)) {
addr_len = sizeof(struct in_addr);
}
/* try matching against:
* 1) a DNS name or IP address as an alternative name (subjectAltName) extension
* in the certificate
* 2) the common name (CN) in the certificate
*
* either of these may be of the form: *.domain.tld
*
* only try (2) if there is no subjectAltName extension of
* type dNSName
*/
subject_alt_names = (GENERAL_NAMES*)X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
if (subject_alt_names) {
int num_alts = sk_GENERAL_NAME_num(subject_alt_names);
for (int i = 0; i < num_alts; i++) {
const GENERAL_NAME* name = sk_GENERAL_NAME_value(subject_alt_names, i);
if (name->type == GEN_DNS) {
found_dns_name = true;
if (RedPeer::x509_cert_host_name_compare((char *)ASN1_STRING_data(name->d.dNSName),
ASN1_STRING_length(name->d.dNSName),
host_name)) {
DBG(0, "alt name match=%s", ASN1_STRING_data(name->d.dNSName));
GENERAL_NAMES_free(subject_alt_names);
return true;
}
} else if (name->type == GEN_IPADD) {
int alt_ip_len = ASN1_STRING_length(name->d.iPAddress);
found_dns_name = true;
if ((addr_len == alt_ip_len)&&
!memcmp(ASN1_STRING_data(name->d.iPAddress), &addr, addr_len)) {
DBG(0, "alt name IP match=%s",
inet_ntoa(*((struct in_addr*)ASN1_STRING_data(name->d.dNSName))));
GENERAL_NAMES_free(subject_alt_names);
return true;
}
}
}
GENERAL_NAMES_free(subject_alt_names);
}
if (found_dns_name)
{
DBG(0, "SubjectAltName mismatch");
return false;
}
/* extracting commonNames */
X509_NAME* subject = X509_get_subject_name(cert);
if (subject) {
int pos = -1;
X509_NAME_ENTRY* cn_entry;
ASN1_STRING* cn_asn1;
while ((pos = X509_NAME_get_index_by_NID(subject, NID_commonName, pos)) != -1) {
cn_entry = X509_NAME_get_entry(subject, pos);
if (!cn_entry) {
continue;
}
cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
if (!cn_asn1) {
continue;
}
if (RedPeer::x509_cert_host_name_compare((char*)ASN1_STRING_data(cn_asn1),
ASN1_STRING_length(cn_asn1),
host_name)) {
DBG(0, "common name match=%s", (char*)ASN1_STRING_data(cn_asn1));
cn_match = true;
break;
}
}
}
if (!cn_match) {
DBG(0, "common name mismatch");
}
return cn_match;
}
bool RedPeer::verify_subject(X509* cert, const HostAuthOptions::CertFieldValueList& subject)
{
X509_NAME* cert_subject = NULL;
HostAuthOptions::CertFieldValueList::const_iterator subject_iter;
X509_NAME* in_subject;
int ret;
ASSERT(cert);
cert_subject = X509_get_subject_name(cert);
if (!cert_subject) {
LOG_WARN("reading certificate subject failed");
return false;
}
if (X509_NAME_entry_count(cert_subject) != subject.size()) {
DBG(0, "subject mismatch: #entries cert=%d, input=%d",
X509_NAME_entry_count(cert_subject), subject.size());
return false;
}
in_subject = X509_NAME_new();
if (!in_subject) {
LOG_WARN("failed to allocate X509_NAME");
return false;
}
for (subject_iter = subject.begin(); subject_iter != subject.end(); subject_iter++) {
if (!X509_NAME_add_entry_by_txt(in_subject,
subject_iter->first.c_str(),
MBSTRING_UTF8,
(const unsigned char*)subject_iter->second.c_str(),
subject_iter->second.length(), -1, 0)) {
LOG_WARN("failed to add entry %s=%s to X509_NAME",
subject_iter->first.c_str(), subject_iter->second.c_str());
X509_NAME_free(in_subject);
return false;
}
}
ret = X509_NAME_cmp(cert_subject, in_subject);
X509_NAME_free(in_subject);
if (ret == 0) {
DBG(0, "subjects match");
return true;
} else {
DBG(0, "subjects mismatch");
return false;
}
}
int RedPeer::ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
int depth;
SSL *ssl;
X509* cert;
SslVerifyCbData* verify_data;
int auth_flags;
depth = X509_STORE_CTX_get_error_depth(ctx);
ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
if (!ssl) {
LOG_WARN("failed to get ssl connection");
return 0;
}
verify_data = (SslVerifyCbData*)SSL_get_app_data(ssl);
auth_flags = verify_data->info.type_flags;
if (depth > 0) {
// if certificate verification failed, we can still authorize the server
// if its public key matches the one we hold in the peer_connect_options.
if (!preverify_ok) {
DBG(0, "openssl verify failed at depth=%d", depth);
verify_data->all_preverify_ok = false;
if (auth_flags & HostAuthOptions::HOST_AUTH_OP_PUBKEY) {
return 1;
} else {
return 0;
}
} else {
return preverify_ok;
}
}
/* depth == 0 */
cert = X509_STORE_CTX_get_current_cert(ctx);
if (!cert) {
LOG_WARN("failed to get server certificate");
return 0;
}
if (auth_flags & HostAuthOptions::HOST_AUTH_OP_PUBKEY) {
if (verify_pubkey(cert, verify_data->info.host_pubkey)) {
return 1;
}
}
if (!verify_data->all_preverify_ok || !preverify_ok) {
return 0;
}
if (auth_flags & HostAuthOptions::HOST_AUTH_OP_NAME) {
if (verify_host_name(cert, verify_data->host_name)) {
return 1;
}
}
if (auth_flags & HostAuthOptions::HOST_AUTH_OP_SUBJECT) {
if (verify_subject(cert, verify_data->info.host_subject)) {
return 1;
}
}
return 0;
}
// todo: use SSL_CTX_set_cipher_list, etc.
void RedPeer::connect_secure(const ConnectionOptions& options, const char* host)
{
int return_code;
int auth_flags;
SslVerifyCbData auth_data;
connect_unsecure(host, options.secure_port);
ASSERT(_ctx == NULL && _ssl == NULL && _peer != INVALID_SOCKET);
try {
@ -137,12 +483,39 @@ void RedPeer::connect_secure(const ConnectionOptions& options, uint32_t ip)
#else
SSL_METHOD *ssl_method = TLSv1_method();
#endif
auth_data.info = options.host_auth;
auth_data.host_name = host;
auth_data.all_preverify_ok = true;
_ctx = SSL_CTX_new(ssl_method);
if (_ctx == NULL) {
ssl_error();
}
auth_flags = auth_data.info.type_flags;
if ((auth_flags & RedPeer::HostAuthOptions::HOST_AUTH_OP_NAME) ||
(auth_flags & RedPeer::HostAuthOptions::HOST_AUTH_OP_SUBJECT)) {
std::string CA_file = auth_data.info.CA_file;
ASSERT(!CA_file.empty());
return_code = SSL_CTX_load_verify_locations(_ctx, CA_file.c_str(), NULL);
if (return_code != 1) {
if (auth_flags & RedPeer::HostAuthOptions::HOST_AUTH_OP_PUBKEY) {
LOG_WARN("SSL_CTX_load_verify_locations failed, CA_file=%s. "
"only pubkey authentication is active", CA_file.c_str());
auth_data.info.type_flags = RedPeer::HostAuthOptions::HOST_AUTH_OP_PUBKEY;
}
else {
LOG_WARN("SSL_CTX_load_verify_locations failed CA_file=%s", CA_file.c_str());
ssl_error();
}
}
}
if (auth_flags) {
SSL_CTX_set_verify(_ctx, SSL_VERIFY_PEER, ssl_verify_callback);
}
_ssl = SSL_new(_ctx);
if (!_ssl) {
THROW("create ssl failed");
@ -154,10 +527,13 @@ void RedPeer::connect_secure(const ConnectionOptions& options, uint32_t ip)
}
SSL_set_bio(_ssl, sbio, sbio);
SSL_set_app_data(_ssl, &auth_data);
int return_code = SSL_connect(_ssl);
return_code = SSL_connect(_ssl);
if (return_code <= 0) {
SSL_get_error(_ssl, return_code);
int ssl_error_code = SSL_get_error(_ssl, return_code);
LOG_WARN("failed to connect w/SSL, ssl_error %s",
ERR_error_string(ssl_error_code, NULL));
ssl_error();
}
} catch (...) {
@ -167,11 +543,6 @@ void RedPeer::connect_secure(const ConnectionOptions& options, uint32_t ip)
}
}
void RedPeer::connect_secure(const ConnectionOptions& options, const char* host)
{
connect_secure(options, host_by_name(host));
}
void RedPeer::shutdown()
{
if (_peer != INVALID_SOCKET) {

View File

@ -37,6 +37,30 @@ public:
class OutMessage;
class DisconnectedException {};
class HostAuthOptions {
public:
enum Type {
HOST_AUTH_OP_PUBKEY = 1,
HOST_AUTH_OP_NAME = (1 << 1),
HOST_AUTH_OP_SUBJECT = (1 << 2),
};
typedef std::vector<uint8_t> PublicKey;
typedef std::pair<std::string, std::string> CertFieldValuePair;
typedef std::list<CertFieldValuePair> CertFieldValueList;
HostAuthOptions() : type_flags(0) {}
public:
int type_flags;
PublicKey host_pubkey;
CertFieldValueList host_subject;
std::string CA_file;
};
class ConnectionOptions {
public:
@ -47,10 +71,12 @@ public:
CON_OP_BOTH,
};
ConnectionOptions(Type in_type, int in_port, int in_sport)
ConnectionOptions(Type in_type, int in_port, int in_sport,
const HostAuthOptions& in_host_auth)
: type (in_type)
, unsecure_port (in_port)
, secure_port (in_sport)
, host_auth (in_host_auth)
{
}
@ -70,12 +96,10 @@ public:
Type type;
int unsecure_port;
int secure_port;
HostAuthOptions host_auth; // for secure connection
};
void connect_unsecure(uint32_t ip, int port);
void connect_unsecure(const char* host, int port);
void connect_secure(const ConnectionOptions& options, uint32_t ip);
void connect_secure(const ConnectionOptions& options, const char* host);
void disconnect();
@ -95,6 +119,15 @@ protected:
virtual void on_event() {}
virtual int get_socket() { return _peer;}
static bool x509_cert_host_name_compare(const char *cert_name, int cert_name_size,
const char *host_name);
static bool verify_pubkey(X509* cert, const HostAuthOptions::PublicKey& key);
static bool verify_host_name(X509* cert, const char* host_name);
static bool verify_subject(X509* cert, const HostAuthOptions::CertFieldValueList& subject);
static int ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx);
private:
void shutdown();
void cleanup();

View File

@ -17,6 +17,8 @@
#include "common.h"
#include <shlobj.h>
#include "platform.h"
#include "win_platform.h"
#include "utils.h"
@ -28,6 +30,8 @@
#include "cursor.h"
#include "named_pipe.h"
#define SPICE_CONFIG_DIR "spicec\\"
int gdi_handlers = 0;
extern HINSTANCE instance;
@ -433,6 +437,21 @@ bool Platform::is_monitors_pos_valid()
return true;
}
void Platform::get_spice_config_dir(std::string& path)
{
char app_data_path[MAX_PATH];
HRESULT res = SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, app_data_path);
if (res != S_OK) {
throw Exception("get user app data dir failed");
}
path = app_data_path;
if (strcmp((app_data_path + strlen(app_data_path) - 2), "\\") != 0) {
path += "\\";
}
path += SPICE_CONFIG_DIR;
}
void Platform::init()
{
create_message_wind();

View File

@ -64,6 +64,8 @@
#define USE_XRANDR_1_2
#endif
#define SPICE_CONFIG_DIR ".spicec/"
static Display* x_display = NULL;
static bool x_shm_avail = false;
static XVisualInfo **vinfo = NULL;
@ -1911,6 +1913,18 @@ bool Platform::is_monitors_pos_valid()
return (ScreenCount(x_display) == 1);
}
void Platform::get_spice_config_dir(std::string& path)
{
char* home_dir = getenv("HOME");
if (!home_dir) {
throw Exception("get home dir failed");
}
path = home_dir;
path += "/";
path += SPICE_CONFIG_DIR;
}
static void root_win_proc(XEvent& event)
{
#ifdef USE_XRANDR_1_2

View File

@ -46,7 +46,7 @@
#define RED_MAGIC (*(uint32_t*)"REDQ")
#define RED_VERSION_MAJOR (~(uint32_t)0 - 1)
#define RED_VERSION_MINOR 1
#define RED_VERSION_MINOR 2
// Encryption & Ticketing Parameters
#define RED_MAX_PASSWORD_LENGTH 60
@ -209,10 +209,27 @@ typedef struct ATTR_PACKED RedMultiMediaTime {
uint32_t time;
} RedMultiMediaTime;
enum {
RED_PUBKEY_TYPE_INVALID,
RED_PUBKEY_TYPE_RSA,
RED_PUBKEY_TYPE_RSA2,
RED_PUBKEY_TYPE_DSA,
RED_PUBKEY_TYPE_DSA1,
RED_PUBKEY_TYPE_DSA2,
RED_PUBKEY_TYPE_DSA3,
RED_PUBKEY_TYPE_DSA4,
RED_PUBKEY_TYPE_DH,
RED_PUBKEY_TYPE_EC,
};
typedef struct ATTR_PACKED RedMigrationBegin {
uint16_t port;
uint16_t sport;
char host[0];
uint32_t host_offset;
uint32_t host_size;
uint16_t pub_key_type;
uint32_t pub_key_offset;
uint32_t pub_key_size;
} RedMigrationBegin;
enum {

View File

@ -61,9 +61,10 @@ static VDIPortInterface *vdagent = NULL;
#define MIGRATION_NOTIFY_SPICE_KEY "spice_mig_ext"
#define REDS_MIG_VERSION 1
#define REDS_MIG_VERSION 3
#define REDS_MIG_CONTINUE 1
#define REDS_MIG_ABORT 2
#define REDS_MIG_DIFF_VERSION 3
#define REDS_AGENT_WINDOW_SIZE 10
#define REDS_TOKENS_TO_SEND 5
@ -278,6 +279,7 @@ typedef struct RedsState {
uint32_t ping_id;
uint32_t net_test_id;
int net_test_stage;
int peer_minor_version;
} RedsState;
uint64_t bitrate_per_sec = ~0;
@ -2908,6 +2910,8 @@ static void reds_handle_read_header_done(void *opaque)
return;
}
reds->peer_minor_version = header->minor_version;
if (header->size < sizeof(RedLinkMess)) {
reds_send_link_error(link, RED_ERR_INVALID_DATA);
red_printf("bad size %u", header->size);
@ -4175,12 +4179,20 @@ typedef struct RedsMigSpice {
char *host;
int port;
int sport;
uint16_t cert_pub_key_type;
uint32_t cert_pub_key_len;
uint8_t* cert_pub_key;
} RedsMigSpice;
typedef struct RedsMigSpiceMessage {
uint32_t link_id;
} RedsMigSpiceMessage;
typedef struct RedsMigCertPubKeyInfo {
uint16_t type;
uint32_t len;
} RedsMigCertPubKeyInfo;
static int reds_mig_actual_read(RedsMigSpice *s)
{
for (;;) {
@ -4289,7 +4301,9 @@ static void reds_mig_continue(RedsMigSpice *s)
red_printf("");
core->set_file_handlers(core, s->fd, NULL, NULL, NULL);
host_len = strlen(s->host) + 1;
if (!(item = new_simple_out_item(RED_MIGRATE_BEGIN, sizeof(RedMigrationBegin) + host_len))) {
item = new_simple_out_item(RED_MIGRATE_BEGIN,
sizeof(RedMigrationBegin) + host_len + s->cert_pub_key_len);
if (!(item)) {
red_printf("alloc item failed");
reds_disconnect();
return;
@ -4297,7 +4311,13 @@ static void reds_mig_continue(RedsMigSpice *s)
migrate = (RedMigrationBegin *)item->data;
migrate->port = s->port;
migrate->sport = s->sport;
memcpy(migrate->host, s->host, host_len);
migrate->host_offset = sizeof(RedMigrationBegin);
migrate->host_size = host_len;
migrate->pub_key_type = s->cert_pub_key_type;
migrate->pub_key_offset = sizeof(RedMigrationBegin) + host_len;
migrate->pub_key_size = s->cert_pub_key_len;
memcpy((uint8_t*)(migrate) + migrate->host_offset , s->host, host_len);
memcpy((uint8_t*)(migrate) + migrate->pub_key_offset, s->cert_pub_key, s->cert_pub_key_len);
reds_push_pipe_item(&item->base);
free(s->local_args);
@ -4362,6 +4382,68 @@ static void reds_mig_send_ticket(RedsMigSpice *s)
BIO_free(bio_key);
}
static void reds_mig_receive_cert_public_key(RedsMigSpice *s)
{
s->cert_pub_key = malloc(s->cert_pub_key_len);
if (!s->cert_pub_key) {
red_printf("alloc failed");
reds_mig_failed(s);
return;
}
memcpy(s->cert_pub_key, s->read.buf, s->cert_pub_key_len);
s->read.size = RED_TICKET_PUBKEY_BYTES;
s->read.end_pos = 0;
s->read.handle_data = reds_mig_send_ticket;
core->set_file_handlers(core, s->fd, reds_mig_read, NULL, s);
}
static void reds_mig_receive_cert_public_key_info(RedsMigSpice *s)
{
RedsMigCertPubKeyInfo* pubkey_info = (RedsMigCertPubKeyInfo*)s->read.buf;
s->cert_pub_key_type = pubkey_info->type;
s->cert_pub_key_len = pubkey_info->len;
if (s->cert_pub_key_len > RECIVE_BUF_SIZE) {
red_printf("certificate public key length exceeds buffer size");
reds_mig_failed(s);
return;
}
if (s->cert_pub_key_len) {
s->read.size = s->cert_pub_key_len;
s->read.end_pos = 0;
s->read.handle_data = reds_mig_receive_cert_public_key;
} else {
s->cert_pub_key = NULL;
s->read.size = RED_TICKET_PUBKEY_BYTES;
s->read.end_pos = 0;
s->read.handle_data = reds_mig_send_ticket;
}
core->set_file_handlers(core, s->fd, reds_mig_read, NULL, s);
}
static void reds_mig_handle_send_abort_done(RedsMigSpice *s)
{
reds_mig_failed(s);
}
static void reds_mig_receive_version(RedsMigSpice *s)
{
uint32_t* dest_version;
uint32_t resault;
dest_version = (uint32_t*)s->read.buf;
resault = REDS_MIG_ABORT;
memcpy(s->write.buf, &resault, sizeof(resault));
s->write.length = sizeof(resault);
s->write.now = s->write.buf;
s->write.handle_done = reds_mig_handle_send_abort_done;
core->set_file_handlers(core, s->fd, reds_mig_write, reds_mig_write, s);
}
static void reds_mig_control(RedsMigSpice *spice_migration)
{
uint32_t *control;
@ -4371,9 +4453,9 @@ static void reds_mig_control(RedsMigSpice *spice_migration)
switch (*control) {
case REDS_MIG_CONTINUE:
spice_migration->read.size = RED_TICKET_PUBKEY_BYTES;
spice_migration->read.size = sizeof(RedsMigCertPubKeyInfo);
spice_migration->read.end_pos = 0;
spice_migration->read.handle_data = reds_mig_send_ticket;
spice_migration->read.handle_data = reds_mig_receive_cert_public_key_info;
core->set_file_handlers(core, spice_migration->fd, reds_mig_read,
NULL, spice_migration);
@ -4382,6 +4464,15 @@ static void reds_mig_control(RedsMigSpice *spice_migration)
red_printf("abort");
reds_mig_failed(spice_migration);
break;
case REDS_MIG_DIFF_VERSION:
red_printf("different versions");
spice_migration->read.size = sizeof(uint32_t);
spice_migration->read.end_pos = 0;
spice_migration->read.handle_data = reds_mig_receive_version;
core->set_file_handlers(core, spice_migration->fd, reds_mig_read,
NULL, spice_migration);
break;
default:
red_printf("invalid control");
reds_mig_failed(spice_migration);
@ -4423,6 +4514,12 @@ static void reds_mig_started(void *opaque, const char *in_args)
goto error;
}
if ((RED_VERSION_MAJOR == 1) && (reds->peer_minor_version < 2)) {
red_printf("minor version mismatch client %u server %u",
reds->peer_minor_version, RED_VERSION_MINOR);
goto error;
}
spice_migration = (RedsMigSpice *)malloc(sizeof(RedsMigSpice));
if (!spice_migration) {
red_printf("Could not allocate memory for spice migration structure");
@ -4625,6 +4722,85 @@ static void reds_mig_write_all(int fd, void *buf, int len, const char *name)
}
}
static void reds_mig_send_cert_public_key(int fd)
{
FILE* cert_file;
X509* x509;
EVP_PKEY* pub_key;
unsigned char* pp = NULL;
int length;
BIO* mem_bio;
RedsMigCertPubKeyInfo pub_key_info_msg;
if (spice_secure_port == -1) {
pub_key_info_msg.type = RED_PUBKEY_TYPE_INVALID;
pub_key_info_msg.len = 0;
reds_mig_write_all(fd, &pub_key_info_msg, sizeof(pub_key_info_msg), "cert public key info");
return;
}
cert_file = fopen(ssl_parameters.certs_file, "r");
if (!cert_file) {
red_error("opening certificate failed");
}
x509 = PEM_read_X509_AUX(cert_file, NULL, NULL, NULL);
if (!x509) {
red_error("reading x509 cert failed");
}
pub_key = X509_get_pubkey(x509);
if (!pub_key) {
red_error("reading public key failed");
}
mem_bio = BIO_new(BIO_s_mem());
i2d_PUBKEY_bio(mem_bio, pub_key);
if (BIO_flush(mem_bio) != 1) {
red_error("bio flush failed");
}
length = BIO_get_mem_data(mem_bio, &pp);
switch(pub_key->type) {
case EVP_PKEY_RSA:
pub_key_info_msg.type = RED_PUBKEY_TYPE_RSA;
break;
case EVP_PKEY_RSA2:
pub_key_info_msg.type = RED_PUBKEY_TYPE_RSA2;
break;
case EVP_PKEY_DSA:
pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA;
break;
case EVP_PKEY_DSA1:
pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA1;
break;
case EVP_PKEY_DSA2:
pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA2;
break;
case EVP_PKEY_DSA3:
pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA3;
break;
case EVP_PKEY_DSA4:
pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA4;
break;
case EVP_PKEY_DH:
pub_key_info_msg.type = RED_PUBKEY_TYPE_DH;
break;
case EVP_PKEY_EC:
pub_key_info_msg.type = RED_PUBKEY_TYPE_EC;
break;
default:
red_error("invalid public key type");
}
pub_key_info_msg.len = length;
reds_mig_write_all(fd, &pub_key_info_msg, sizeof(pub_key_info_msg), "cert public key info");
reds_mig_write_all(fd, pp, length, "cert public key");
BIO_free(mem_bio);
fclose(cert_file);
EVP_PKEY_free(pub_key);
X509_free(x509);
}
static void reds_mig_recv(void *opaque, int fd)
{
uint32_t ack_message = *(uint32_t *)"ack_";
@ -4639,16 +4815,36 @@ static void reds_mig_recv(void *opaque, int fd)
BUF_MEM *buff;
reds_mig_read_all(fd, &version, sizeof(version), "version");
if (version != REDS_MIG_VERSION) {
// starting from version 3, if the version of the src is bigger
// than ours, we send our version to the src.
if (version < REDS_MIG_VERSION) {
resault = REDS_MIG_ABORT;
reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
mig->notifier_done(mig, reds->mig_notifier);
return;
} else if (version > REDS_MIG_VERSION) {
uint32_t src_resault;
uint32_t self_version = REDS_MIG_VERSION;
resault = REDS_MIG_DIFF_VERSION;
reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
reds_mig_write_all(fd, &self_version, sizeof(self_version), "dest-version");
reds_mig_read_all(fd, &src_resault, sizeof(src_resault), "src resault");
if (src_resault == REDS_MIG_ABORT) {
red_printf("abort (response to REDS_MIG_DIFF_VERSION)");
mig->notifier_done(mig, reds->mig_notifier);
return;
} else if (src_resault != REDS_MIG_CONTINUE) {
red_printf("invalid response to REDS_MIG_DIFF_VERSION");
mig->notifier_done(mig, reds->mig_notifier);
return;
}
} else {
resault = REDS_MIG_CONTINUE;
reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
}
resault = REDS_MIG_CONTINUE;
reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
reds_mig_send_cert_public_key(fd);
ticketing_info.bn = BN_new();
if (!ticketing_info.bn) {