[Spice-commits] 8 commits - client/application.cpp client/red_client.cpp client/red_peer.cpp client/red_peer.h client/windows common/canvas_base.h common/canvas_utils.c common/canvas_utils.h common/Makefile.am common/mem.c common/mem.h common/ring.h common/ssl_verify.c common/ssl_verify.h configure.ac server/Makefile.am server/tests
Marc-André Lureau
elmarco at kemper.freedesktop.org
Tue May 3 08:31:53 PDT 2011
client/application.cpp | 19 -
client/red_client.cpp | 2
client/red_peer.cpp | 377 +--------------------------------
client/red_peer.h | 23 --
client/windows/platform.cpp | 1
common/Makefile.am | 2
common/canvas_base.h | 3
common/canvas_utils.c | 2
common/canvas_utils.h | 4
common/mem.c | 4
common/mem.h | 30 ++
common/ring.h | 13 +
common/ssl_verify.c | 481 +++++++++++++++++++++++++++++++++++++++++++
common/ssl_verify.h | 60 +++++
configure.ac | 5
server/Makefile.am | 1
server/tests/test_playback.c | 1
17 files changed, 640 insertions(+), 388 deletions(-)
New commits:
commit 138c4211936503c66eee81822eaaba8e9e727318
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Tue May 3 15:40:48 2011 +0200
build: fix gettimeofday warning
CC test_playback.o
test_playback.c: In function âplayback_timer_cbâ:
test_playback.c:56:5: warning: implicit declaration of function âgettimeofdayâ
diff --git a/server/tests/test_playback.c b/server/tests/test_playback.c
index 18220ea..6f332a9 100644
--- a/server/tests/test_playback.c
+++ b/server/tests/test_playback.c
@@ -1,6 +1,7 @@
#include <stdio.h>
#include <strings.h>
#include <sys/select.h>
+#include <sys/time.h>
#include <math.h>
#include <spice.h>
commit d46f9d3f4e006d3bca9b99fac25169b17e7ac803
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Tue May 3 16:14:18 2011 +0200
client: make use of ssl_verify.c
Fixed since v1:
- don't include C code, rather use the common lib
- add missing spice_openssl_verify_free() call
- keep the extra-parsing of subject for error reporting
diff --git a/client/application.cpp b/client/application.cpp
index bc6a6ee..e308ad1 100644
--- a/client/application.cpp
+++ b/client/application.cpp
@@ -354,7 +354,7 @@ Application::Application()
, _monitors (NULL)
, _title ("SPICEc:%d")
, _sys_key_intercept_mode (false)
- , _enable_controller (false)
+ , _enable_controller (false)
#ifdef USE_GUI
, _gui_mode (GUI_MODE_FULL)
#endif // USE_GUI
@@ -387,7 +387,7 @@ Application::Application()
_canvas_types[0] = CANVAS_OPTION_SW;
#endif
- _host_auth_opt.type_flags = RedPeer::HostAuthOptions::HOST_AUTH_OP_NAME;
+ _host_auth_opt.type_flags = SPICE_SSL_VERIFY_OP_HOSTNAME;
Platform::get_app_data_dir(_host_auth_opt.CA_file, app_name);
Platform::path_append(_host_auth_opt.CA_file, CA_FILE_NAME);
@@ -1993,9 +1993,11 @@ bool Application::set_host_cert_subject(const char* subject, const char* arg0)
std::string subject_str(subject);
std::string::const_iterator iter = subject_str.begin();
std::string entry;
- _host_auth_opt.type_flags = RedPeer::HostAuthOptions::HOST_AUTH_OP_SUBJECT;
- _host_auth_opt.host_subject.clear();
+ _host_auth_opt.type_flags = SPICE_SSL_VERIFY_OP_SUBJECT;
+ _host_auth_opt.host_subject = subject;
+ /* the follow is only checking code, subject is parsed later
+ ssl_verify.c. We keep simply because of better error message... */
while (true) {
if ((iter == subject_str.end()) || (*iter == ',')) {
RedPeer::HostAuthOptions::CertFieldValuePair entry_pair;
@@ -2015,7 +2017,6 @@ bool Application::set_host_cert_subject(const char* subject, const char* arg0)
}
entry_pair.first = entry.substr(start_pos, value_pos - start_pos);
entry_pair.second = entry.substr(value_pos + 1);
- _host_auth_opt.host_subject.push_back(entry_pair);
DBG(0, "subject entry: %s=%s", entry_pair.first.c_str(), entry_pair.second.c_str());
if (iter == subject_str.end()) {
break;
@@ -2039,6 +2040,7 @@ bool Application::set_host_cert_subject(const char* subject, const char* arg0)
}
iter++;
}
+
return true;
}
@@ -2284,8 +2286,9 @@ bool Application::process_cmd_line(int argc, char** argv, bool &full_screen)
#ifdef USE_SMARTCARD
parser.add(SPICE_OPT_SMARTCARD, "smartcard", "enable smartcard channel");
parser.add(SPICE_OPT_NOSMARTCARD, "nosmartcard", "disable smartcard channel");
- parser.add(SPICE_OPT_SMARTCARD_CERT, "smartcard-cert", "Use virtual reader+card with given cert(s)",
- "smartcard-cert", true);
+ parser.add(SPICE_OPT_SMARTCARD_CERT, "smartcard-cert",
+ "Use virtual reader+card with given cert(s)",
+ "smartcard-cert", true);
parser.set_multi(SPICE_OPT_SMARTCARD_CERT, ',');
parser.add(SPICE_OPT_SMARTCARD_DB, "smartcard-db", "Use given db for smartcard certs", "smartcard-db", true);
#endif
@@ -2516,7 +2519,7 @@ void spice_log(unsigned int type, const char *function, const char *format, ...)
Platform::get_thread_id(),
function_to_func_name(function).c_str(),
formated_message.c_str());
- fflush(log_file);
+ fflush(log_file);
}
if (type >= LOG_WARN) {
diff --git a/client/red_client.cpp b/client/red_client.cpp
index 56a3e22..8918e4f 100644
--- a/client/red_client.cpp
+++ b/client/red_client.cpp
@@ -274,7 +274,7 @@ void Migrate::start(const SpiceMsgMainMigrationBegin* migrate)
_host.assign((char *)migrate->host_data);
_port = migrate->port ? migrate->port : -1;
_sport = migrate->sport ? migrate->sport : -1;
- _auth_options.type_flags = RedPeer::HostAuthOptions::HOST_AUTH_OP_PUBKEY;
+ _auth_options.type_flags = SPICE_SSL_VERIFY_OP_PUBKEY;
_auth_options.host_pubkey.assign(migrate->pub_key_data, migrate->pub_key_data + migrate->pub_key_size);
}
diff --git a/client/red_peer.cpp b/client/red_peer.cpp
index 19919a6..37ca2b8 100644
--- a/client/red_peer.cpp
+++ b/client/red_peer.cpp
@@ -27,12 +27,7 @@
#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;
+#include "ssl_verify.h"
static void ssl_error()
{
@@ -135,346 +130,11 @@ void RedPeer::connect_unsecure(const char* host, int portnr)
}
}
-bool RedPeer::verify_pubkey(X509* cert, const HostAuthOptions::PublicKey& key)
-{
- 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;
- }
-}
-
-/* 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)
-{
- /* 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 ((size_t)X509_NAME_entry_count(cert_subject) != subject.size()) {
- LOG_ERROR("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 {
- LOG_ERROR("host-subject 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;
-}
-
void RedPeer::connect_secure(const ConnectionOptions& options, const char* host)
{
int return_code;
- int auth_flags;
- SslVerifyCbData auth_data;
+ SPICE_SSL_VERIFY_OP auth_flags;
+ SpiceOpenSSLVerify* verify = NULL;
connect_unsecure(host, options.secure_port);
ASSERT(_ctx == NULL && _ssl == NULL && _peer != INVALID_SOCKET);
@@ -485,27 +145,23 @@ void RedPeer::connect_secure(const ConnectionOptions& options, const char* host)
#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;
+ auth_flags = options.host_auth.type_flags;
+ if ((auth_flags & SPICE_SSL_VERIFY_OP_HOSTNAME) ||
+ (auth_flags & SPICE_SSL_VERIFY_OP_SUBJECT)) {
+ std::string CA_file = options.host_auth.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) {
+ if (auth_flags & SPICE_SSL_VERIFY_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;
+ auth_flags = SPICE_SSL_VERIFY_OP_PUBKEY;
}
else {
LOG_ERROR("SSL_CTX_load_verify_locations failed CA_file=%s", CA_file.c_str());
@@ -514,10 +170,6 @@ void RedPeer::connect_secure(const ConnectionOptions& options, const char* host)
}
}
- if (auth_flags) {
- SSL_CTX_set_verify(_ctx, SSL_VERIFY_PEER, ssl_verify_callback);
- }
-
return_code = SSL_CTX_set_cipher_list(_ctx, options.ciphers.c_str());
if (return_code != 1) {
LOG_ERROR("SSL_CTX_set_cipher_list failed, ciphers=%s", options.ciphers.c_str());
@@ -529,13 +181,19 @@ void RedPeer::connect_secure(const ConnectionOptions& options, const char* host)
THROW("create ssl failed");
}
+ verify = spice_openssl_verify_new(
+ _ssl, auth_flags,
+ host,
+ (char*)&options.host_auth.host_pubkey[0],
+ options.host_auth.host_pubkey.size(),
+ options.host_auth.host_subject.c_str());
+
BIO* sbio = BIO_new_socket(_peer, BIO_NOCLOSE);
if (!sbio) {
THROW("alloc new socket bio failed");
}
SSL_set_bio(_ssl, sbio, sbio);
- SSL_set_app_data(_ssl, &auth_data);
return_code = SSL_connect(_ssl);
if (return_code <= 0) {
@@ -546,9 +204,12 @@ void RedPeer::connect_secure(const ConnectionOptions& options, const char* host)
}
} catch (...) {
Lock lock(_lock);
+ spice_openssl_verify_free(verify);
cleanup();
throw;
}
+
+ spice_openssl_verify_free(verify);
}
void RedPeer::shutdown()
diff --git a/client/red_peer.h b/client/red_peer.h
index a4310e6..7e3428b 100644
--- a/client/red_peer.h
+++ b/client/red_peer.h
@@ -27,6 +27,8 @@
#include "threads.h"
#include "platform_utils.h"
#include "marshaller.h"
+#include "debug.h"
+#include "ssl_verify.h"
class RedPeer: protected EventSources::Socket {
public:
@@ -41,24 +43,18 @@ public:
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) {}
+ HostAuthOptions() : type_flags(SPICE_SSL_VERIFY_OP_NONE) {}
public:
- int type_flags;
+ SPICE_SSL_VERIFY_OP type_flags;
PublicKey host_pubkey;
- CertFieldValueList host_subject;
+ std::string host_subject;
std::string CA_file;
};
@@ -124,15 +120,6 @@ public:
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);
void cleanup();
private:
commit c16b1a924b161d8031193fc375be8e2773f8d0c1
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Tue Jan 25 13:00:33 2011 +0100
common: add ssl_verify.c common code
Code adapter from RedPeer::ssl_verify_callback() and used by
spice-gtk.
Since v1:
- fixed Makefile.am
- added config.h include
- autoconf alloca added in patch series
- moved int escape inside for loop
- added a failed case when missing assignment
- replaced strlen () by -1
- skip spaces after comma
- c++ guards
I didn't use bool, because openSSL uses int, and it is more future
proof for error reporting.
diff --git a/common/Makefile.am b/common/Makefile.am
index dff9574..e0f4d49 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -34,6 +34,8 @@ libspice_common_la_SOURCES = \
rop3.c \
rop3.h \
spice_common.h \
+ ssl_verify.c \
+ ssl_verify.h \
$(NULL)
if SUPPORT_GL
diff --git a/common/ssl_verify.c b/common/ssl_verify.c
new file mode 100644
index 0000000..5e3722e
--- /dev/null
+++ b/common/ssl_verify.c
@@ -0,0 +1,481 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2011 Red Hat, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mem.h"
+#include "ssl_verify.h"
+
+#ifndef WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include <ctype.h>
+
+#ifndef SPICE_DEBUG
+# define SPICE_DEBUG(format, args...)
+#endif
+
+#ifdef WIN32
+static int inet_aton(const char* ip, struct in_addr* in_addr)
+{
+ unsigned long addr = inet_addr(ip);
+
+ if (addr == INADDR_NONE) {
+ return 0;
+ }
+ in_addr->S_un.S_addr = addr;
+ return 1;
+}
+#endif
+
+static int verify_pubkey(X509* cert, const char *key, size_t key_size)
+{
+ EVP_PKEY* cert_pubkey = NULL;
+ EVP_PKEY* orig_pubkey = NULL;
+ BIO* bio = NULL;
+ int ret = 0;
+
+ if (!key || key_size == 0)
+ return 0;
+
+ if (!cert) {
+ SPICE_DEBUG("warning: no cert!");
+ return 0;
+ }
+
+ cert_pubkey = X509_get_pubkey(cert);
+ if (!cert_pubkey) {
+ SPICE_DEBUG("warning: reading public key from certificate failed");
+ goto finish;
+ }
+
+ bio = BIO_new_mem_buf((void*)key, key_size);
+ if (!bio) {
+ SPICE_DEBUG("creating BIO failed");
+ goto finish;
+ }
+
+ orig_pubkey = d2i_PUBKEY_bio(bio, NULL);
+ if (!orig_pubkey) {
+ SPICE_DEBUG("reading pubkey from bio failed");
+ goto finish;
+ }
+
+ ret = EVP_PKEY_cmp(orig_pubkey, cert_pubkey);
+
+ if (ret == 1)
+ SPICE_DEBUG("public keys match");
+ else if (ret == 0)
+ SPICE_DEBUG("public keys mismatch");
+ else
+ SPICE_DEBUG("public keys types mismatch");
+
+finish:
+ if (bio)
+ BIO_free(bio);
+
+ if (orig_pubkey)
+ EVP_PKEY_free(orig_pubkey);
+
+ if (cert_pubkey)
+ EVP_PKEY_free(cert_pubkey);
+
+ return ret;
+}
+
+/* from gnutls
+ * compare hostname against certificate, taking account of wildcards
+ * return 1 on success or 0 on error
+ *
+ * note: certnamesize is required as X509 certs can contain embedded NULs in
+ * the strings such as CN or subjectAltName
+ */
+static int _gnutls_hostname_compare(const char *certname,
+ size_t certnamesize, const char *hostname)
+{
+ /* find the first different character */
+ for (; *certname && *hostname && toupper (*certname) == toupper (*hostname);
+ certname++, hostname++, certnamesize--)
+ ;
+
+ /* the strings are the same */
+ if (certnamesize == 0 && *hostname == '\0')
+ return 1;
+
+ if (*certname == '*')
+ {
+ /* a wildcard certificate */
+
+ certname++;
+ certnamesize--;
+
+ while (1)
+ {
+ /* Use a recursive call to allow multiple wildcards */
+ if (_gnutls_hostname_compare (certname, certnamesize, hostname))
+ return 1;
+
+ /* wildcards are only allowed to match a single domain
+ component or component fragment */
+ if (*hostname == '\0' || *hostname == '.')
+ break;
+ hostname++;
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+
+/**
+ * From gnutls and spice red_peer.c
+ * TODO: switch to gnutls and get rid of this
+ *
+ * This function will check if the given certificate's subject matches
+ * the given 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.
+ *
+ * Returns: 1 for a successful match, and 0 on failure.
+ **/
+static int verify_hostname(X509* cert, const char *hostname)
+{
+ GENERAL_NAMES* subject_alt_names;
+ int found_dns_name = 0;
+ struct in_addr addr;
+ int addr_len = 0;
+ int cn_match = 0;
+
+ if (!cert) {
+ SPICE_DEBUG("warning: no cert!");
+ return 0;
+ }
+
+ // only IpV4 supported
+ if (inet_aton(hostname, &addr)) {
+ addr_len = sizeof(struct in_addr);
+ }
+
+ /* try matching against:
+ * 1) a DNS name 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
+ */
+
+ /* Check through all included subjectAltName extensions, comparing
+ * against all those 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);
+ int i;
+ for (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 = 1;
+ if (_gnutls_hostname_compare((char *)ASN1_STRING_data(name->d.dNSName),
+ ASN1_STRING_length(name->d.dNSName),
+ hostname)) {
+ SPICE_DEBUG("alt name match=%s", ASN1_STRING_data(name->d.dNSName));
+ GENERAL_NAMES_free(subject_alt_names);
+ return 1;
+ }
+ } else if (name->type == GEN_IPADD) {
+ int alt_ip_len = ASN1_STRING_length(name->d.iPAddress);
+ found_dns_name = 1;
+ if ((addr_len == alt_ip_len)&&
+ !memcmp(ASN1_STRING_data(name->d.iPAddress), &addr, addr_len)) {
+ SPICE_DEBUG("alt name IP match=%s",
+ inet_ntoa(*((struct in_addr*)ASN1_STRING_data(name->d.dNSName))));
+ GENERAL_NAMES_free(subject_alt_names);
+ return 1;
+ }
+ }
+ }
+ GENERAL_NAMES_free(subject_alt_names);
+ }
+
+ if (found_dns_name) {
+ SPICE_DEBUG("warning: SubjectAltName mismatch");
+ return 0;
+ }
+
+ /* 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 (_gnutls_hostname_compare((char*)ASN1_STRING_data(cn_asn1),
+ ASN1_STRING_length(cn_asn1),
+ hostname)) {
+ SPICE_DEBUG("common name match=%s", (char*)ASN1_STRING_data(cn_asn1));
+ cn_match = 1;
+ break;
+ }
+ }
+ }
+
+ if (!cn_match)
+ SPICE_DEBUG("warning: common name mismatch");
+
+ return cn_match;
+}
+
+X509_NAME* subject_to_x509_name(const char *subject, int *nentries)
+{
+ X509_NAME* in_subject;
+ const char *p;
+ char *key, *val, *k, *v = NULL;
+ enum {
+ KEY,
+ VALUE
+ } state;
+
+ key = (char*)alloca(strlen(subject));
+ val = (char*)alloca(strlen(subject));
+ in_subject = X509_NAME_new();
+
+ if (!in_subject || !key || !val) {
+ SPICE_DEBUG("failed to allocate");
+ return NULL;
+ }
+
+ *nentries = 0;
+
+ k = key;
+ state = KEY;
+ for (p = subject;; ++p) {
+ int escape = 0;
+ if (*p == '\\') {
+ ++p;
+ if (*p != '\\' && *p != ',') {
+ SPICE_DEBUG("Invalid character after \\");
+ goto fail;
+ }
+ escape = 1;
+ }
+
+ switch (state) {
+ case KEY:
+ if (*p == ' ' && k == key) {
+ continue; /* skip spaces before key */
+ } if (*p == 0) {
+ if (k == key) /* empty key, ending */
+ goto success;
+ goto fail;
+ } else if (*p == ',' && !escape) {
+ goto fail; /* assignment is missing */
+ } else if (*p == '=' && !escape) {
+ state = VALUE;
+ *k = 0;
+ v = val;
+ } else
+ *k++ = *p;
+ break;
+ case VALUE:
+ if (*p == 0 || (*p == ',' && !escape)) {
+ if (v == val) /* empty value */
+ goto fail;
+
+ *v = 0;
+
+ if (!X509_NAME_add_entry_by_txt(in_subject, key,
+ MBSTRING_UTF8,
+ (const unsigned char*)val,
+ -1, -1, 0)) {
+ SPICE_DEBUG("warning: failed to add entry %s=%s to X509_NAME",
+ key, val);
+ goto fail;
+ }
+ *nentries += 1;
+
+ if (*p == 0)
+ goto success;
+
+ state = KEY;
+ k = key;
+ } else
+ *v++ = *p;
+ break;
+ }
+ }
+
+success:
+ return in_subject;
+
+fail:
+ if (in_subject)
+ X509_NAME_free(in_subject);
+
+ return NULL;
+}
+
+int verify_subject(X509* cert, SpiceOpenSSLVerify* verify)
+{
+ X509_NAME *cert_subject = NULL;
+ int ret;
+ int in_entries;
+
+ if (!cert) {
+ SPICE_DEBUG("warning: no cert!");
+ return 0;
+ }
+
+ cert_subject = X509_get_subject_name(cert);
+ if (!cert_subject) {
+ SPICE_DEBUG("warning: reading certificate subject failed");
+ return 0;
+ }
+
+ if (!verify->in_subject) {
+ verify->in_subject = subject_to_x509_name(verify->subject, &in_entries);
+ if (!verify->in_subject) {
+ SPICE_DEBUG("warning: no in_subject!");
+ return 0;
+ }
+ }
+
+ /* Note: this check is redundant with the pre-condition in X509_NAME_cmp */
+ if (X509_NAME_entry_count(cert_subject) != in_entries) {
+ SPICE_DEBUG("subject mismatch: #entries cert=%d, input=%d",
+ X509_NAME_entry_count(cert_subject), in_entries);
+ return 0;
+ }
+
+ ret = X509_NAME_cmp(cert_subject, verify->in_subject);
+
+ if (ret == 0)
+ SPICE_DEBUG("subjects match");
+ else
+ SPICE_DEBUG("subjects mismatch");
+
+ return !ret;
+}
+
+static int openssl_verify(int preverify_ok, X509_STORE_CTX *ctx)
+{
+ int depth;
+ SpiceOpenSSLVerify *v;
+ SSL *ssl;
+ X509* cert;
+
+ ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ v = (SpiceOpenSSLVerify*)SSL_get_app_data(ssl);
+
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ if (depth > 0) {
+ if (!preverify_ok) {
+ SPICE_DEBUG("openssl verify failed at depth=%d", depth);
+ v->all_preverify_ok = 0;
+ return 0;
+ } else
+ return 1;
+ }
+
+ /* depth == 0 */
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ if (!cert) {
+ SPICE_DEBUG("failed to get server certificate");
+ return 0;
+ }
+
+ if (v->verifyop & SPICE_SSL_VERIFY_OP_PUBKEY &&
+ verify_pubkey(cert, v->pubkey, v->pubkey_size))
+ return 1;
+
+ if (!v->all_preverify_ok || !preverify_ok)
+ return 0;
+
+ if (v->verifyop & SPICE_SSL_VERIFY_OP_HOSTNAME &&
+ verify_hostname(cert, v->hostname))
+ return 1;
+
+ if (v->verifyop & SPICE_SSL_VERIFY_OP_SUBJECT &&
+ verify_subject(cert, v))
+ return 1;
+
+ return 0;
+}
+
+SpiceOpenSSLVerify* spice_openssl_verify_new(SSL *ssl, SPICE_SSL_VERIFY_OP verifyop,
+ const char *hostname,
+ const char *pubkey, size_t pubkey_size,
+ const char *subject)
+{
+ SpiceOpenSSLVerify *v;
+
+ if (!verifyop)
+ return NULL;
+
+ v = spice_new0(SpiceOpenSSLVerify, 1);
+
+ v->ssl = ssl;
+ v->verifyop = verifyop;
+ v->hostname = spice_strdup(hostname);
+ v->pubkey = (char*)spice_memdup(pubkey, pubkey_size);
+ v->pubkey_size = pubkey_size;
+ v->subject = spice_strdup(subject);
+
+ v->all_preverify_ok = 1;
+
+ SSL_set_app_data(ssl, v);
+ SSL_set_verify(ssl,
+ SSL_VERIFY_PEER, openssl_verify);
+
+ return v;
+}
+
+void spice_openssl_verify_free(SpiceOpenSSLVerify* verify)
+{
+ if (!verify)
+ return;
+
+ free(verify->pubkey);
+ free(verify->subject);
+ free(verify->hostname);
+
+ if (verify->in_subject)
+ X509_NAME_free(verify->in_subject);
+
+ if (verify->ssl)
+ SSL_set_app_data(verify->ssl, NULL);
+ free(verify);
+}
diff --git a/common/ssl_verify.h b/common/ssl_verify.h
new file mode 100644
index 0000000..f0b97d8
--- /dev/null
+++ b/common/ssl_verify.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2011 Red Hat, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SSL_VERIFY_H
+#define SSL_VERIFY_H
+
+#include <openssl/rsa.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ SPICE_SSL_VERIFY_OP_NONE = 0,
+ SPICE_SSL_VERIFY_OP_PUBKEY = (1 << 0),
+ SPICE_SSL_VERIFY_OP_HOSTNAME = (1 << 1),
+ SPICE_SSL_VERIFY_OP_SUBJECT = (1 << 2),
+} SPICE_SSL_VERIFY_OP;
+
+typedef struct {
+ SSL *ssl;
+ SPICE_SSL_VERIFY_OP verifyop;
+ int all_preverify_ok;
+ char *hostname;
+ char *pubkey;
+ size_t pubkey_size;
+ char *subject;
+ X509_NAME *in_subject;
+} SpiceOpenSSLVerify;
+
+SpiceOpenSSLVerify* spice_openssl_verify_new(SSL *ssl, SPICE_SSL_VERIFY_OP verifyop,
+ const char *hostname,
+ const char *pubkey, size_t pubkey_size,
+ const char *subject);
+void spice_openssl_verify_free(SpiceOpenSSLVerify* verify);
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+#endif // SSL_VERIFY_H
commit 2b78b4968a514262d532626d7aee78ad4a481fd2
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Tue May 3 13:53:59 2011 +0200
common: add ring_get_length() for debugging purposes
Please notice it has a "static" modifier, like the rest of the inlined
functions in ring.h, so it won't warn if it isn't used.
diff --git a/common/ring.h b/common/ring.h
index ff4ef6d..bd030d2 100644
--- a/common/ring.h
+++ b/common/ring.h
@@ -154,6 +154,19 @@ static inline RingItem *ring_prev(Ring *ring, RingItem *pos)
(var) = ring_prev(ring, var))
+static inline unsigned int ring_get_length(Ring *ring)
+{
+ RingItem *i;
+ unsigned int ret = 0;
+
+ for (i = ring_get_head(ring);
+ i != NULL;
+ i = ring_next(ring, i))
+ ret++;
+
+ return ret;
+}
+
#ifdef __cplusplus
}
#endif
commit dc1d4bdb680e6fcfa078fecae5a00966b644b0a9
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Tue May 3 13:39:38 2011 +0200
common: mem.h add alloca definition
We don't support the autoconf ALLOCA/C_ALLOC fallback. If one day,
someone cares for a weird platform, he can fix it.
diff --git a/common/mem.h b/common/mem.h
index 8ba6586..980ea13 100644
--- a/common/mem.h
+++ b/common/mem.h
@@ -22,10 +22,40 @@
#include <stdlib.h>
#include <spice/macros.h>
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
#ifdef __cplusplus
extern "C" {
#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#elif defined __GNUC__
+# define alloca __builtin_alloca
+#elif defined _AIX
+# define alloca __alloca
+#elif defined _MSC_VER
+# include <malloc.h>
+# define alloca _alloca
+#else
+# ifndef HAVE_ALLOCA
+# ifdef __cplusplus
+extern "C"
+# endif
+void *alloca (size_t);
+# endif
+#endif
+
typedef struct SpiceChunk {
uint8_t *data;
uint32_t len;
diff --git a/configure.ac b/configure.ac
index 4b3d59f..18209bf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -27,6 +27,7 @@ AC_C_BIGENDIAN
AC_PATH_PROGS(PYTHON, python2 python)
AC_CHECK_HEADERS([sys/time.h])
+AC_FUNC_ALLOCA
SPICE_LT_VERSION=m4_format("%d:%d:%d", 1, 0, 2)
AC_SUBST(SPICE_LT_VERSION)
commit fc80f096e4b0432f873807c6d5d7a66ebb345323
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Tue Jan 25 16:17:12 2011 +0100
common: spice_memdup could accept NULL
(this patch is not to solve a crash fix, but to align with glib API)
diff --git a/common/mem.c b/common/mem.c
index 96be351..7236cf0 100644
--- a/common/mem.c
+++ b/common/mem.c
@@ -74,6 +74,10 @@ void *spice_memdup(const void *mem, size_t n_bytes)
{
void *copy;
+ if (mem == NULL) {
+ return NULL;
+ }
+
copy = spice_malloc(n_bytes);
memcpy(copy, mem, n_bytes);
return copy;
commit 27f771566d2fb76f62010acb6967a28e9045c43c
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Wed Dec 15 18:39:14 2010 +0100
common: add windows.h where required, make gdi_handlers static
This patch has not been verified with VS/brew. It should be safe
hopefully. Compilation is fine with mingw32/spice-gtk.
diff --git a/client/windows/platform.cpp b/client/windows/platform.cpp
index c50d7fd..b81c2f5 100644
--- a/client/windows/platform.cpp
+++ b/client/windows/platform.cpp
@@ -45,7 +45,6 @@
#endif
#include <spice/vd_agent.h>
-int gdi_handlers = 0;
extern HINSTANCE instance;
class DefaultEventListener: public Platform::EventListener {
diff --git a/common/canvas_base.h b/common/canvas_base.h
index 7c5f275..7a69def 100644
--- a/common/canvas_base.h
+++ b/common/canvas_base.h
@@ -27,6 +27,9 @@
#include "lz.h"
#include "region.h"
#include "draw.h"
+#ifdef WIN32
+#include <windows.h>
+#endif
#ifdef __cplusplus
extern "C" {
diff --git a/common/canvas_utils.c b/common/canvas_utils.c
index d861800..604f589 100644
--- a/common/canvas_utils.c
+++ b/common/canvas_utils.c
@@ -30,7 +30,7 @@
#include "mem.h"
#ifdef WIN32
-extern int gdi_handlers;
+static int gdi_handlers = 0;
#endif
#ifndef CANVAS_ERROR
diff --git a/common/canvas_utils.h b/common/canvas_utils.h
index ffed927..16ada45 100644
--- a/common/canvas_utils.h
+++ b/common/canvas_utils.h
@@ -19,6 +19,10 @@
#ifndef _H_CANVAS_UTILS
#define _H_CANVAS_UTILS
+#ifdef WIN32
+#include <windows.h>
+#endif
+
#include <spice/types.h>
#include "pixman_utils.h"
commit aae03570230d37a74b03892021e5dbe24b7739d2
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Wed Jan 19 14:57:48 2011 +0100
build: require c99
Because we use c99: stdbool.h, inttypes.h, bool, variadic macros, // comments, ...
diff --git a/configure.ac b/configure.ac
index 430bf83..4b3d59f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -14,6 +14,10 @@ AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip subdir-objects])
AM_MAINTAINER_MODE
AC_PROG_CC
+AC_PROG_CC_C99
+if test x"$ac_cv_prog_cc_c99" = xno; then
+ AC_MSG_ERROR([C99 compiler is required.])
+fi
AC_PROG_CXX
AC_PROG_INSTALL
AC_CANONICAL_HOST
diff --git a/server/Makefile.am b/server/Makefile.am
index 81649a4..601840a 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -16,7 +16,6 @@ INCLUDES = \
$(SSL_CFLAGS) \
$(VISIBILITY_HIDDEN_CFLAGS) \
$(WARN_CFLAGS) \
- -std=gnu99 \
$(NULL)
spice_built_sources = generated_marshallers.c generated_marshallers.h generated_demarshallers.c
More information about the Spice-commits
mailing list