diff options
author | Marc-André Lureau <marcandre.lureau@redhat.com> | 2011-05-03 16:14:18 +0200 |
---|---|---|
committer | Marc-André Lureau <marcandre.lureau@redhat.com> | 2011-05-03 17:16:46 +0200 |
commit | d46f9d3f4e006d3bca9b99fac25169b17e7ac803 (patch) | |
tree | 4dbe7bbc59ba99441115ebaa7f2475774b0856ce | |
parent | c16b1a924b161d8031193fc375be8e2773f8d0c1 (diff) |
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
-rw-r--r-- | client/application.cpp | 19 | ||||
-rw-r--r-- | client/red_client.cpp | 2 | ||||
-rw-r--r-- | client/red_peer.cpp | 377 | ||||
-rw-r--r-- | client/red_peer.h | 23 |
4 files changed, 36 insertions, 385 deletions
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: |