diff --git a/panda/src/downloader/Sources.pp b/panda/src/downloader/Sources.pp index 742a692c5b..64efa50a37 100644 --- a/panda/src/downloader/Sources.pp +++ b/panda/src/downloader/Sources.pp @@ -35,7 +35,6 @@ multiplexStreamBuf.I multiplexStreamBuf.h \ patcher.h patcher.I \ socketStream.h socketStream.I \ - ssl_utils.h \ stringStreamBuf.I stringStreamBuf.h \ stringStream.I stringStream.h \ urlSpec.I urlSpec.h \ @@ -66,7 +65,6 @@ multiplexStream.cxx multiplexStreamBuf.cxx \ patcher.cxx \ socketStream.cxx \ - ssl_utils.cxx \ stringStreamBuf.cxx \ stringStream.cxx \ urlSpec.cxx \ @@ -99,7 +97,6 @@ multiplexStreamBuf.I multiplexStreamBuf.h \ patcher.h patcher.I \ socketStream.h socketStream.I \ - ssl_utils.h \ stringStreamBuf.I stringStreamBuf.h \ stringStream.I stringStream.h \ urlSpec.h urlSpec.I \ diff --git a/panda/src/downloader/bioStreamBuf.cxx b/panda/src/downloader/bioStreamBuf.cxx index 15fe766c99..b777727c51 100644 --- a/panda/src/downloader/bioStreamBuf.cxx +++ b/panda/src/downloader/bioStreamBuf.cxx @@ -14,7 +14,7 @@ #include "bioStreamBuf.h" #include "config_downloader.h" -#include "ssl_utils.h" +#include "openSSLWrapper.h" #include #ifdef HAVE_OPENSSL @@ -199,7 +199,7 @@ underflow() { << "Lost connection to " << _source->get_server_name() << ":" << _source->get_port() << " (" << read_count << ").\n"; - notify_ssl_errors(); + OpenSSLWrapper::get_global_ptr()->notify_ssl_errors(); SSL *ssl = NULL; BIO_get_ssl(*_source, &ssl); diff --git a/panda/src/downloader/config_downloader.cxx b/panda/src/downloader/config_downloader.cxx index 79296a5f14..723ede2fa5 100644 --- a/panda/src/downloader/config_downloader.cxx +++ b/panda/src/downloader/config_downloader.cxx @@ -64,9 +64,6 @@ ConfigVariableInt patcher_buffer_size ConfigVariableList expected_ssl_server ("expected-ssl-server"); -ConfigVariableList ssl_certificates -("ssl-certificates"); - ConfigVariableBool http_proxy_tunnel ("http-proxy-tunnel", false, PRC_DESC("This specifies the default value for HTTPChannel::set_proxy_tunnel(). " @@ -145,7 +142,7 @@ init_libdownloader() { "library is loaded), or false to defer this until it is actually " "needed (which will be the first time you open an https connection " "or otherwise use encryption services). You can also call " - "HTTPClient::initialize_ssl() to " + "HTTPClient::init_random_seed() to " "do this when you are ready. The issue is that on Windows, " "OpenSSL will attempt to " "randomize its seed by crawling through the entire heap of " diff --git a/panda/src/downloader/config_downloader.h b/panda/src/downloader/config_downloader.h index 4e2cd2e7da..08202d37df 100644 --- a/panda/src/downloader/config_downloader.h +++ b/panda/src/downloader/config_downloader.h @@ -39,7 +39,6 @@ extern ConfigVariableDouble extractor_step_time; extern ConfigVariableInt patcher_buffer_size; extern ConfigVariableList expected_ssl_server; -extern ConfigVariableList ssl_certificates; extern ConfigVariableBool http_proxy_tunnel; extern ConfigVariableDouble http_connect_timeout; diff --git a/panda/src/downloader/downloader_composite2.cxx b/panda/src/downloader/downloader_composite2.cxx index b7f03d1b35..b06e28e4bb 100644 --- a/panda/src/downloader/downloader_composite2.cxx +++ b/panda/src/downloader/downloader_composite2.cxx @@ -13,7 +13,6 @@ #include "multiplexStreamBuf.cxx" #include "patcher.cxx" #include "socketStream.cxx" -#include "ssl_utils.cxx" #include "stringStreamBuf.cxx" #include "stringStream.cxx" #include "urlSpec.cxx" diff --git a/panda/src/downloader/httpChannel.cxx b/panda/src/downloader/httpChannel.cxx index 65b9036094..3a45e3b532 100644 --- a/panda/src/downloader/httpChannel.cxx +++ b/panda/src/downloader/httpChannel.cxx @@ -16,7 +16,6 @@ #include "httpClient.h" #include "httpCookie.h" #include "bioStream.h" -#include "ssl_utils.h" #include "chunkedStream.h" #include "identityStream.h" #include "config_downloader.h" @@ -26,7 +25,6 @@ #include #ifdef HAVE_OPENSSL -#include "openssl/x509.h" #ifdef WIN32_VC #include @@ -989,7 +987,7 @@ run_connecting() { downloader_cat.info() << "Could not connect to " << _bio->get_server_name() << ":" << _bio->get_port() << "\n"; - notify_ssl_errors(); + OpenSSLWrapper::get_global_ptr()->notify_ssl_errors(); _status_entry._status_code = SC_no_connection; _state = S_try_next_proxy; return false; @@ -1487,7 +1485,7 @@ run_setup_ssl() { if (result == 0) { downloader_cat.error() << "Invalid cipher list: '" << cipher_list << "'\n"; - notify_ssl_errors(); + OpenSSLWrapper::get_global_ptr()->notify_ssl_errors(); _status_entry._status_code = SC_ssl_internal_failure; _state = S_failure; return false; @@ -1563,7 +1561,7 @@ run_ssl_handshake() { downloader_cat.info() << "Could not establish SSL handshake with " << _request.get_url().get_server_and_port() << "\n"; - notify_ssl_errors(); + OpenSSLWrapper::get_global_ptr()->notify_ssl_errors(); // It seems to be an error to free sbio at this point; perhaps // it's already been freed? @@ -2485,7 +2483,7 @@ begin_request(HTTPEnum::Method method, const DocumentSpec &url, } else { // Couldn't read the file. - notify_ssl_errors(); + OpenSSLWrapper::get_global_ptr()->notify_ssl_errors(); _status_entry._status_code = SC_no_connection; _state = S_failure; } diff --git a/panda/src/downloader/httpClient.cxx b/panda/src/downloader/httpClient.cxx index 2f66fb2ca8..ee58917d81 100644 --- a/panda/src/downloader/httpClient.cxx +++ b/panda/src/downloader/httpClient.cxx @@ -15,7 +15,6 @@ #include "httpClient.h" #include "httpChannel.h" #include "config_downloader.h" -#include "ssl_utils.h" #include "filename.h" #include "config_express.h" #include "virtualFileSystem.h" @@ -26,19 +25,6 @@ #ifdef HAVE_OPENSSL -#include "openssl/rand.h" -#include "openssl/err.h" - -// Windows may define this macro inappropriately. -#ifdef X509_NAME -#undef X509_NAME -#endif - -bool HTTPClient::_ssl_initialized = false; - -// This is created once and never freed. -X509_STORE *HTTPClient::_x509_store = NULL; - PT(HTTPClient) HTTPClient::_global_ptr; //////////////////////////////////////////////////////////////////// @@ -183,10 +169,8 @@ HTTPClient() { _client_certificate_priv = NULL; // The first time we create an HTTPClient, we must initialize the - // OpenSSL library. - if (!_ssl_initialized) { - initialize_ssl(); - } + // OpenSSL library. The OpenSSLWrapper object does that. + OpenSSLWrapper::get_global_ptr(); } //////////////////////////////////////////////////////////////////// @@ -238,7 +222,6 @@ HTTPClient:: // pointer from it, so it won't be destroyed along with it (this // object is shared among all contexts). if (_ssl_ctx != (SSL_CTX *)NULL) { - nassertv(_ssl_ctx->cert_store == _x509_store); _ssl_ctx->cert_store = NULL; SSL_CTX_free(_ssl_ctx); } @@ -267,16 +250,9 @@ HTTPClient:: //////////////////////////////////////////////////////////////////// void HTTPClient:: init_random_seed() { - static bool _initialized = false; - if (!_initialized) { - _initialized = true; - - // It is necessary to call this before making any other OpenSSL - // call, per the docs. Also, the docs say that making this call - // will seed the random number generator. Apparently you can get - // away with not calling it in versions prior to 0.9.8, however. - SSL_library_init(); - } + // Creating the global OpenSSLWrapper object is nowadays sufficient + // to ensure that OpenSSL and its random seed have been initialized. + OpenSSLWrapper::get_global_ptr(); } //////////////////////////////////////////////////////////////////// @@ -995,26 +971,8 @@ parse_http_version_string(const string &version) { //////////////////////////////////////////////////////////////////// bool HTTPClient:: load_certificates(const Filename &filename) { - // The line below might be a recursive call, but it should be safe, - // since get_ssl_ctx() won't call load_certificates() until after it - // has assigned _ssl_ctx--guaranteeing that the second call to - // get_ssl_ctx() will be a no-op. - SSL_CTX *ctx = get_ssl_ctx(); - - int result = load_verify_locations(ctx, filename); - - if (result <= 0) { - downloader_cat.info() - << "Could not load certificates from " << filename << ".\n"; - notify_ssl_errors(); - return false; - } - - downloader_cat.info() - << "Appending " << result << " SSL certificates from " - << filename << "\n"; - - return true; + OpenSSLWrapper *sslw = OpenSSLWrapper::get_global_ptr(); + return sslw->load_certificates(filename); } //////////////////////////////////////////////////////////////////// @@ -1178,7 +1136,8 @@ get_ssl_ctx() { #endif // Make sure the error strings are loaded. - notify_ssl_errors(); + OpenSSLWrapper *sslw = OpenSSLWrapper::get_global_ptr(); + sslw->notify_ssl_errors(); // Get the configured set of expected servers. int num_servers = expected_ssl_server.get_num_unique_values(); @@ -1187,29 +1146,7 @@ get_ssl_ctx() { add_expected_server(expected_server); } - if (_x509_store != (X509_STORE *)NULL) { - // If we've already created an x509 store object, share it with - // this context. It would be better to make a copy of the store - // object for each context, so we could locally add certificates, - // but (a) there doesn't seem to be an interface for this, and (b) - // something funny about loading certificates that seems to save - // some persistent global state anyway. - SSL_CTX_set_cert_store(_ssl_ctx, _x509_store); - - } else { - // Create the first x509 store object, and fill it up with our - // certificates. - _x509_store = X509_STORE_new(); - SSL_CTX_set_cert_store(_ssl_ctx, _x509_store); - - // Load in any default certificates listed in the Configrc file. - int num_certs = ssl_certificates.get_num_unique_values(); - for (int ci = 0; ci < num_certs; ci++) { - string cert_file = ssl_certificates.get_unique_value(ci); - Filename filename = Filename::expand_from(cert_file); - load_certificates(filename); - } - } + SSL_CTX_set_cert_store(_ssl_ctx, sslw->get_x509_store()); return _ssl_ctx; } @@ -1461,114 +1398,6 @@ unload_client_certificate() { _client_certificate_loaded = false; } -//////////////////////////////////////////////////////////////////// -// Function: HTTPClient::initialize_ssl -// Access: Private, Static -// Description: Called once the first time this class is used to -// initialize the OpenSSL library. -//////////////////////////////////////////////////////////////////// -void HTTPClient:: -initialize_ssl() { - OpenSSL_add_all_algorithms(); - _ssl_initialized = true; -} - -//////////////////////////////////////////////////////////////////// -// Function: HTTPClient::load_verify_locations -// Access: Private, Static -// Description: An implementation of the OpenSSL-provided -// SSL_CTX_load_verify_locations() that takes a Filename -// (and supports Panda vfs). -// -// This reads the certificates from the named ca_file -// and makes them available to the given SSL context. -// It returns a positive number on success, or <= 0 on -// failure. -//////////////////////////////////////////////////////////////////// -int HTTPClient:: -load_verify_locations(SSL_CTX *ctx, const Filename &ca_file) { - VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); - - // First, read the complete file into memory. - string data; - if (!vfs->read_file(ca_file, data, true)) { - // Could not find or read file. - downloader_cat.info() - << "Could not read " << ca_file << ".\n"; - return 0; - } - - STACK_OF(X509_INFO) *inf; - - // Now create an in-memory BIO to read the "file" from the buffer we - // just read, and call the low-level routines to read the - // certificates from the BIO. - BIO *mbio = BIO_new_mem_buf((void *)data.data(), data.length()); - - // We have to be sure and clear the OpenSSL error state before we - // call this function, or it will get confused. - ERR_clear_error(); - inf = PEM_X509_INFO_read_bio(mbio, NULL, NULL, NULL); - BIO_free(mbio); - - if (!inf) { - // Could not scan certificates. - downloader_cat.info() - << "PEM_X509_INFO_read_bio() returned NULL.\n"; - notify_ssl_errors(); - return 0; - } - - if (downloader_cat.is_spam()) { - downloader_cat.spam() - << "PEM_X509_INFO_read_bio() found " << sk_X509_INFO_num(inf) - << " entries.\n"; - } - - // Now add the certificates to the context. - X509_STORE *store = ctx->cert_store; - - int count = 0; - int num_entries = sk_X509_INFO_num(inf); - for (int i = 0; i < num_entries; i++) { - X509_INFO *itmp = sk_X509_INFO_value(inf, i); - - if (itmp->x509) { - X509_STORE_add_cert(store, itmp->x509); - count++; - if (downloader_cat.is_spam()) { - downloader_cat.spam() - << "Entry " << i << " is x509\n"; - } - - } else if (itmp->crl) { - X509_STORE_add_crl(store, itmp->crl); - count++; - if (downloader_cat.is_spam()) { - downloader_cat.spam() - << "Entry " << i << " is crl\n"; - } - - } else if (itmp->x_pkey) { - // X509_STORE_add_crl(store, itmp->x_pkey); - // count++; - if (downloader_cat.is_spam()) { - downloader_cat.spam() - << "Entry " << i << " is pkey\n"; - } - - } else { - if (downloader_cat.is_spam()) { - downloader_cat.spam() - << "Entry " << i << " is unknown type\n"; - } - } - } - sk_X509_INFO_pop_free(inf, X509_INFO_free); - - return count; -} - //////////////////////////////////////////////////////////////////// // Function: HTTPClient::parse_x509_name // Access: Private, Static diff --git a/panda/src/downloader/httpClient.h b/panda/src/downloader/httpClient.h index 98369dedea..2f055ab32f 100644 --- a/panda/src/downloader/httpClient.h +++ b/panda/src/downloader/httpClient.h @@ -23,7 +23,6 @@ // communications. #ifdef HAVE_OPENSSL -#define OPENSSL_NO_KRB5 #include "urlSpec.h" #include "httpAuthorization.h" @@ -35,13 +34,7 @@ #include "pmap.h" #include "pset.h" #include "referenceCount.h" - -#include "openssl/ssl.h" - -// Windows may define this macro inappropriately. -#ifdef X509_NAME -#undef X509_NAME -#endif +#include "openSSLWrapper.h" class Filename; class HTTPChannel; @@ -155,9 +148,6 @@ private: void unload_client_certificate(); - static void initialize_ssl(); - static int load_verify_locations(SSL_CTX *ctx, const Filename &ca_file); - static X509_NAME *parse_x509_name(const string &source); #if defined(SSL_097) && !defined(NDEBUG) @@ -206,9 +196,6 @@ private: X509 *_client_certificate_pub; EVP_PKEY *_client_certificate_priv; - static bool _ssl_initialized; - static X509_STORE *_x509_store; - static PT(HTTPClient) _global_ptr; friend class HTTPChannel; diff --git a/panda/src/downloader/ssl_utils.cxx b/panda/src/downloader/ssl_utils.cxx deleted file mode 100644 index 0d93fc685d..0000000000 --- a/panda/src/downloader/ssl_utils.cxx +++ /dev/null @@ -1,52 +0,0 @@ -// Filename: ssl_utils.cxx -// Created by: drose (15Dec03) -// -//////////////////////////////////////////////////////////////////// -// -// PANDA 3D SOFTWARE -// Copyright (c) Carnegie Mellon University. All rights reserved. -// -// All use of this software is subject to the terms of the revised BSD -// license. You should have received a copy of this license along -// with this source code in a file named "LICENSE." -// -//////////////////////////////////////////////////////////////////// - -#include "ssl_utils.h" -#include "config_downloader.h" - -#ifdef HAVE_OPENSSL - -#ifdef REPORT_OPENSSL_ERRORS -#include "openssl/err.h" -#endif - -//////////////////////////////////////////////////////////////////// -// Function: notify_ssl_errors -// Description: A convenience function that is itself a wrapper -// around the OpenSSL convenience function to output the -// recent OpenSSL errors. This function sends the error -// string to downloader_cat.warning(). If -// REPORT_OPENSSL_ERRORS is not defined, the function -// does nothing. -//////////////////////////////////////////////////////////////////// -void notify_ssl_errors() { -#ifdef REPORT_OPENSSL_ERRORS - static bool strings_loaded = false; - if (!strings_loaded) { - SSL_load_error_strings(); - strings_loaded = true; - } - - unsigned long e = ERR_get_error(); - while (e != 0) { - static const size_t buffer_len = 256; - char buffer[buffer_len]; - ERR_error_string_n(e, buffer, buffer_len); - downloader_cat.warning() << buffer << "\n"; - e = ERR_get_error(); - } -#endif // REPORT_OPENSSL_ERRORS -} - -#endif // HAVE_OPENSSL diff --git a/panda/src/downloadertools/multify.cxx b/panda/src/downloadertools/multify.cxx index 6e5bce4343..9935f5e0a8 100644 --- a/panda/src/downloadertools/multify.cxx +++ b/panda/src/downloadertools/multify.cxx @@ -22,6 +22,7 @@ #include "pointerTo.h" #include "filename.h" #include "pset.h" +#include "vector_string.h" #include #include @@ -47,6 +48,8 @@ Filename chdir_to; // -C bool got_chdir_to = false; size_t scale_factor = 0; // -F pset dont_compress; // -Z +vector_string sign_params; // -S +pvector cert_chain; // -N // Default extensions not to compress. May be overridden with -Z. string dont_compress_str = "jpg,png,mp3,ogg"; @@ -220,7 +223,27 @@ help() { " -1 .. -9\n" " Specify the compression level when -z is in effect. Larger numbers\n" " generate slightly smaller files, but compression takes longer. The\n" - " default is -" << default_compression_level << ".\n\n"; + " default is -" << default_compression_level << ".\n\n" + + " -N ca-chain.crt\n" + " Adds the indicated certificate chain file to the multifile. This must\n" + " used in conjunction with -c or -u mode. The indicated file must be\n" + " a PEM-formatted collection of certificates, representing the chain of\n" + " authentication from the certificate used to sign the multifile (-S,\n" + " below), and a certificate authority. This may be necessary for\n" + " certain certificates whose signing authority is one or more steps\n" + " removed from a well-known certificate authority. You must add this\n" + " file to the multifile before signing it (or at least at the same time).\n\n" + + " -S file.crt,file.key[,\"password\"]\n" + " Sign the multifile. The signing certificate should be in PEM form in\n" + " file.crt, with its private key in PEM form in file.key. If the key\n" + " is encrypted on-disk, specify the decryption password as the third\n" + " option. PEM form is the form accepted by the Apache web server. The\n" + " signature is written to the multifile to prove it is unchanged; any\n" + " subsequent change to the multifile will invalidate the signature.\n" + " This parameter may be repeated to sign the multifile with different\n" + " certificates.\n\n"; } const string & @@ -236,16 +259,17 @@ get_password() { bool -is_named(const string &subfile_name, int argc, char *argv[]) { +is_named(const string &subfile_name, const vector_string ¶ms) { // Returns true if the indicated subfile appears on the list of // files named on the command line. - if (argc < 2) { + if (params.empty()) { // No named files; everything is listed. return true; } - for (int i = 1; i < argc; i++) { - if (subfile_name == argv[i]) { + vector_string::const_iterator pi; + for (pi = params.begin(); pi != params.end(); ++pi) { + if (subfile_name == (*pi)) { return true; } } @@ -273,20 +297,39 @@ get_compression_level(const Filename &subfile_name) { } bool -add_directory(Multifile *multifile, const Filename &directory_name) { +do_add_files(Multifile *multifile, const pvector &filenames, + bool cert_chain_flag); + +bool +do_add_directory(Multifile *multifile, const Filename &directory_name, + bool cert_chain_flag) { vector_string files; if (!directory_name.scan_directory(files)) { cerr << "Unable to scan directory " << directory_name << "\n"; return false; } - bool okflag = true; - + pvector filenames; + filenames.reserve(files.size()); vector_string::const_iterator fi; for (fi = files.begin(); fi != files.end(); ++fi) { Filename subfile_name(directory_name, (*fi)); + filenames.push_back(subfile_name); + } + + return do_add_files(multifile, filenames, cert_chain_flag); +} + +bool +do_add_files(Multifile *multifile, const pvector &filenames, + bool cert_chain_flag) { + bool okflag = true; + pvector::const_iterator fi; + for (fi = filenames.begin(); fi != filenames.end(); ++fi) { + const Filename &subfile_name = (*fi); + if (subfile_name.is_directory()) { - if (!add_directory(multifile, subfile_name)) { + if (!do_add_directory(multifile, subfile_name, cert_chain_flag)) { okflag = false; } @@ -310,15 +353,20 @@ add_directory(Multifile *multifile, const Filename &directory_name) { if (verbose) { cout << new_subfile_name << "\n"; } + if (cert_chain_flag) { + // Set the cert_chain flag on the file. + int index = multifile->find_subfile(new_subfile_name); + nassertr(index >= 0, false); + multifile->set_subfile_is_cert_chain(index, true); + } } } } - return okflag; } bool -add_files(int argc, char *argv[]) { +add_files(const vector_string ¶ms) { PT(Multifile) multifile = new Multifile; if (append || update) { if (!multifile->open_read_write(multifile_name)) { @@ -350,36 +398,17 @@ add_files(int argc, char *argv[]) { multifile->set_scale_factor(scale_factor); } - bool okflag = true; - for (int i = 1; i < argc; i++) { - Filename subfile_name = Filename::from_os_specific(argv[i]); - if (subfile_name.is_directory()) { - if (!add_directory(multifile, subfile_name)) { - okflag = false; - } + pvector filenames; + filenames.reserve(params.size()); + vector_string::const_iterator si; + for (si = params.begin(); si != params.end(); ++si) { + Filename subfile_name = Filename::from_os_specific(*si); + filenames.push_back(subfile_name); + } - } else if (!subfile_name.exists()) { - cerr << "Not found: " << subfile_name << "\n"; - okflag = false; - - } else { - string new_subfile_name; - if (update) { - new_subfile_name = multifile->update_subfile - (subfile_name, subfile_name, get_compression_level(subfile_name)); - } else { - new_subfile_name = multifile->add_subfile - (subfile_name, subfile_name, get_compression_level(subfile_name)); - } - if (new_subfile_name.empty()) { - cerr << "Unable to add " << subfile_name << ".\n"; - okflag = false; - } else { - if (verbose) { - cout << new_subfile_name << "\n"; - } - } - } + bool okflag = do_add_files(multifile, filenames, false); + if (okflag && !cert_chain.empty()) { + okflag = do_add_files(multifile, cert_chain, true); } if (multifile->needs_repack()) { @@ -398,7 +427,7 @@ add_files(int argc, char *argv[]) { } bool -extract_files(int argc, char *argv[]) { +extract_files(const vector_string ¶ms) { if (!multifile_name.exists()) { cerr << multifile_name << " not found.\n"; return false; @@ -418,7 +447,7 @@ extract_files(int argc, char *argv[]) { bool any_encrypted = false; for (i = 0; i < num_subfiles && !any_encrypted; i++) { string subfile_name = multifile->get_subfile_name(i); - if (is_named(subfile_name, argc, argv)) { + if (is_named(subfile_name, params)) { if (multifile->is_subfile_encrypted(i)) { any_encrypted = true; } @@ -432,7 +461,7 @@ extract_files(int argc, char *argv[]) { // Now walk back through the list and this time do the extraction. for (i = 0; i < num_subfiles; i++) { string subfile_name = multifile->get_subfile_name(i); - if (is_named(subfile_name, argc, argv)) { + if (is_named(subfile_name, params)) { Filename filename = subfile_name; if (got_chdir_to) { filename = Filename(chdir_to, subfile_name); @@ -455,7 +484,7 @@ extract_files(int argc, char *argv[]) { } bool -kill_files(int argc, char *argv[]) { +kill_files(const vector_string ¶ms) { if (!multifile_name.exists()) { cerr << multifile_name << " not found.\n"; return false; @@ -473,7 +502,7 @@ kill_files(int argc, char *argv[]) { int i = 0; while (i < multifile->get_num_subfiles()) { string subfile_name = multifile->get_subfile_name(i); - if (is_named(subfile_name, argc, argv)) { + if (is_named(subfile_name, params)) { Filename filename = subfile_name; if (verbose) { @@ -502,6 +531,50 @@ kill_files(int argc, char *argv[]) { return okflag; } +bool +sign_multifile() { +#ifndef HAVE_OPENSSL + cerr << "Cannot sign multifiles without OpenSSL compiled in.\n"; + return false; + +#else // HAVE_OPENSSL + // Re-open the Multifile, and sign it with the indicated certificate + // and key files. + PT(Multifile) multifile = new Multifile; + if (!multifile->open_read_write(multifile_name)) { + cerr << "Unable to re-open " << multifile_name << " for signing.\n"; + return false; + } + + vector_string::iterator si; + for (si = sign_params.begin(); si != sign_params.end(); ++si) { + const string ¶m = (*si); + size_t comma1 = param.find(','); + if (comma1 == string::npos) { + cerr << "Signing parameter requires a comma: " << param << "\n"; + return false; + } + size_t comma2 = param.find(',', comma1 + 1); + + Filename certificate = Filename::from_os_specific(param.substr(0, comma1)); + Filename pkey; + string password; + if (comma2 != string::npos) { + pkey = Filename::from_os_specific(param.substr(comma1 + 1, comma2 - comma1 - 1)); + password = param.substr(comma2 + 1); + } else { + pkey = Filename::from_os_specific(param.substr(comma1 + 1)); + } + + if (!multifile->add_signature(certificate, pkey, password)) { + return false; + } + } + + return true; +#endif // HAVE_OPENSSL +} + const char * format_timestamp(bool record_timestamp, time_t timestamp) { static const size_t buffer_size = 512; @@ -533,7 +606,7 @@ format_timestamp(bool record_timestamp, time_t timestamp) { } bool -list_files(int argc, char *argv[]) { +list_files(const vector_string ¶ms) { if (!multifile_name.exists()) { cerr << multifile_name << " not found.\n"; return false; @@ -546,15 +619,20 @@ list_files(int argc, char *argv[]) { int num_subfiles = multifile->get_num_subfiles(); + int i; if (verbose) { cout << num_subfiles << " subfiles:\n" << flush; - for (int i = 0; i < num_subfiles; i++) { + for (i = 0; i < num_subfiles; i++) { string subfile_name = multifile->get_subfile_name(i); - if (is_named(subfile_name, argc, argv)) { + if (is_named(subfile_name, params)) { char encrypted_symbol = ' '; if (multifile->is_subfile_encrypted(i)) { encrypted_symbol = 'e'; } + char cert_chain_symbol = ' '; + if (multifile->get_subfile_is_cert_chain(i)) { + cert_chain_symbol = 'N'; + } if (multifile->is_subfile_compressed(i)) { size_t orig_length = multifile->get_subfile_length(i); size_t internal_length = multifile->get_subfile_internal_length(i); @@ -563,24 +641,25 @@ list_files(int argc, char *argv[]) { ratio = (double)internal_length / (double)orig_length; } if (ratio > 1.0) { - printf("%12d worse %c %s %s\n", + printf("%12d worse %c%c %s %s\n", (int)multifile->get_subfile_length(i), - encrypted_symbol, + encrypted_symbol, cert_chain_symbol, format_timestamp(multifile->get_record_timestamp(), multifile->get_subfile_timestamp(i)), subfile_name.c_str()); } else { - printf("%12d %3.0f%% %c %s %s\n", + printf("%12d %3.0f%% %c%c %s %s\n", (int)multifile->get_subfile_length(i), - 100.0 - ratio * 100.0, encrypted_symbol, + 100.0 - ratio * 100.0, + encrypted_symbol, cert_chain_symbol, format_timestamp(multifile->get_record_timestamp(), multifile->get_subfile_timestamp(i)), subfile_name.c_str()); } } else { - printf("%12d %c %s %s\n", + printf("%12d %c%c %s %s\n", (int)multifile->get_subfile_length(i), - encrypted_symbol, + encrypted_symbol, cert_chain_symbol, format_timestamp(multifile->get_record_timestamp(), multifile->get_subfile_timestamp(i)), subfile_name.c_str()); @@ -601,14 +680,35 @@ list_files(int argc, char *argv[]) { cout << "Multifile needs to be repacked.\n"; } } else { - for (int i = 0; i < num_subfiles; i++) { + for (i = 0; i < num_subfiles; i++) { string subfile_name = multifile->get_subfile_name(i); - if (is_named(subfile_name, argc, argv)) { + if (is_named(subfile_name, params)) { cout << subfile_name << "\n"; } } } +#ifdef HAVE_OPENSSL + int num_signatures = multifile->get_num_signatures(); + if (num_signatures != 0) { + multifile->load_certificate_chains(); + cout << "\n"; + for (i = 0; i < num_signatures; ++i) { + cout << "Signed by " << multifile->get_signature_common_name(i); + int verify_result = multifile->validate_signature_certificate(i); + if (verify_result == 0) { + cout << " (certificate validated)\n"; + } else { + cout << " (certificate unknown, reason " << verify_result << ")\n"; + } + if (verbose) { + multifile->write_signature_certificate(i, cout); + cout << "\n"; + } + } + } +#endif // HAVE_OPENSSL + return true; } @@ -647,7 +747,7 @@ main(int argc, char *argv[]) { extern char *optarg; extern int optind; - static const char *optflags = "crutxkvz123456789Z:T:f:OC:ep:P:F:h"; + static const char *optflags = "crutxkvz123456789Z:T:S:T:f:OC:ep:P:F:h"; int flag = getopt(argc, argv, optflags); Filename rel_path; while (flag != EOF) { @@ -715,6 +815,12 @@ main(int argc, char *argv[]) { case 'Z': dont_compress_str = optarg; break; + case 'N': + cert_chain.push_back(Filename::from_os_specific(optarg)); + break; + case 'S': + sign_params.push_back(optarg); + break; case 'T': { int flag; @@ -798,24 +904,35 @@ main(int argc, char *argv[]) { // Split out the extensions named by -Z into different words. tokenize_extensions(dont_compress_str, dont_compress); + // Build a list of remaining parameters. + vector_string params; + params.reserve(argc - 1); + for (int i = 1; i < argc; i++) { + params.push_back(argv[i]); + } + bool okflag = true; if (create || append || update) { - okflag = add_files(argc, argv); + okflag = add_files(params); } else if (extract) { if (got_record_timestamp_flag) { cerr << "Warning: -T ignored on extract.\n"; } - okflag = extract_files(argc, argv); + okflag = extract_files(params); } else if (kill_cmd) { if (got_record_timestamp_flag) { cerr << "Warning: -T ignored on kill.\n"; } - okflag = kill_files(argc, argv); + okflag = kill_files(params); } else { // list if (got_record_timestamp_flag) { cerr << "Warning: -T ignored on list.\n"; } - okflag = list_files(argc, argv); + okflag = list_files(params); + } + + if (okflag && !sign_params.empty()) { + sign_multifile(); } if (okflag) { diff --git a/panda/src/express/Sources.pp b/panda/src/express/Sources.pp index dec8e3e53f..01b0cd7798 100644 --- a/panda/src/express/Sources.pp +++ b/panda/src/express/Sources.pp @@ -35,6 +35,7 @@ nodePointerToBase.h nodePointerToBase.I \ nodePointerTo.h nodePointerTo.I \ nodeReferenceCount.h nodeReferenceCount.I \ + openSSLWrapper.h openSSLWrapper.I \ ordered_vector.h ordered_vector.I ordered_vector.T \ password_hash.h \ patchfile.I patchfile.h \ @@ -88,6 +89,7 @@ nodePointerToBase.cxx \ nodePointerTo.cxx \ nodeReferenceCount.cxx \ + openSSLWrapper.cxx \ ordered_vector.cxx \ password_hash.cxx \ patchfile.cxx \ @@ -147,6 +149,7 @@ nodePointerToBase.h nodePointerToBase.I \ nodePointerTo.h nodePointerTo.I \ nodeReferenceCount.h nodeReferenceCount.I \ + openSSLWrapper.h openSSLWrapper.I \ ordered_vector.h ordered_vector.I ordered_vector.T \ password_hash.h \ patchfile.I patchfile.h \ diff --git a/panda/src/express/config_express.cxx b/panda/src/express/config_express.cxx index 9961ec5b8a..05e144e582 100644 --- a/panda/src/express/config_express.cxx +++ b/panda/src/express/config_express.cxx @@ -69,6 +69,9 @@ ConfigVariableBool collect_tcp ConfigVariableDouble collect_tcp_interval ("collect-tcp-interval", 0.2); +ConfigVariableList ssl_certificates +("ssl-certificates"); + //////////////////////////////////////////////////////////////////// // Function: init_libexpress // Description: Initializes the library. This must be called at diff --git a/panda/src/express/config_express.h b/panda/src/express/config_express.h index 6442077aed..6ab94f3098 100644 --- a/panda/src/express/config_express.h +++ b/panda/src/express/config_express.h @@ -21,6 +21,8 @@ #include "configVariableBool.h" #include "configVariableInt.h" +#include "configVariableDouble.h" +#include "configVariableList.h" // Include this so interrogate can find it. #include "executionEnvironment.h" @@ -50,6 +52,7 @@ extern ConfigVariableBool keep_temporary_files; extern EXPCL_PANDAEXPRESS ConfigVariableBool collect_tcp; extern EXPCL_PANDAEXPRESS ConfigVariableDouble collect_tcp_interval; +extern ConfigVariableList ssl_certificates; // Expose the Config variable for Python access. BEGIN_PUBLISH diff --git a/panda/src/express/express_composite1.cxx b/panda/src/express/express_composite1.cxx index 19ac8a0c56..7f070d3da9 100644 --- a/panda/src/express/express_composite1.cxx +++ b/panda/src/express/express_composite1.cxx @@ -21,6 +21,7 @@ #include "nodePointerToBase.cxx" #include "nodePointerTo.cxx" #include "nodeReferenceCount.cxx" +#include "openSSLWrapper.cxx" #include "ordered_vector.cxx" #include "patchfile.cxx" #include "password_hash.cxx" diff --git a/panda/src/express/multifile.I b/panda/src/express/multifile.I index 23c2130be8..4ff32dc915 100644 --- a/panda/src/express/multifile.I +++ b/panda/src/express/multifile.I @@ -436,15 +436,26 @@ Subfile() { _source = (istream *)NULL; _flags = 0; _compression_level = 0; +#ifdef HAVE_OPENSSL + _pkey = NULL; +#endif } //////////////////////////////////////////////////////////////////// // Function: Multifile::Subfile::operator < // Access: Public -// Description: +// Description: Compares two Subfiles for proper sorting within the +// index. //////////////////////////////////////////////////////////////////// INLINE bool Multifile::Subfile:: operator < (const Multifile::Subfile &other) const { + // This should only be called on normal subfiles, not on certificate + // files or signature files. (We don't attempt to sort these + // special signature files.) + nassertr(!is_cert_special() && !other.is_cert_special(), false); + + // Normal subfiles are simply sorted in alphabetical order by + // filename. return _name < other._name; } @@ -484,3 +495,28 @@ INLINE bool Multifile::Subfile:: is_data_invalid() const { return (_flags & SF_data_invalid) != 0; } + +//////////////////////////////////////////////////////////////////// +// Function: Multifile::Subfile::is_cert_special +// Access: Public +// Description: Returns true if this Subfile represents a signature +// record, which is treated specially; or false if it is +// an ordinary Subfile. +//////////////////////////////////////////////////////////////////// +INLINE bool Multifile::Subfile:: +is_cert_special() const { + return (_flags & SF_signature) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: Multifile::Subfile::get_last_byte_pos +// Access: Public +// Description: Returns the byte position within the Multifile of the +// last byte that contributes to this Subfile, either in +// the index record or in the subfile data. +//////////////////////////////////////////////////////////////////// +INLINE streampos Multifile::Subfile:: +get_last_byte_pos() const { + return max(_index_start + (streampos)_index_length, + _data_start + (streampos)_data_length) - (streampos)1; +} diff --git a/panda/src/express/multifile.cxx b/panda/src/express/multifile.cxx index e254942b8e..cede3b03fa 100644 --- a/panda/src/express/multifile.cxx +++ b/panda/src/express/multifile.cxx @@ -20,6 +20,7 @@ #include "datagram.h" #include "zStream.h" #include "encryptStream.h" +#include "virtualFileSystem.h" #include @@ -116,6 +117,7 @@ Multifile() : _owns_stream = false; _next_index = 0; _last_index = 0; + _last_data_byte = 0; _needs_repack = false; _timestamp = 0; _timestamp_dirty = false; @@ -556,6 +558,375 @@ update_subfile(const string &subfile_name, const Filename &filename, return name; } +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::add_signature +// Access: Published +// Description: Adds a new signature to the Multifile. This +// signature associates the indicated certificate with +// the current contents of the Multifile. When the +// Multifile is read later, the signature will still be +// present only if the Multifile is unchanged; any +// subsequent changes to the Multifile will +// automatically invalidate and remove the signature. +// +// The specified private key must match the certificate, +// and the Multifile must be open in read-write mode. +// The private key is only used for generating the +// signature; it is not written to the Multifile and +// cannot be retrieved from the Multifile later. +// (However, the certificate *can* be retrieved from the +// Multifile later, to identify the entity that created +// the signature.) +// +// This implicitly causes a repack() operation if one is +// needed. Returns true on success, false on failure. +// +// This flavor of add_signature() reads the certificate +// and private key from a PEM-formatted file, for +// instance as generated by the openssl command. If the +// private key file is password-encrypted, the third +// parameter will be used as the password to decrypt it. +//////////////////////////////////////////////////////////////////// +bool Multifile:: +add_signature(const Filename &certificate, const Filename &pkey, + const string &password) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + // Read the certificate file from VFS. First, read the complete + // file into memory. + string certificate_data; + if (!vfs->read_file(certificate, certificate_data, true)) { + express_cat.info() + << "Could not read " << certificate << ".\n"; + return false; + } + + // Create an in-memory BIO to read the "file" from the buffer. + BIO *certificate_mbio = BIO_new_mem_buf((void *)certificate_data.data(), certificate_data.size()); + X509 *x509 = PEM_read_bio_X509(certificate_mbio, NULL, NULL, (void *)""); + BIO_free(certificate_mbio); + if (x509 == NULL) { + express_cat.info() + << "Could not read certificate in " << certificate << ".\n"; + return false; + } + + // Now do the same thing with the private key. This one may be + // password-encrypted on disk. + string pkey_data; + if (!vfs->read_file(pkey, pkey_data, true)) { + express_cat.info() + << "Could not read " << pkey << ".\n"; + return false; + } + + // Create an in-memory BIO to read the "file" from the buffer. + BIO *pkey_mbio = BIO_new_mem_buf((void *)pkey_data.data(), pkey_data.size()); + EVP_PKEY *evp_pkey = PEM_read_bio_PrivateKey(pkey_mbio, NULL, NULL, + (void *)password.c_str()); + BIO_free(pkey_mbio); + if (evp_pkey == NULL) { + express_cat.info() + << "Could not read private key in " << pkey << ".\n"; + X509_free(x509); + return false; + } + + bool result = add_signature(x509, evp_pkey); + + EVP_PKEY_free(evp_pkey); + X509_free(x509); + + return result; +} +#endif // HAVE_OPENSSL + +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::add_signature +// Access: Published +// Description: Adds a new signature to the Multifile. This +// signature associates the indicated certificate with +// the current contents of the Multifile. When the +// Multifile is read later, the signature will still be +// present only if the Multifile is unchanged; any +// subsequent changes to the Multifile will +// automatically invalidate and remove the signature. +// +// The specified private key must match the certificate, +// and the Multifile must be open in read-write mode. +// The private key is only used for generating the +// signature; it is not written to the Multifile and +// cannot be retrieved from the Multifile later. +// (However, the certificate *can* be retrieved from the +// Multifile later, to identify the entity that created +// the signature.) +// +// This implicitly causes a repack() operation if one is +// needed. Returns true on success, false on failure. +//////////////////////////////////////////////////////////////////// +bool Multifile:: +add_signature(X509 *certificate, EVP_PKEY *pkey) { + if (_needs_repack) { + if (!repack()) { + return false; + } + } else { + if (!flush()) { + return false; + } + } + + // Encode the certificate into DER form for writing to the + // Multifile. + int der_len = i2d_X509(certificate, NULL); + unsigned char *der_buf = new unsigned char[der_len]; + unsigned char *p = der_buf; + i2d_X509(certificate, &p); + string der_string((char *)der_buf, der_len); + delete[] der_buf; + istringstream der_stream(der_string); + + // Create a temporary Subfile for writing out the signature. + Subfile *subfile = new Subfile; + subfile->_pkey = pkey; + subfile->_flags |= SF_signature; + subfile->_source = &der_stream; + + // Write the new Subfile at the end. The cert_special subfiles + // always go at the end, because they're not the part of the file + // that's signed. + nassertr(_new_subfiles.empty(), false); + _new_subfiles.push_back(subfile); + bool result = flush(); + + delete subfile; + + return result; +} +#endif // HAVE_OPENSSL + +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::get_num_signatures +// Access: Published +// Description: Returns the number of signatures found on the +// Multifile. This reports only the signatures that +// match the contents of the Multifile; signatures that +// have become invalidated by a change in the Multifile +// are not reported here. However, this does not verify +// that the certificates themselves are valid; only that +// they match the Multifile contents. +//////////////////////////////////////////////////////////////////// +int Multifile:: +get_num_signatures() const { + ((Multifile *)this)->check_signatures(); + return _signatures.size(); +} +#endif // HAVE_OPENSSL + +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::get_signature +// Access: Published +// Description: Returns the nth signature found on the Multifile. +// See the comments in get_num_signatures(). +//////////////////////////////////////////////////////////////////// +X509 *Multifile:: +get_signature(int n) const { + nassertr(n >= 0 && n < (int)_signatures.size(), NULL); + return _signatures[n]; +} +#endif // HAVE_OPENSSL + +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::get_signature_subject_name +// Access: Published +// Description: Returns the "subject name" for the nth signature found +// on the Multifile. This is a string formatted +// according to RFC2253 that should more-or-less +// identify a particular certificate. See the comments +// in get_num_signatures(). +//////////////////////////////////////////////////////////////////// +string Multifile:: +get_signature_subject_name(int n) const { + X509 *x509 = get_signature(n); + nassertr(x509 != NULL, ""); + + X509_NAME *xname = X509_get_subject_name(x509); + if (xname != NULL) { + // We use "print" to dump the output to a memory BIO. Is + // there an easier way to extract the X509_NAME text? Curse + // these incomplete docs. + BIO *mbio = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(mbio, xname, 0, XN_FLAG_RFC2253); + + char *pp; + long pp_size = BIO_get_mem_data(mbio, &pp); + string name(pp, pp_size); + BIO_free(mbio); + return name; + } + + return string(); +} +#endif // HAVE_OPENSSL + +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::get_signature_common_name +// Access: Published +// Description: Returns the "common name" for the nth signature found +// on the Multifile, encoded in utf-8. This is a subset +// of the subject_name, but it is probably a friendlier +// string to present to a user. See the comments in +// get_num_signatures(). +//////////////////////////////////////////////////////////////////// +string Multifile:: +get_signature_common_name(int n) const { + X509 *x509 = get_signature(n); + nassertr(x509 != NULL, ""); + + // A complex OpenSSL interface to extract out the common name in + // utf-8. + X509_NAME *xname = X509_get_subject_name(x509); + if (xname != NULL) { + int pos = X509_NAME_get_index_by_NID(xname, NID_commonName, -1); + if (pos != -1) { + // We just get the first common name. I guess it's possible to + // have more than one; not sure what that means in this context. + X509_NAME_ENTRY *xentry = X509_NAME_get_entry(xname, pos); + if (xentry != NULL) { + ASN1_STRING *data = X509_NAME_ENTRY_get_data(xentry); + if (data != NULL) { + // We use "print" to dump the output to a memory BIO. Is + // there an easier way to decode the ASN1_STRING? Curse + // these incomplete docs. + BIO *mbio = BIO_new(BIO_s_mem()); + ASN1_STRING_print_ex(mbio, data, ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB); + + char *pp; + long pp_size = BIO_get_mem_data(mbio, &pp); + string name(pp, pp_size); + BIO_free(mbio); + return name; + } + } + } + } + + return string(); +} +#endif // HAVE_OPENSSL + +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::write_signature_certificate +// Access: Published +// Description: Writes the certificate for the nth signature, in +// verbose form, to the indicated stream. See the +// comments in get_num_signatures(). +//////////////////////////////////////////////////////////////////// +void Multifile:: +write_signature_certificate(int n, ostream &out) const { + X509 *x509 = get_signature(n); + nassertv(x509 != NULL); + + BIO *mbio = BIO_new(BIO_s_mem()); + X509_print(mbio, x509); + + char *pp; + long pp_size = BIO_get_mem_data(mbio, &pp); + out.write(pp, pp_size); + BIO_free(mbio); +} +#endif // HAVE_OPENSSL + +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::load_certificate_chains +// Access: Published +// Description: Loads any certificate chains specified in the +// Multifile into the global certificate store. This +// may be necessary to successfully validate the +// certificate used to sign the multifile in +// validate_signature_certificate(), below. +//////////////////////////////////////////////////////////////////// +void Multifile:: +load_certificate_chains() { + OpenSSLWrapper *sslw = OpenSSLWrapper::get_global_ptr(); + + Subfiles::iterator si; + for (si = _subfiles.begin(); si != _subfiles.end(); ++si) { + Subfile *subfile = (*si); + + if ((subfile->_flags & SF_cert_chain) != 0) { + // If it's a certificate chain, add it to the global chain list. + + // Read the cert chain into memory. + istream *stream = open_read_subfile(subfile); + nassertv(stream != NULL); + pvector buffer; + bool success = read_to_pvector(buffer, *stream); + nassertv(success); + close_read_subfile(stream); + + // Now it to the global chain. + if (!buffer.empty()) { + int result = sslw->load_certificates_from_ram((char *)&buffer[0], buffer.size()); + if (result > 0) { + express_cat.info() + << "Loaded " << result << " certificates from " + << subfile->_name << "\n"; + } + } + } + } +} +#endif // HAVE_OPENSSL + +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::validate_signature_certificate +// Access: Published +// Description: Checks that the certificate used for the nth +// signature is a valid, authorized certificate with +// some known certificate authority. Returns 0 if it +// is valid, -1 if there is some error, or the +// corresponding OpenSSL error code if it is invalid, +// out-of-date, or self-signed. +//////////////////////////////////////////////////////////////////// +int Multifile:: +validate_signature_certificate(int n) const { + int verify_result = -1; + + X509 *x509 = get_signature(n); + nassertr(x509 != NULL, false); + + OpenSSLWrapper *sslw = OpenSSLWrapper::get_global_ptr(); + + X509_STORE_CTX *ctx = X509_STORE_CTX_new(); + X509_STORE_CTX_init(ctx, sslw->get_x509_store(), x509, NULL); + X509_STORE_CTX_set_cert(ctx, x509); + + X509_verify_cert(ctx); + verify_result = X509_STORE_CTX_get_error(ctx); + + if (express_cat.is_debug()) { + express_cat.debug() + << get_signature_subject_name(n) << ": validate " << verify_result + << "\n"; + } + + X509_STORE_CTX_cleanup(ctx); + X509_STORE_CTX_free(ctx); + + return verify_result; +} +#endif // HAVE_OPENSSL + //////////////////////////////////////////////////////////////////// // Function: Multifile::flush // Access: Published @@ -673,6 +1044,10 @@ flush() { if (subfile->is_data_invalid()) { wrote_ok = false; } + + if (!subfile->is_cert_special()) { + _last_data_byte = max(_last_data_byte, subfile->get_last_byte_pos()); + } nassertr(_next_index == _write->tellp(), false); } @@ -711,6 +1086,7 @@ flush() { close(); return false; } + return true; } @@ -770,6 +1146,7 @@ repack() { copy(_subfiles.begin(), _subfiles.end(), back_inserter(_new_subfiles)); _next_index = 0; _last_index = 0; + _last_data_byte = 0; _scale_factor = _new_scale_factor; // And we write our contents to our new temporary file. @@ -999,7 +1376,7 @@ get_subfile_timestamp(int index) const { //////////////////////////////////////////////////////////////////// bool Multifile:: is_subfile_compressed(int index) const { - nassertr(index >= 0 && index < (int)_subfiles.size(), 0); + nassertr(index >= 0 && index < (int)_subfiles.size(), false); return (_subfiles[index]->_flags & SF_compressed) != 0; } @@ -1012,10 +1389,55 @@ is_subfile_compressed(int index) const { //////////////////////////////////////////////////////////////////// bool Multifile:: is_subfile_encrypted(int index) const { - nassertr(index >= 0 && index < (int)_subfiles.size(), 0); + nassertr(index >= 0 && index < (int)_subfiles.size(), false); return (_subfiles[index]->_flags & SF_encrypted) != 0; } +//////////////////////////////////////////////////////////////////// +// Function: Multifile::set_subfile_is_cert_chain +// Access: Published +// Description: Sets the cert_chain flag on the indicated subfile. +// This should be set for any subfiles that define the +// certificate chain that will be necessary to validate +// signatures that are subsequently used to sign the +// multifile. +// +// Note that the certificate chain subfiles must be +// added and this flag set *before* signing the +// multifile. +//////////////////////////////////////////////////////////////////// +void Multifile:: +set_subfile_is_cert_chain(int index, bool flag) { + nassertv(is_write_valid()); + nassertv(index >= 0 && index < (int)_subfiles.size()); + + Subfile *subfile = _subfiles[index]; + bool current_flag = (subfile->_flags & SF_cert_chain) != 0; + if (current_flag != flag) { + if (flag) { + subfile->_flags |= SF_cert_chain; + } else { + subfile->_flags &= ~SF_cert_chain; + } + subfile->rewrite_index_flags(*_write); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: Multifile::get_subfile_is_cert_chain +// Access: Published +// Description: Returns the cert_chain flag on the indicated subfile. +// This should be set for any subfiles that define the +// certificate chain that will be necessary to validate +// signatures that are subsequently used to sign the +// multifile. +//////////////////////////////////////////////////////////////////// +bool Multifile:: +get_subfile_is_cert_chain(int index) const { + nassertr(index >= 0 && index < (int)_subfiles.size(), false); + return (_subfiles[index]->_flags & SF_cert_chain) != 0; +} + //////////////////////////////////////////////////////////////////// // Function: Multifile::get_index_end // Access: Published @@ -1108,61 +1530,7 @@ open_read_subfile(int index) { nassertr(subfile == _subfiles[index], NULL); } - // Return an ISubStream object that references into the open - // Multifile istream. - nassertr(subfile->_data_start != (streampos)0, NULL); - istream *stream = - new ISubStream(_read, subfile->_data_start, - subfile->_data_start + (streampos)subfile->_data_length); - - if ((subfile->_flags & SF_encrypted) != 0) { -#ifndef HAVE_OPENSSL - express_cat.error() - << "OpenSSL not compiled in; cannot read encrypted multifiles.\n"; - delete stream; - return NULL; -#else // HAVE_OPENSSL - // The subfile is encrypted. So actually, return an - // IDecryptStream that wraps around the ISubStream. - IDecryptStream *wrapper = - new IDecryptStream(stream, true, _encryption_password); - stream = wrapper; - - // Validate the password by confirming that the encryption header - // matches. - char this_header[_encrypt_header_size]; - stream->read(this_header, _encrypt_header_size); - if (stream->fail() || stream->gcount() != (unsigned)_encrypt_header_size || - memcmp(this_header, _encrypt_header, _encrypt_header_size) != 0) { - express_cat.error() - << "Unable to decrypt subfile " << subfile->_name << ".\n"; - delete stream; - return NULL; - } -#endif // HAVE_OPENSSL - } - - if ((subfile->_flags & SF_compressed) != 0) { -#ifndef HAVE_ZLIB - express_cat.error() - << "zlib not compiled in; cannot read compressed multifiles.\n"; - delete stream; - return NULL; -#else // HAVE_ZLIB - // Oops, the subfile is compressed. So actually, return an - // IDecompressStream that wraps around the ISubStream. - IDecompressStream *wrapper = new IDecompressStream(stream, true); - stream = wrapper; -#endif // HAVE_ZLIB - } - - if (stream->fail()) { - // Hmm, some inexplicable problem. - delete stream; - return NULL; - } - - return stream; + return open_read_subfile(subfile); } //////////////////////////////////////////////////////////////////// @@ -1229,7 +1597,7 @@ extract_subfile_to(int index, ostream &out) { return false; } - static const size_t buffer_size = 1024; + static const size_t buffer_size = 4096; char buffer[buffer_size]; in->read(buffer, buffer_size); @@ -1397,6 +1765,9 @@ bool Multifile:: read_subfile(int index, string &result) { result = string(); + // We use a temporary pvector, because dynamic accumulation of a + // pvector seems to be many times faster than that of a string, at + // least on the Windows implementation of STL. pvector pv; if (!read_subfile(index, pv)) { return false; @@ -1426,20 +1797,9 @@ read_subfile(int index, pvector &result) { return false; } - static const size_t buffer_size = 1024; - char buffer[buffer_size]; - - in->read(buffer, buffer_size); - size_t count = in->gcount(); - while (count != 0) { - result.insert(result.end(), buffer, buffer + count); - in->read(buffer, buffer_size); - count = in->gcount(); - } - - bool failed = in->fail() && !in->eof(); + bool success = read_to_pvector(result, *in); close_read_subfile(in); - nassertr(!failed, false); + nassertr(success, false); return true; } @@ -1519,6 +1879,76 @@ add_new_subfile(Subfile *subfile, int compression_level) { _new_subfiles.push_back(subfile); } +//////////////////////////////////////////////////////////////////// +// Function: Multifile::open_read_subfile +// Access: Private +// Description: This variant of open_read_subfile() is used +// internally only, and accepts a pointer to the +// internal Subfile object, which is assumed to be valid +// and written to the multifile. +//////////////////////////////////////////////////////////////////// +istream *Multifile:: +open_read_subfile(Subfile *subfile) { + nassertr(subfile->_source == (istream *)NULL && + subfile->_source_filename.empty(), NULL); + + // Return an ISubStream object that references into the open + // Multifile istream. + nassertr(subfile->_data_start != (streampos)0, NULL); + istream *stream = + new ISubStream(_read, subfile->_data_start, + subfile->_data_start + (streampos)subfile->_data_length); + + if ((subfile->_flags & SF_encrypted) != 0) { +#ifndef HAVE_OPENSSL + express_cat.error() + << "OpenSSL not compiled in; cannot read encrypted multifiles.\n"; + delete stream; + return NULL; +#else // HAVE_OPENSSL + // The subfile is encrypted. So actually, return an + // IDecryptStream that wraps around the ISubStream. + IDecryptStream *wrapper = + new IDecryptStream(stream, true, _encryption_password); + stream = wrapper; + + // Validate the password by confirming that the encryption header + // matches. + char this_header[_encrypt_header_size]; + stream->read(this_header, _encrypt_header_size); + if (stream->fail() || stream->gcount() != (unsigned)_encrypt_header_size || + memcmp(this_header, _encrypt_header, _encrypt_header_size) != 0) { + express_cat.error() + << "Unable to decrypt subfile " << subfile->_name << ".\n"; + delete stream; + return NULL; + } +#endif // HAVE_OPENSSL + } + + if ((subfile->_flags & SF_compressed) != 0) { +#ifndef HAVE_ZLIB + express_cat.error() + << "zlib not compiled in; cannot read compressed multifiles.\n"; + delete stream; + return NULL; +#else // HAVE_ZLIB + // Oops, the subfile is compressed. So actually, return an + // IDecompressStream that wraps around the ISubStream. + IDecompressStream *wrapper = new IDecompressStream(stream, true); + stream = wrapper; +#endif // HAVE_ZLIB + } + + if (stream->fail()) { + // Hmm, some inexplicable problem. + delete stream; + return NULL; + } + + return stream; +} + //////////////////////////////////////////////////////////////////// // Function: Multifile::standardize_subfile_name // Access: Private @@ -1540,6 +1970,31 @@ standardize_subfile_name(const string &subfile_name) const { } } +//////////////////////////////////////////////////////////////////// +// Function: Multifile::read_to_pvector +// Access: Private, Static +// Description: Helper function to read the entire contents of the +// indicated stream from the current position, and +// append it onto the indicated pvector. Returns true +// on success, false on failure. +//////////////////////////////////////////////////////////////////// +bool Multifile:: +read_to_pvector(pvector &result, istream &stream) { + static const size_t buffer_size = 4096; + char buffer[buffer_size]; + + stream.read(buffer, buffer_size); + size_t count = stream.gcount(); + while (count != 0) { + result.insert(result.end(), buffer, buffer + count); + stream.read(buffer, buffer_size); + count = stream.gcount(); + } + + bool failed = stream.fail() && !stream.eof(); + return !failed; +} + //////////////////////////////////////////////////////////////////// // Function: Multifile::clear_subfiles // Access: Private @@ -1560,6 +2015,21 @@ clear_subfiles() { // also appear in _subfiles. _new_subfiles.clear(); +#ifdef HAVE_OPENSSL + for (pi = _cert_special.begin(); pi != _cert_special.end(); ++pi) { + Subfile *subfile = (*pi); + delete subfile; + } + _cert_special.clear(); + + Certificates::iterator ci; + for (ci = _signatures.begin(); ci != _signatures.end(); ++ci) { + X509 *cert = (*ci); + X509_free(cert); + } + _signatures.clear(); +#endif // HAVE_OPENSSL + Subfiles::iterator fi; for (fi = _subfiles.begin(); fi != _subfiles.end(); ++fi) { Subfile *subfile = (*fi); @@ -1674,7 +2144,10 @@ read_index() { _next_index = normalize_streampos(_next_index); read->seekg(_next_index); _last_index = 0; + _last_data_byte = 0; streampos index_forward; + streamoff bytes_skipped = 0; + bool read_cert_special = false; Subfile *subfile = new Subfile; index_forward = subfile->read_index(*read, _next_index, this); @@ -1684,14 +2157,31 @@ read_index() { // Ignore deleted Subfiles in the index. _needs_repack = true; delete subfile; + } else if (subfile->is_cert_special()) { + // Certificate chains and signature files get stored in a + // special list. + _cert_special.push_back(subfile); + read_cert_special = true; } else { _subfiles.push_back(subfile); } - if (index_forward != normalize_streampos(read->tellg())) { - // If the index entries don't follow exactly sequentially, the - // file ought to be repacked. - _needs_repack = true; + if (!subfile->is_cert_special()) { + if (bytes_skipped != 0) { + // If the index entries don't follow exactly sequentially + // (except for the cert special files), the file ought to be + // repacked. + _needs_repack = true; + } + if (read_cert_special) { + // If we read a normal subfile following a cert_special entry, + // the file ought to be repacked (certificates have to go at + // the end). + _needs_repack = true; + } + _last_data_byte = max(_last_data_byte, subfile->get_last_byte_pos()); } + streampos curr_pos = normalize_streampos(read->tellg()); + bytes_skipped = index_forward - curr_pos; read->seekg(index_forward); _next_index = index_forward; subfile = new Subfile; @@ -1771,6 +2261,107 @@ write_header() { return true; } +//////////////////////////////////////////////////////////////////// +// Function: Multifile::check_signatures +// Access: Private +// Description: Walks through the list of _cert_special entries in +// the Multifile, moving any valid signatures found to +// _signatures. After this call, _cert_special will be +// empty. +// +// This does not check the validity of the certificates +// themselves. It only checks that they correctly sign +// the Multifile contents. +//////////////////////////////////////////////////////////////////// +void Multifile:: +check_signatures() { +#ifdef HAVE_OPENSSL + PendingSubfiles::iterator pi; + + for (pi = _cert_special.begin(); pi != _cert_special.end(); ++pi) { + Subfile *subfile = (*pi); + nassertv((subfile->_flags & SF_signature) != 0); + + // Extract the signature data and certificate separately. + istream *stream = open_read_subfile(subfile); + nassertv(stream != NULL); + StreamReader reader(*stream); + size_t sig_size = reader.get_uint32(); + string sig_string = reader.extract_bytes(sig_size); + + pvector buffer; + bool success = read_to_pvector(buffer, *stream); + nassertv(success); + close_read_subfile(stream); + + X509 *x509 = NULL; + EVP_PKEY *pkey = NULL; + if (!buffer.empty()) { + const unsigned char *bp = (const unsigned char *)&buffer[0]; + x509 = d2i_X509(NULL, &bp, buffer.size()); + } + + if (x509 != NULL) { + pkey = X509_get_pubkey(x509); + } + + if (pkey != NULL) { + EVP_MD_CTX *md_ctx; +#ifdef SSL_097 + md_ctx = EVP_MD_CTX_create(); +#else + md_ctx = new EVP_MD_CTX; +#endif + EVP_VerifyInit(md_ctx, EVP_sha1()); + + nassertv(_read != NULL); + _read->acquire(); + istream *read = _read->get_istream(); + + // Read and hash the multifile contents, but only up till + // _last_data_byte. + read->seekg(0); + streampos bytes_remaining = _last_data_byte; + static const size_t buffer_size = 4096; + char buffer[buffer_size]; + read->read(buffer, min((streampos)buffer_size, bytes_remaining)); + size_t count = read->gcount(); + while (count != 0) { + nassertv(count <= buffer_size); + EVP_VerifyUpdate(md_ctx, buffer, count); + bytes_remaining -= count; + read->read(buffer, min((streampos)buffer_size, bytes_remaining)); + count = read->gcount(); + } + nassertv(bytes_remaining == (streampos)0); + _read->release(); + + // Now check that the signature matches the hash. + int verify_result = + EVP_VerifyFinal(md_ctx, + (unsigned char *)sig_string.data(), + sig_string.size(), pkey); + if (verify_result == 1) { + // The signature matches; save the certificate. + _signatures.push_back(x509); + x509 = NULL; + } else { + // Bad match. + _needs_repack = true; + } + } + + if (x509 != NULL) { + // If we still have the X509 pointer by this point, we haven't + // saved it anywhere, so free it now. + X509_free(x509); + } + } +#endif // HAVE_OPENSSL + + _cert_special.clear(); +} + //////////////////////////////////////////////////////////////////// // Function: Multifile::Subfile::read_index // Access: Public @@ -1801,6 +2392,7 @@ read_index(istream &read, streampos fpos, Multifile *multifile) { // Now get the rest of the index. _index_start = fpos; + _index_length = 0; _data_start = multifile->word_to_streampos(reader.get_uint32()); _data_length = reader.get_uint32(); @@ -1839,6 +2431,7 @@ read_index(istream &read, streampos fpos, Multifile *multifile) { return 0; } + _index_length = read.tellg() - fpos; return next_index; } @@ -1859,6 +2452,7 @@ write_index(ostream &write, streampos fpos, Multifile *multifile) { nassertr(write.tellp() == fpos, fpos); _index_start = fpos; + _index_length = 0; // This will be the contents of this particular index record. We // build it up first since it will be variable length. @@ -1893,6 +2487,7 @@ write_index(ostream &write, streampos fpos, Multifile *multifile) { write.write((const char *)idg.get_data(), idg.get_length()); write.write((const char *)dg.get_data(), dg.get_length()); + _index_length = write.tellp() - fpos; return next_index; } @@ -1970,7 +2565,8 @@ write_data(ostream &write, istream *read, streampos fpos, // Without OpenSSL, we can't support encryption. The flag had // better not be set. nassertr((_flags & SF_encrypted) == 0, fpos); -#else // HAVE_ZLIB + +#else // HAVE_OPENSSL if ((_flags & SF_encrypted) != 0) { // Write it encrypted. OEncryptStream *encrypt = new OEncryptStream; @@ -1985,7 +2581,7 @@ write_data(ostream &write, istream *read, streampos fpos, // decryption. putter->write(_encrypt_header, _encrypt_header_size); } -#endif // HAVE_ZLIB +#endif // HAVE_OPENSSL #ifndef HAVE_ZLIB // Without ZLIB, we can't support compression. The flag had @@ -2002,7 +2598,76 @@ write_data(ostream &write, istream *read, streampos fpos, streampos write_start = fpos; _uncompressed_length = 0; - static const size_t buffer_size = 1024; +#ifndef HAVE_OPENSSL + // We also need OpenSSL for signatures. + nassertr((_flags & SF_signature) == 0, fpos); + +#else // HAVE_OPENSSL + if ((_flags & SF_signature) != 0) { + // If it's a special signature record, precede the record data + // (the certificate itself) with the signature data generated + // against the multifile contents. + + // In order to generate a signature, we need to have a valid + // read pointer. + nassertr(read != NULL, fpos); + + // And we also need to have a private key. + nassertr(_pkey != NULL, fpos); + + EVP_MD_CTX *md_ctx; +#ifdef SSL_097 + md_ctx = EVP_MD_CTX_create(); +#else + md_ctx = new EVP_MD_CTX; +#endif + EVP_SignInit(md_ctx, EVP_sha1()); + + // Read and hash the multifile contents, but only up till + // _last_data_byte. + nassertr(multifile->_last_data_byte < fpos, fpos); + read->seekg(0); + streampos bytes_remaining = multifile->_last_data_byte; + static const size_t buffer_size = 4096; + char buffer[buffer_size]; + read->read(buffer, min((streampos)buffer_size, bytes_remaining)); + size_t count = read->gcount(); + while (count != 0) { + nassertr(count <= buffer_size, fpos); + EVP_SignUpdate(md_ctx, buffer, count); + bytes_remaining -= count; + read->read(buffer, min((streampos)buffer_size, bytes_remaining)); + count = read->gcount(); + } + nassertr(bytes_remaining == (streampos)0, fpos); + + // Now generate and write out the signature. + unsigned int max_size = EVP_PKEY_size(_pkey); + unsigned char *sig_data = new unsigned char[max_size]; + unsigned int sig_size; + if (!EVP_SignFinal(md_ctx, sig_data, &sig_size, _pkey)) { + OpenSSLWrapper *sslw = OpenSSLWrapper::get_global_ptr(); + sslw->notify_ssl_errors(); + } + nassertr(sig_size <= max_size, fpos); + + StreamWriter writer(*putter); + writer.add_uint32(sig_size); + putter->write((char *)sig_data, sig_size); + _uncompressed_length += 4 + sig_size; + + delete[] sig_data; + +#ifdef SSL_097 + EVP_MD_CTX_destroy(md_ctx); +#else + delete md_ctx; +#endif + } +#endif // HAVE_OPENSSL + + // Finally, we can write out the data itself. + static const size_t buffer_size = 4096; char buffer[buffer_size]; source->read(buffer, buffer_size); @@ -2076,7 +2741,7 @@ rewrite_index_data_start(ostream &write, Multifile *multifile) { //////////////////////////////////////////////////////////////////// // Function: Multifile::Subfile::rewrite_index_flags // Access: Public -// Description: Seeks within the indicate pfstream back to the index +// Description: Seeks within the indicated ostream back to the index // record and rewrites just the _flags part of the // index record. //////////////////////////////////////////////////////////////////// diff --git a/panda/src/express/multifile.h b/panda/src/express/multifile.h index 04d79c30c6..3951031b08 100644 --- a/panda/src/express/multifile.h +++ b/panda/src/express/multifile.h @@ -25,6 +25,7 @@ #include "indirectLess.h" #include "referenceCount.h" #include "pvector.h" +#include "openSSLWrapper.h" //////////////////////////////////////////////////////////////////// // Class : Multifile @@ -81,6 +82,22 @@ PUBLISHED: int compression_level); string update_subfile(const string &subfile_name, const Filename &filename, int compression_level); + +#ifdef HAVE_OPENSSL + bool add_signature(const Filename &certificate, const Filename &pkey, + const string &password = ""); + bool add_signature(X509 *certificate, EVP_PKEY *pkey); + + int get_num_signatures() const; + X509 *get_signature(int n) const; + string get_signature_subject_name(int n) const; + string get_signature_common_name(int n) const; + void write_signature_certificate(int n, ostream &out) const; + + void load_certificate_chains(); + int validate_signature_certificate(int n) const; +#endif // HAVE_OPENSSL + BLOCKING bool flush(); BLOCKING bool repack(); @@ -97,6 +114,8 @@ PUBLISHED: time_t get_subfile_timestamp(int index) const; bool is_subfile_compressed(int index) const; bool is_subfile_encrypted(int index) const; + void set_subfile_is_cert_chain(int index, bool flag); + bool get_subfile_is_cert_chain(int index) const; streampos get_index_end() const; streampos get_subfile_internal_start(int index) const; @@ -128,6 +147,8 @@ private: SF_data_invalid = 0x0004, SF_compressed = 0x0008, SF_encrypted = 0x0010, + SF_cert_chain = 0x0020, + SF_signature = 0x0040, }; class Subfile { @@ -145,9 +166,12 @@ private: INLINE bool is_deleted() const; INLINE bool is_index_invalid() const; INLINE bool is_data_invalid() const; + INLINE bool is_cert_special() const; + INLINE streampos get_last_byte_pos() const; string _name; streampos _index_start; + size_t _index_length; streampos _data_start; size_t _data_length; size_t _uncompressed_length; @@ -156,6 +180,9 @@ private: Filename _source_filename; int _flags; int _compression_level; // Not preserved on disk. +#ifdef HAVE_OPENSSL + EVP_PKEY *_pkey; // Not preserved on disk. +#endif }; INLINE streampos word_to_streampos(size_t word) const; @@ -164,24 +191,34 @@ private: streampos pad_to_streampos(streampos fpos); void add_new_subfile(Subfile *subfile, int compression_level); + istream *open_read_subfile(Subfile *subfile); string standardize_subfile_name(const string &subfile_name) const; + static bool read_to_pvector(pvector &result, istream &stream); void clear_subfiles(); bool read_index(); bool write_header(); + void check_signatures(); typedef ov_set > Subfiles; Subfiles _subfiles; typedef pvector PendingSubfiles; PendingSubfiles _new_subfiles; PendingSubfiles _removed_subfiles; + PendingSubfiles _cert_special; + +#ifdef HAVE_OPENSSL + typedef pvector Certificates; + Certificates _signatures; +#endif IStreamWrapper *_read; ostream *_write; bool _owns_stream; streampos _next_index; streampos _last_index; + streampos _last_data_byte; bool _needs_repack; time_t _timestamp; diff --git a/panda/src/downloader/ssl_utils.h b/panda/src/express/openSSLWrapper.I similarity index 56% rename from panda/src/downloader/ssl_utils.h rename to panda/src/express/openSSLWrapper.I index 8b36a86b5d..c7e12551c7 100644 --- a/panda/src/downloader/ssl_utils.h +++ b/panda/src/express/openSSLWrapper.I @@ -1,5 +1,5 @@ -// Filename: ssl_utils.h -// Created by: drose (15Dec03) +// Filename: openSSLWrapper.I +// Created by: drose (05Sep09) // //////////////////////////////////////////////////////////////////// // @@ -12,21 +12,3 @@ // //////////////////////////////////////////////////////////////////// -#ifndef SSL_UTILS_H -#define SSL_UTILS_H - -#include "pandabase.h" - -// This module is not compiled if OpenSSL is not available. -#ifdef HAVE_OPENSSL -#define OPENSSL_NO_KRB5 - -#include "openssl/ssl.h" - -EXPCL_PANDAEXPRESS void notify_ssl_errors(); - -#endif // HAVE_OPENSSL - -#endif - - diff --git a/panda/src/express/openSSLWrapper.cxx b/panda/src/express/openSSLWrapper.cxx new file mode 100644 index 0000000000..f8de7731c8 --- /dev/null +++ b/panda/src/express/openSSLWrapper.cxx @@ -0,0 +1,241 @@ +// Filename: openSSLWrapper.cxx +// Created by: drose (05Sep09) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "openSSLWrapper.h" + +#ifdef HAVE_OPENSSL + +#include "virtualFileSystem.h" + +OpenSSLWrapper *OpenSSLWrapper::_global_ptr = NULL; + +//////////////////////////////////////////////////////////////////// +// Function: OpenSSLWrapper::Constructor +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +OpenSSLWrapper:: +OpenSSLWrapper() { + // It is necessary to call this before making any other OpenSSL + // call, per the docs. Also, the docs say that making this call + // will seed the random number generator. Apparently you can get + // away with not calling it in versions prior to 0.9.8, however. + SSL_library_init(); + + OpenSSL_add_all_algorithms(); + + _x509_store = X509_STORE_new(); + + // Load in any default certificates listed in the Config.prc file. + int num_certs = ssl_certificates.get_num_unique_values(); + for (int ci = 0; ci < num_certs; ci++) { + string cert_file = ssl_certificates.get_unique_value(ci); + Filename filename = Filename::expand_from(cert_file); + load_certificates(filename); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: OpenSSLWrapper::Destructor +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +OpenSSLWrapper:: +~OpenSSLWrapper() { + // Actually, the destructor is never called. + X509_STORE_free(_x509_store); +} + +//////////////////////////////////////////////////////////////////// +// Function: OpenSSLWrapper::load_certificates +// Access: Public +// Description: Reads the certificate(s) (delimited by -----BEGIN +// CERTIFICATE----- and -----END CERTIFICATE-----) from +// the indicated file and adds them to the global store +// object, retrieved via get_x509_store(). +// +// Returns the number of certificates read on success, +// or 0 on failure. +//////////////////////////////////////////////////////////////////// +int OpenSSLWrapper:: +load_certificates(const Filename &filename) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + // First, read the complete file into memory. + string data; + if (!vfs->read_file(filename, data, true)) { + // Could not find or read file. + express_cat.info() + << "Could not read " << filename << ".\n"; + return 0; + } + + int result = load_certificates_from_ram(data.data(), data.size()); + + if (result <= 0) { + express_cat.info() + << "Could not load certificates from " << filename << ".\n"; + notify_ssl_errors(); + return 0; + } + + express_cat.info() + << "Appending " << result << " SSL certificates from " + << filename << "\n"; + + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: OpenSSLWrapper::load_certificates_from_ram +// Access: Public +// Description: Reads a chain of certificates from the indicated +// data buffer and adds them to the X509_STORE object. +// Returns the number of certificates read on success, +// or 0 on failure. +//////////////////////////////////////////////////////////////////// +int OpenSSLWrapper:: +load_certificates_from_ram(const char *data, size_t data_size) { + STACK_OF(X509_INFO) *inf; + + // Create an in-memory BIO to read the "file" from the buffer, and + // call the low-level routines to read the certificates from the + // BIO. + BIO *mbio = BIO_new_mem_buf((void *)data, data_size); + + // We have to be sure and clear the OpenSSL error state before we + // call this function, or it will get confused. + ERR_clear_error(); + inf = PEM_X509_INFO_read_bio(mbio, NULL, NULL, NULL); + BIO_free(mbio); + + if (!inf) { + // Could not scan certificates. + express_cat.info() + << "PEM_X509_INFO_read_bio() returned NULL.\n"; + notify_ssl_errors(); + return 0; + } + + if (express_cat.is_spam()) { + express_cat.spam() + << "PEM_X509_INFO_read_bio() found " << sk_X509_INFO_num(inf) + << " entries.\n"; + } + + // Now add the certificates to the store. + + int count = 0; + int num_entries = sk_X509_INFO_num(inf); + for (int i = 0; i < num_entries; i++) { + X509_INFO *itmp = sk_X509_INFO_value(inf, i); + + if (itmp->x509) { + X509_STORE_add_cert(_x509_store, itmp->x509); + count++; + if (express_cat.is_spam()) { + express_cat.spam() + << "Entry " << i << " is x509\n"; + } + + } else if (itmp->crl) { + X509_STORE_add_crl(_x509_store, itmp->crl); + count++; + if (express_cat.is_spam()) { + express_cat.spam() + << "Entry " << i << " is crl\n"; + } + + } else if (itmp->x_pkey) { + // X509_STORE_add_crl(_x509_store, itmp->x_pkey); + // count++; + if (express_cat.is_spam()) { + express_cat.spam() + << "Entry " << i << " is pkey\n"; + } + + } else { + if (express_cat.is_spam()) { + express_cat.spam() + << "Entry " << i << " is unknown type\n"; + } + } + } + sk_X509_INFO_pop_free(inf, X509_INFO_free); + + return count; +} + +//////////////////////////////////////////////////////////////////// +// Function: OpenSSLWrapper::get_x509_store +// Access: Public +// Description: Returns the global X509_STORE object. +// +// It has to be a global object, because OpenSSL seems +// to store some global pointers associated with this +// object whether you want it to or not, and keeping +// independent copies of a local X509_STORE object +// doesn't seem to work that well. So, we have one +// store that keeps all certificates the application +// might need. +//////////////////////////////////////////////////////////////////// +X509_STORE *OpenSSLWrapper:: +get_x509_store() { + return _x509_store; +} + +//////////////////////////////////////////////////////////////////// +// Function: OpenSSLWrapper::notify_ssl_errors +// Access: Public +// Description: A convenience function that is itself a wrapper +// around the OpenSSL convenience function to output the +// recent OpenSSL errors. This function sends the error +// string to express_cat.warning(). If +// REPORT_OPENSSL_ERRORS is not defined, the function +// does nothing. +//////////////////////////////////////////////////////////////////// +void OpenSSLWrapper:: +notify_ssl_errors() { +#ifdef REPORT_OPENSSL_ERRORS + static bool strings_loaded = false; + if (!strings_loaded) { + SSL_load_error_strings(); + strings_loaded = true; + } + + unsigned long e = ERR_get_error(); + while (e != 0) { + static const size_t buffer_len = 256; + char buffer[buffer_len]; + ERR_error_string_n(e, buffer, buffer_len); + express_cat.warning() << buffer << "\n"; + e = ERR_get_error(); + } +#endif // REPORT_OPENSSL_ERRORS +} + +//////////////////////////////////////////////////////////////////// +// Function: OpenSSLWrapper::get_global_ptr +// Access: Public, Static +// Description: +//////////////////////////////////////////////////////////////////// +OpenSSLWrapper *OpenSSLWrapper:: +get_global_ptr() { + if (_global_ptr == NULL) { + _global_ptr = new OpenSSLWrapper; + } + return _global_ptr; +} + +#endif // HAVE_OPENSSL diff --git a/panda/src/express/openSSLWrapper.h b/panda/src/express/openSSLWrapper.h new file mode 100644 index 0000000000..2208cdbd6a --- /dev/null +++ b/panda/src/express/openSSLWrapper.h @@ -0,0 +1,63 @@ +// Filename: openSSLWrapper.h +// Created by: drose (05Sep09) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef OPENSSLWRAPPER_H +#define OPENSSLWRAPPER_H + +#include "pandabase.h" + +#ifdef HAVE_OPENSSL + +#define OPENSSL_NO_KRB5 +#include "openssl/ssl.h" +#include "openssl/rand.h" +#include "openssl/err.h" +#include "openssl/x509.h" + +// Windows may define this macro inappropriately. +#ifdef X509_NAME +#undef X509_NAME +#endif + +//////////////////////////////////////////////////////////////////// +// Class : OpenSSLWrapper +// Description : Provides an interface wrapper around the OpenSSL +// library, to ensure that the library is properly +// initialized in the application, and to provide some +// hooks into global OpenSSL context data. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS OpenSSLWrapper { +private: + OpenSSLWrapper(); + ~OpenSSLWrapper(); + +public: + int load_certificates(const Filename &filename); + int load_certificates_from_ram(const char *data, size_t data_size); + X509_STORE *get_x509_store(); + + void notify_ssl_errors(); + + static OpenSSLWrapper *get_global_ptr(); + +private: + X509_STORE *_x509_store; + + static OpenSSLWrapper *_global_ptr; +}; + +#include "openSSLWrapper.I" + +#endif // HAVE_OPENSSL +#endif