more security work

This commit is contained in:
David Rose 2009-09-11 23:16:05 +00:00
parent 9f734e3cbf
commit b52dfe14dd
20 changed files with 798 additions and 129 deletions

View File

@ -474,9 +474,16 @@ class AppRunner(DirectObject):
self.instanceId = instanceId
self.tokens = tokens
self.tokenDict = dict(tokens)
self.argv = argv
# We build up a token dictionary with care, so that if a given
# token appears twice in the token list, we record only the
# first value, not the second or later. This is consistent
# with the internal behavior of the core API.
self.tokenDict = {}
for token, keyword in tokens:
self.tokenDict.setdefault(token, keyword)
# Also store the arguments on sys, for applications that
# aren't instance-ready.
sys.argv = argv

View File

@ -46,10 +46,11 @@ class images(package):
print "Could not locate %s" % (filename)
# Also make a few special cases. We use the same default image
# for download, ready, and launch.
# for download, ready, unauth, and launch.
download = configDict.get('download_img', None)
if download:
configDict['ready_img'] = download
configDict['unauth_img'] = download
configDict['launch_img'] = download
config(**configDict)

View File

@ -127,7 +127,7 @@ load_plugin(const string &p3d_plugin_filename,
const string &contents_filename, const string &download_url,
bool verify_contents, const string &platform,
const string &log_directory, const string &log_basename,
bool keep_cwd) {
bool trusted_environment) {
string filename = p3d_plugin_filename;
if (filename.empty()) {
// Look for the plugin along the path.
@ -301,7 +301,7 @@ load_plugin(const string &p3d_plugin_filename,
if (!P3D_initialize(P3D_API_VERSION, contents_filename.c_str(),
download_url.c_str(), verify_contents, platform.c_str(),
log_directory.c_str(), log_basename.c_str(),
keep_cwd)) {
trusted_environment)) {
// Oops, failure to initialize.
cerr << "Failed to initialize plugin (wrong API version?)\n";
unload_plugin();

View File

@ -63,7 +63,7 @@ load_plugin(const string &p3d_plugin_filename,
const string &contents_filename, const string &download_url,
bool verify_contents, const string &platform,
const string &log_directory, const string &log_basename,
bool keep_cwd);
bool trusted_environment);
void unload_plugin();
bool is_plugin_loaded();

View File

@ -71,17 +71,3 @@ inline bool P3DHost::
has_contents_file() const {
return (_xcontents != NULL);
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::encode_hexdigit
// Access: Private
// Description: Returns the hex digit corresponding to the
// indicated integer value.
////////////////////////////////////////////////////////////////////
inline char P3DHost::
encode_hexdigit(int c) {
if (c >= 10) {
return c - 10 + 'a';
}
return c + '0';
}

View File

@ -293,8 +293,8 @@ determine_host_dir() {
for (size_t i = 0; i < keep_hash; ++i) {
int high = (md[i] >> 4) & 0xf;
int low = md[i] & 0xf;
_host_dir += encode_hexdigit(high);
_host_dir += encode_hexdigit(low);
_host_dir += P3DInstanceManager::encode_hexdigit(high);
_host_dir += P3DInstanceManager::encode_hexdigit(low);
}
}

View File

@ -56,7 +56,6 @@ private:
static string standardize_filename(const string &filename);
static bool copy_file(const string &from_filename, const string &to_filename);
static inline char encode_hexdigit(int c);
private:
string _host_dir;

View File

@ -55,6 +55,10 @@ const char *P3DInstance::_image_type_names[P3DInstance::IT_num_image_types] = {
"play_ready",
"play_rollover",
"play_click",
"unauth",
"auth_ready",
"auth_rollover",
"auth_click",
"none", // Not really used.
};
@ -80,6 +84,7 @@ P3DInstance(P3D_request_ready_func *func,
_current_button_image = IT_none;
_got_fparams = false;
_got_wparams = false;
_p3d_trusted = false;
_fparams.set_tokens(tokens, num_tokens);
_fparams.set_args(argc, argv);
@ -89,6 +94,7 @@ P3DInstance(P3D_request_ready_func *func,
_hidden = false;
_allow_python_dev = false;
_auto_start = false;
_auth_button_approved = false;
_session = NULL;
_panda3d = NULL;
_splash_window = NULL;
@ -116,6 +122,10 @@ P3DInstance(P3D_request_ready_func *func,
_panda_script_object->set_string_property("downloadPackageDisplayName", "");
_panda_script_object->set_bool_property("downloadComplete", false);
_panda_script_object->set_string_property("status", "initial");
// We'll start off with the "download" image displayed in the splash
// window (when it opens), until we get stuff downloaded.
set_background_image(IT_download);
}
////////////////////////////////////////////////////////////////////
@ -234,50 +244,24 @@ set_p3d_url(const string &p3d_url) {
void P3DInstance::
set_p3d_filename(const string &p3d_filename) {
_fparams.set_p3d_filename(p3d_filename);
_got_fparams = true;
_panda_script_object->set_float_property("instanceDownloadProgress", 1.0);
// This also sets up some internal data based on the contents of the
// above file and the associated tokens.
// Extract the application desc file from the p3d file.
P3DMultifileReader reader;
stringstream sstream;
if (!reader.extract_one(p3d_filename, sstream, "p3d_info.xml")) {
nout << "No p3d_info.xml file found in " << p3d_filename << "\n";
} else {
sstream.seekg(0);
TiXmlDocument doc;
sstream >> doc;
// This also starts required packages downloading. When all
// packages have been installed, we will start the instance.
scan_app_desc_file(&doc);
}
// For the moment, all sessions will be unique. TODO: support
// multiple instances per session.
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
ostringstream strm;
strm << inst_mgr->get_unique_id();
_session_key = strm.str();
// Until we've done all of the above processing, we haven't fully
// committed to having fparams. (Setting this flag down here
// instead of up there avoids starting the instance in
// scan_app_desc_file(), before we've had a chance to finish
// processing this method.)
_got_fparams = true;
// Generate a special notification: onpluginload, indicating the
// plugin has read its parameters and is ready to be queried (even
// if Python has not yet started).
send_notify("onpluginload");
// Now that we're all set up, start the instance if we're fully
// downloaded.
if (get_packages_ready() && _got_wparams) {
ready_to_start();
if (!_mf_reader.open_read(_fparams.get_p3d_filename())) {
nout << "Couldn't read " << _fparams.get_p3d_filename() << "\n";
return;
}
if (check_p3d_signature()) {
mark_p3d_trusted();
} else {
mark_p3d_untrusted();
}
}
@ -356,7 +340,7 @@ set_wparams(const P3DWindowParams &wparams) {
_session->send_command(doc);
}
if (get_packages_ready() && _got_fparams) {
if (get_packages_ready() && _p3d_trusted) {
ready_to_start();
}
}
@ -904,29 +888,174 @@ make_xml() {
////////////////////////////////////////////////////////////////////
void P3DInstance::
splash_button_clicked() {
if (_session == NULL) {
if (!_p3d_trusted) {
auth_button_clicked();
} else if (_session == NULL) {
play_button_clicked();
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::auth_button_clicked
// Access: Public
// Description: Called to authorize the p3d file by the user clicking
// the red "auth" button.
////////////////////////////////////////////////////////////////////
void P3DInstance::
auth_button_clicked() {
// Here's where we need to invoke the authorization program.
cerr << "auth clicked\n";
// After the authorization program has returned, check the signature
// again.
if (check_p3d_signature()) {
// Set this flag to indicate that the user has clicked on the red
// "auth" button an successfully approved the application. This
// eliminates the need to click on the green "start" button.
_auth_button_approved = true;
mark_p3d_trusted();
} else {
mark_p3d_untrusted();
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::play_button_clicked
// Access: Public
// Description: Called to start the game by the user clicking the
// "play" button, or by JavaScript calling start().
// green "play" button, or by JavaScript calling
// start().
////////////////////////////////////////////////////////////////////
void P3DInstance::
play_button_clicked() {
if (_session == NULL) {
if (_splash_window != NULL) {
_splash_window->set_button_active(false);
}
set_button_image(IT_none);
set_background_image(IT_launch);
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
inst_mgr->start_instance(this);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::check_p3d_signature
// Access: Private
// Description: Checks the signature(s) encoded in the p3d file, and
// looks to see if any of them are recognized. Returns
// true if the signature is recognized and the file
// should be trusted, false otherwise.
////////////////////////////////////////////////////////////////////
bool P3DInstance::
check_p3d_signature() {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (inst_mgr->get_trusted_environment()) {
// If we're in a trusted environment (e.g. the panda3d command
// line, where we've already downloaded the p3d file separately),
// then everything is approved.
return true;
}
// Temporary hack: disabling further security checks until this code
// is complete.
return true;
nout << "check_p3d_signature\n";
int num_signatures = _mf_reader.get_num_signatures();
nout << "file has " << num_signatures << " signatures\n";
for (int i = 0; i < num_signatures; ++i) {
const P3DMultifileReader::CertChain &chain = _mf_reader.get_signature(i);
// Here's a certificate that has signed this multifile.
X509 *cert = chain[0]._cert;
// Look up the certificate to see if we've stored a copy in our
// certs dir.
if (inst_mgr->find_cert(cert)) {
return true;
}
}
// Couldn't find any approved certificates.
return false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::mark_p3d_untrusted
// Access: Private
// Description: This is called internally when it has been determined
// that the p3d file can't (yet) be trusted, for
// instance because it lacks a signature, or because it
// is signed by an unrecognized certificate. This puts
// up the red "auth" button and waits for the user to
// approve the app before continuing.
////////////////////////////////////////////////////////////////////
void P3DInstance::
mark_p3d_untrusted() {
// Failed test.
nout << "p3d untrusted\n";
set_background_image(IT_unauth);
set_button_image(IT_auth_ready);
make_splash_window();
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::mark_p3d_trusted
// Access: Private
// Description: This is called internally when it has been determined
// that the p3d file can be trusted and started. When
// this is called, the p3d file will be examined and
// made ready to start; it will not be started until
// this is called.
////////////////////////////////////////////////////////////////////
void P3DInstance::
mark_p3d_trusted() {
nout << "p3d trusted\n";
// Only call this once.
assert(!_p3d_trusted);
// Extract the application desc file from the p3d file.
stringstream sstream;
if (!_mf_reader.extract_one(sstream, "p3d_info.xml")) {
nout << "No p3d_info.xml file found in " << _fparams.get_p3d_filename() << "\n";
// TODO: fail.
} else {
sstream.seekg(0);
TiXmlDocument doc;
sstream >> doc;
// This also starts required packages downloading. When all
// packages have been installed, we will start the instance.
scan_app_desc_file(&doc);
}
// Now we've got no further need to keep the _mf_reader open.
_mf_reader.close();
// For the moment, all sessions will be unique. TODO: support
// multiple instances per session.
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
ostringstream strm;
strm << inst_mgr->get_unique_id();
_session_key = strm.str();
// Until we've done all of the above processing, we haven't fully
// committed to setting the trusted flag. (Setting this flag down
// here instead of up there avoids starting the instance in
// scan_app_desc_file(), before we've had a chance to finish
// processing this method.)
_p3d_trusted = true;
// Notify JS that we've accepted the trust of the p3d file.
send_notify("ontrust");
// Now that we're all set up, start the instance if we're fully
// downloaded.
if (get_packages_ready() && _got_wparams) {
ready_to_start();
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::scan_app_desc_file
// Access: Private
@ -970,10 +1099,15 @@ scan_app_desc_file(TiXmlDocument *doc) {
if (_fparams.lookup_token_int("auto_start") != 0) {
_auto_start = true;
}
if (_auth_button_approved) {
// But finally, if the user has already clicked through the red
// "auth" button, no need to present him/her with another green
// "play" button as well.
_auto_start = true;
}
nout << "_auto_start = " << _auto_start << "\n";
// But auto_start will be set false if the p3d file has not been
// signed by an approved signature. TODO.
if (_hidden && _got_wparams) {
_wparams.set_window_type(P3D_WT_hidden);
@ -1284,9 +1418,10 @@ make_splash_window() {
return;
}
if (_wparams.get_window_type() != P3D_WT_embedded &&
!_stuff_to_download && _auto_start) {
!_stuff_to_download && _auto_start && _p3d_trusted) {
// If it's a toplevel or fullscreen window, then we don't want a
// splash window until we have stuff to download.
// splash window unless we have stuff to download, or a button to
// display.
return;
}
@ -1296,10 +1431,6 @@ make_splash_window() {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
// Direct the "download" image to the background slot on the splash
// window for now, while we perform the download.
set_background_image(IT_download);
// Go get the required images.
bool any_standard_images = false;
for (int i = 0; i < (int)IT_none; ++i) {
@ -1344,6 +1475,10 @@ make_splash_window() {
_image_package->add_instance(this);
}
}
if (_current_button_image != IT_none) {
_splash_window->set_button_active(true);
}
}
////////////////////////////////////////////////////////////////////
@ -1357,6 +1492,7 @@ make_splash_window() {
void P3DInstance::
set_background_image(ImageType image_type) {
if (image_type != _current_background_image) {
nout << "setting background to " << _image_type_names[image_type] << "\n";
// Remove the previous image.
_image_files[_current_background_image]._image_placement = P3DSplashWindow::IP_none;
@ -1387,6 +1523,7 @@ set_background_image(ImageType image_type) {
void P3DInstance::
set_button_image(ImageType image_type) {
if (image_type != _current_button_image) {
nout << "setting button to " << _image_type_names[image_type] << "\n";
// Remove the previous image.
_image_files[_current_button_image]._image_placement = P3DSplashWindow::IP_none;
if (_current_button_image != IT_none) {
@ -1408,10 +1545,20 @@ set_button_image(ImageType image_type) {
_splash_window->set_image_filename(_image_files[_current_button_image]._filename, P3DSplashWindow::IP_button_ready);
_splash_window->set_image_filename(_image_files[_current_button_image + 1]._filename, P3DSplashWindow::IP_button_rollover);
_splash_window->set_image_filename(_image_files[_current_button_image + 2]._filename, P3DSplashWindow::IP_button_click);
_splash_window->set_button_active(true);
} else {
_splash_window->set_image_filename(string(), P3DSplashWindow::IP_button_ready);
_splash_window->set_image_filename(string(), P3DSplashWindow::IP_button_rollover);
_splash_window->set_image_filename(string(), P3DSplashWindow::IP_button_click);
_splash_window->set_button_active(false);
}
}
} else {
// We're not changing the button graphic, but we might be
// re-activating it.
if (_splash_window != NULL) {
if (_current_button_image != IT_none) {
_splash_window->set_button_active(true);
} else {
_splash_window->set_button_active(false);
}
}
}
@ -1532,7 +1679,7 @@ start_next_download() {
_splash_window->set_install_label("");
}
if (_got_wparams && _got_fparams) {
if (_got_wparams && _p3d_trusted) {
ready_to_start();
}
}
@ -1556,7 +1703,6 @@ ready_to_start() {
// We're fully downloaded, and waiting for the user to click play.
set_background_image(IT_ready);
set_button_image(IT_play_ready);
_splash_window->set_button_active(true);
}
}

View File

@ -22,6 +22,7 @@
#include "p3dReferenceCount.h"
#include "p3dSplashWindow.h"
#include "p3dTemporaryFile.h"
#include "p3dMultifileReader.h"
#include "get_tinyxml.h"
#ifdef __APPLE__
@ -101,6 +102,7 @@ public:
TiXmlElement *make_xml();
void splash_button_clicked();
void auth_button_clicked();
void play_button_clicked();
private:
@ -126,6 +128,7 @@ private:
// The different kinds of image files we download for the splash
// window.
enum ImageType {
// Also update _image_type_names when you update this list.
IT_download,
IT_ready,
IT_failed,
@ -133,10 +136,17 @@ private:
IT_play_ready,
IT_play_rollover,
IT_play_click,
IT_unauth,
IT_auth_ready,
IT_auth_rollover,
IT_auth_click,
IT_none, // Must be the last value
IT_num_image_types, // Not a real value
};
bool check_p3d_signature();
void mark_p3d_untrusted();
void mark_p3d_trusted();
void scan_app_desc_file(TiXmlDocument *doc);
void send_browser_script_object();
@ -192,16 +202,20 @@ private:
bool _got_fparams;
P3DFileParams _fparams;
P3DMultifileReader _mf_reader;
bool _got_wparams;
P3DWindowParams _wparams;
bool _p3d_trusted;
int _instance_id;
string _session_key;
string _log_basename;
bool _hidden;
bool _allow_python_dev;
bool _auto_start;
bool _auth_button_approved;
P3DSession *_session;

View File

@ -105,17 +105,21 @@ get_log_pathname() const {
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::get_keep_cwd
// Function: P3DInstanceManager::get_trusted_environment
// Access: Public
// Description: Returns the value of the keep_cwd flag passed to the
// constructor. This is true if the original working
// directory was valuable and meaningful, and should be
// preserved; or false if it is meaningless and should
// be changed.
// Description: Returns the value of the trusted_environment flag
// passed to the constructor. If this is true, it means
// the environment we are running in is trusted and the
// p3d file is already vetted. This means the current
// working directory will remain unchanged, and the p3d
// file will be run without checking its signature.
//
// This should generally be true only when run by
// panda3d.exe, and not when run by the web plugin.
////////////////////////////////////////////////////////////////////
inline bool P3DInstanceManager::
get_keep_cwd() const {
return _keep_cwd;
get_trusted_environment() const {
return _trusted_environment;
}
////////////////////////////////////////////////////////////////////
@ -165,3 +169,17 @@ new_bool_object(bool value) {
P3D_OBJECT_INCREF(obj);
return obj;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::encode_hexdigit
// Access: Public
// Description: Returns the hex digit corresponding to the
// indicated integer value.
////////////////////////////////////////////////////////////////////
inline char P3DInstanceManager::
encode_hexdigit(int c) {
if (c >= 10) {
return c - 10 + 'a';
}
return c + '0';
}

View File

@ -35,8 +35,11 @@
#else
#include <sys/stat.h>
#include <signal.h>
#include <dirent.h>
#endif
#include <stdio.h>
static ofstream logfile;
ostream *nout_stream = &logfile;
@ -52,7 +55,7 @@ P3DInstanceManager() {
_is_initialized = false;
_next_temp_filename_counter = 1;
_unique_id = 0;
_keep_cwd = false;
_trusted_environment = false;
_notify_thread_continue = false;
_started_notify_thread = false;
@ -157,8 +160,8 @@ bool P3DInstanceManager::
initialize(const string &contents_filename, const string &download_url,
bool verify_contents,
const string &platform, const string &log_directory,
const string &log_basename, bool keep_cwd) {
_keep_cwd = keep_cwd;
const string &log_basename, bool trusted_environment) {
_trusted_environment = trusted_environment;
_root_dir = find_root_dir();
_verify_contents = verify_contents;
_platform = platform;
@ -256,6 +259,12 @@ initialize(const string &contents_filename, const string &download_url,
return false;
}
// Make the certificate directory.
_certs_dir = _root_dir + "/certs";
if (!mkdir_complete(_certs_dir, nout)) {
nout << "Couldn't mkdir " << _certs_dir << "\n";
}
_is_initialized = true;
if (!_verify_contents &&
@ -645,6 +654,176 @@ release_temp_filename(const string &filename) {
unlink(filename.c_str());
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::find_cert
// Access: Public
// Description: Looks for the particular certificate in the cache of
// recognized certificates. Returns true if it is
// found, false if not.
////////////////////////////////////////////////////////////////////
bool P3DInstanceManager::
find_cert(X509 *cert) {
// First, we need the DER representation.
string der = cert_to_der(cert);
// If we've previously found this certificate, we don't have to hit
// disk again.
ApprovedCerts::iterator ci = _approved_certs.find(der);
if (ci != _approved_certs.end()) {
return true;
}
// Well, we haven't found it already. Look for it on disk. For
// this, we hash the cert into a hex string. This is similar to
// OpenSSL's get_by_subject() approach, except we hash the whole
// cert, not just the subject. (Since we also store self-signed
// certs in this list, we can't trust the subject name alone.)
static const size_t hash_size = 16;
unsigned char md[hash_size];
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, der.data(), der.size());
MD5_Final(md, &ctx);
string basename;
static const size_t keep_hash = 6;
for (size_t i = 0; i < keep_hash; ++i) {
int high = (md[i] >> 4) & 0xf;
int low = md[i] & 0xf;
basename += P3DInstanceManager::encode_hexdigit(high);
basename += P3DInstanceManager::encode_hexdigit(low);
}
string this_cert_dir = _certs_dir + "/" + basename;
nout << "looking in " << this_cert_dir << "\n";
vector<string> contents;
scan_directory(this_cert_dir, contents);
// Now look at each of the files in this directory and see if any of
// them matches the certificate.
vector<string>::iterator si;
for (si = contents.begin(); si != contents.end(); ++si) {
string filename = this_cert_dir + "/" + (*si);
X509 *x509 = NULL;
FILE *fp = fopen(filename.c_str(), "r");
if (fp != NULL) {
x509 = PEM_read_X509(fp, NULL, NULL, (void *)"");
fclose(fp);
}
if (x509 != NULL) {
string der2 = cert_to_der(x509);
// We might as well save this cert in the table for next time,
// even if it's not the one we're looking for right now.
_approved_certs.insert(der2);
if (der == der2) {
return true;
}
}
}
// Nothing matched.
return false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::cert_to_der
// Access: Public, Static
// Description: Converts the indicated certificate to its binary DER
// representation.
////////////////////////////////////////////////////////////////////
string P3DInstanceManager::
cert_to_der(X509 *cert) {
int buffer_size = i2d_X509(cert, NULL);
unsigned char *buffer = new unsigned char[buffer_size];
unsigned char *p = buffer;
i2d_X509(cert, &p);
string result((char *)buffer, buffer_size);
delete[] buffer;
return result;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::scan_directory
// Access: Public, Static
// Description: Attempts to open the named filename as if it were a
// directory and looks for the non-hidden files within
// the directory. Fills the given vector up with the
// sorted list of filenames that are local to this
// directory.
//
// It is the user's responsibility to ensure that the
// contents vector is empty before making this call;
// otherwise, the new files will be appended to it.
//
// Returns true on success, false if the directory could
// not be read for some reason.
////////////////////////////////////////////////////////////////////
bool P3DInstanceManager::
scan_directory(const string &dirname, vector<string> &contents) {
#ifdef _WIN32
// Use Windows' FindFirstFile() / FindNextFile() to walk through the
// list of files in a directory.
size_t orig_size = contents.size();
string match = dirname + "\\*.*";
WIN32_FIND_DATA find_data;
HANDLE handle = FindFirstFile(match.c_str(), &find_data);
if (handle == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_NO_MORE_FILES) {
// No matching files is not an error.
return true;
}
return false;
}
do {
string filename = find_data.cFileName;
if (filename != "." && filename != "..") {
contents.push_back(filename);
}
} while (FindNextFile(handle, &find_data));
bool scan_ok = (GetLastError() == ERROR_NO_MORE_FILES);
FindClose(handle);
sort(contents.begin() + orig_size, contents.end());
return scan_ok;
#else // _WIN32
// Use Posix's opendir() / readdir() to walk through the list of
// files in a directory.
size_t orig_size = contents.size();
DIR *root = opendir(dirname.c_str());
if (root == (DIR *)NULL) {
return false;
}
struct dirent *d;
d = readdir(root);
while (d != (struct dirent *)NULL) {
if (d->d_name[0] != '.') {
contents.push_back(d->d_name);
}
d = readdir(root);
}
closedir(root);
sort(contents.begin() + orig_size, contents.end());
return true;
#endif // _WIN32
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::get_global_ptr
// Access: Public, Static

View File

@ -26,6 +26,11 @@
#include <signal.h>
#endif
#define OPENSSL_NO_KRB5
#include "openssl/x509.h"
#include "openssl/pem.h"
#include "openssl/md5.h"
class P3DInstance;
class P3DSession;
class P3DHost;
@ -49,7 +54,7 @@ public:
const string &platform,
const string &log_directory,
const string &log_basename,
bool keep_cwd);
bool trusted_environment);
inline bool is_initialized() const;
inline bool get_verify_contents() const;
@ -59,7 +64,7 @@ public:
inline const string &get_platform() const;
inline const string &get_log_directory() const;
inline const string &get_log_pathname() const;
inline bool get_keep_cwd() const;
inline bool get_trusted_environment() const;
P3DInstance *
create_instance(P3D_request_ready_func *func,
@ -92,9 +97,14 @@ public:
string make_temp_filename(const string &extension);
void release_temp_filename(const string &filename);
bool find_cert(X509 *cert);
static string cert_to_der(X509 *cert);
static P3DInstanceManager *get_global_ptr();
static void delete_global_ptr();
static inline char encode_hexdigit(int c);
static bool scan_directory(const string &dirname, vector<string> &contents);
private:
// The notify thread. This thread runs only for the purpose of
// generating asynchronous notifications of requests, to callers who
@ -105,19 +115,23 @@ private:
private:
bool _is_initialized;
string _root_dir;
string _certs_dir;
bool _verify_contents;
string _platform;
string _log_directory;
string _log_basename;
string _log_pathname;
string _temp_directory;
bool _keep_cwd;
bool _trusted_environment;
P3D_object *_undefined_object;
P3D_object *_none_object;
P3D_object *_true_object;
P3D_object *_false_object;
typedef set<string> ApprovedCerts;
ApprovedCerts _approved_certs;
typedef set<P3DInstance *> Instances;
Instances _instances;

View File

@ -13,6 +13,16 @@
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::is_open
// Access: Public
// Description: Returns true if the reader is open, false otherwise.
////////////////////////////////////////////////////////////////////
inline bool P3DMultifileReader::
is_open() const {
return _is_open;
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::read_uint16
// Access: Private
@ -38,3 +48,49 @@ read_uint32() {
unsigned int d = _in.get();
return (d << 24) | (c << 16) | (b << 8) | a;
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::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 size_t P3DMultifileReader::Subfile::
get_last_byte_pos() const {
return max(_index_start + _index_length, _data_start + _data_length) - 1;
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::CertRecord::Constructor
// Access: Public
// Description: Ownership of the X509 object is passed into the
// CertRecord; it will be freed when the CertRecord
// destructs.
////////////////////////////////////////////////////////////////////
inline P3DMultifileReader::CertRecord::
CertRecord(X509 *cert) :
_cert(cert)
{
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::CertRecord::Copy Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
inline P3DMultifileReader::CertRecord::
CertRecord(const P3DMultifileReader::CertRecord &copy) :
_cert(X509_dup(copy._cert))
{
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::CertRecord::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
inline P3DMultifileReader::CertRecord::
~CertRecord() {
X509_free(_cert);
}

View File

@ -45,15 +45,46 @@ const int P3DMultifileReader::_current_minor_ver = 1;
////////////////////////////////////////////////////////////////////
P3DMultifileReader::
P3DMultifileReader() {
_is_open = false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::open_read
// Access: Public
// Description: Opens the indicated file for reading. Returns true
// on success, false on failure.
////////////////////////////////////////////////////////////////////
bool P3DMultifileReader::
open_read(const string &pathname) {
if (_is_open) {
close();
}
if (!read_header(pathname)) {
return false;
}
_is_open = true;
return true;
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::close
// Access: Public
// Description: Closes the previously-opened file.
////////////////////////////////////////////////////////////////////
void P3DMultifileReader::
close() {
_in.close();
_is_open = false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::extract_all
// Access: Public
// Description: Reads the named multifile, and extracts all the
// expected extractable components within it to the
// indicated directory. Returns true on success, false
// on failure.
// Description: Reads the multifile, and extracts all the expected
// extractable components within it to the indicated
// directory. Returns true on success, false on
// failure.
//
// The parameters package, start_progress, and
// progress_size are provided to make the appropriate
@ -61,9 +92,10 @@ P3DMultifileReader() {
// during this operation.
////////////////////////////////////////////////////////////////////
bool P3DMultifileReader::
extract_all(const string &pathname, const string &to_dir,
extract_all(const string &to_dir,
P3DPackage *package, double start_progress, double progress_size) {
if (!read_header(pathname)) {
assert(_is_open);
if (_in.fail()) {
return false;
}
@ -118,13 +150,14 @@ extract_all(const string &pathname, const string &to_dir,
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::extract_one
// Access: Public
// Description: Reads the named multifile, and extracts only the
// named component to the indicated stream. Returns
// true on success, false on failure.
// Description: Reads the multifile, and extracts only the named
// component to the indicated stream. Returns true on
// success, false on failure.
////////////////////////////////////////////////////////////////////
bool P3DMultifileReader::
extract_one(const string &pathname, ostream &out, const string &filename) {
if (!read_header(pathname)) {
extract_one(ostream &out, const string &filename) {
assert(_is_open);
if (_in.fail()) {
return false;
}
@ -141,6 +174,42 @@ extract_one(const string &pathname, ostream &out, const string &filename) {
return false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::get_num_signatures
// Access: Published
// Description: Returns the number of matching signatures found on
// the Multifile. These signatures may be iterated via
// get_signature() and related methods.
//
// A signature on this list is guaranteed to match the
// Multifile contents, proving that the Multifile has
// been unmodified since the signature was applied.
// However, this does not guarantee that the certificate
// itself is actually from who it says it is from; only
// that it matches the Multifile contents. See
// validate_signature_certificate() to authenticate a
// particular certificate.
////////////////////////////////////////////////////////////////////
int P3DMultifileReader::
get_num_signatures() const {
assert(_is_open);
((P3DMultifileReader *)this)->check_signatures();
return _signatures.size();
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::get_signature
// Access: Published
// Description: Returns the nth signature found on the Multifile.
// See the comments in get_num_signatures().
////////////////////////////////////////////////////////////////////
const P3DMultifileReader::CertChain &P3DMultifileReader::
get_signature(int n) const {
static CertChain error_chain;
assert(n >= 0 && n < (int)_signatures.size());
return _signatures[n];
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::read_header
// Access: Private
@ -150,7 +219,10 @@ extract_one(const string &pathname, ostream &out, const string &filename) {
////////////////////////////////////////////////////////////////////
bool P3DMultifileReader::
read_header(const string &pathname) {
assert(!_is_open);
_subfiles.clear();
_cert_special.clear();
_signatures.clear();
_in.open(pathname.c_str(), ios::in | ios::binary);
if (!_in) {
@ -228,16 +300,19 @@ read_header(const string &pathname) {
////////////////////////////////////////////////////////////////////
bool P3DMultifileReader::
read_index() {
_last_data_byte = 0;
unsigned int next_entry = read_uint32();
if (!_in) {
return false;
}
while (next_entry != 0) {
Subfile s;
s._start = read_uint32();
s._length = read_uint32();
s._index_start = (size_t)_in.tellg();
s._index_length = 0;
s._data_start = read_uint32();
s._data_length = read_uint32();
unsigned int flags = read_uint16();
if ((flags & 0x18) != 0) {
if ((flags & (SF_compressed | SF_encrypted)) != 0) {
// Skip over the uncompressed length.
read_uint32();
}
@ -255,9 +330,19 @@ read_index() {
s._filename = string(buffer, name_length);
delete[] buffer;
if (flags == 0) {
// We can only support subfiles with no particular flags set.
_subfiles.push_back(s);
s._index_length = (size_t)_in.tellg() - s._index_start;
if (flags & SF_signature) {
// A subfile with this bit set is a signature.
_cert_special.push_back(s);
} else {
// Otherwise, it's a regular file.
_last_data_byte = max(_last_data_byte, s.get_last_byte_pos());
if (flags == 0) {
// We can only support subfiles with no particular flags set.
_subfiles.push_back(s);
}
}
_in.seekg(next_entry);
@ -279,12 +364,12 @@ read_index() {
////////////////////////////////////////////////////////////////////
bool P3DMultifileReader::
extract_subfile(ostream &out, const Subfile &s) {
_in.seekg(s._start);
_in.seekg(s._data_start);
static const size_t buffer_size = 1024;
static const size_t buffer_size = 4096;
char buffer[buffer_size];
size_t remaining_data = s._length;
size_t remaining_data = s._data_length;
_in.read(buffer, min(buffer_size, remaining_data));
size_t count = _in.gcount();
while (count != 0) {
@ -302,3 +387,120 @@ extract_subfile(ostream &out, const Subfile &s) {
return true;
}
////////////////////////////////////////////////////////////////////
// Function: P3DMultifileReader::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 P3DMultifileReader::
check_signatures() {
Subfiles::iterator pi;
for (pi = _cert_special.begin(); pi != _cert_special.end(); ++pi) {
Subfile *subfile = &(*pi);
// Extract the signature data and certificate separately.
_in.seekg(subfile->_data_start);
size_t sig_size = read_uint32();
char *sig = new char[sig_size];
_in.read(sig, sig_size);
if (_in.gcount() != sig_size) {
nout << "read failure\n";
delete[] sig;
return;
}
size_t num_certs = read_uint32();
// Read the remaining buffer of certificate data.
size_t bytes_read = (size_t)_in.tellg() - subfile->_data_start;
size_t buffer_size = subfile->_data_length - bytes_read;
char *buffer = new char[buffer_size];
_in.read(buffer, buffer_size);
if (_in.gcount() != buffer_size) {
nout << "read failure\n";
delete[] sig;
delete[] buffer;
return;
}
// Now convert each of the certificates to an X509 object, and
// store it in our CertChain.
CertChain chain;
EVP_PKEY *pkey = NULL;
if (buffer_size > 0) {
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
// Beginning in 0.9.8, d2i_X509() accepted a const unsigned char **.
const unsigned char *bp, *bp_end;
#else
// Prior to 0.9.8, d2i_X509() accepted an unsigned char **.
unsigned char *bp, *bp_end;
#endif
bp = (unsigned char *)&buffer[0];
bp_end = bp + buffer_size;
X509 *x509 = d2i_X509(NULL, &bp, bp_end - bp);
while (num_certs > 0 && x509 != NULL) {
chain.push_back(CertRecord(x509));
--num_certs;
x509 = d2i_X509(NULL, &bp, bp_end - bp);
}
if (num_certs != 0 || x509 != NULL) {
nout << "Extra data in signature record.\n";
}
}
delete[] buffer;
if (!chain.empty()) {
pkey = X509_get_pubkey(chain[0]._cert);
}
if (pkey != NULL) {
EVP_MD_CTX *md_ctx;
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
md_ctx = EVP_MD_CTX_create();
#else
md_ctx = new EVP_MD_CTX;
#endif
EVP_VerifyInit(md_ctx, EVP_sha1());
// Read and hash the multifile contents, but only up till
// _last_data_byte.
_in.seekg(0);
streampos bytes_remaining = (streampos)_last_data_byte;
static const size_t buffer_size = 4096;
char buffer[buffer_size];
_in.read(buffer, min((streampos)buffer_size, bytes_remaining));
size_t count = _in.gcount();
while (count != 0) {
assert(count <= buffer_size);
EVP_VerifyUpdate(md_ctx, buffer, count);
bytes_remaining -= count;
_in.read(buffer, min((streampos)buffer_size, bytes_remaining));
count = _in.gcount();
}
assert(bytes_remaining == (streampos)0);
// Now check that the signature matches the hash.
int verify_result =
EVP_VerifyFinal(md_ctx, (unsigned char *)sig,
sig_size, pkey);
if (verify_result == 1) {
// The signature matches; save the certificate and its chain.
_signatures.push_back(chain);
}
}
delete[] sig;
}
_cert_special.clear();
}

View File

@ -16,6 +16,7 @@
#define P3DMULTIFILEREADER_H
#include "p3d_plugin_common.h"
#include "p3dInstanceManager.h" // for openssl
class P3DPackage;
@ -30,13 +31,26 @@ class P3DPackage;
class P3DMultifileReader {
public:
P3DMultifileReader();
bool open_read(const string &pathname);
inline bool is_open() const;
void close();
bool extract_all(const string &pathname, const string &to_dir,
bool extract_all(const string &to_dir,
P3DPackage *package, double start_progress,
double progress_size);
bool extract_one(const string &pathname, ostream &out,
const string &filename);
bool extract_one(ostream &out, const string &filename);
class CertRecord {
public:
inline CertRecord(X509 *cert);
inline CertRecord(const CertRecord &copy);
inline ~CertRecord();
X509 *_cert;
};
typedef vector<CertRecord> CertChain;
int get_num_signatures() const;
const CertChain &get_signature(int n) const;
private:
class Subfile;
@ -45,21 +59,39 @@ private:
bool read_index();
bool extract_subfile(ostream &out, const Subfile &s);
void check_signatures();
inline unsigned int read_uint16();
inline unsigned int read_uint32();
ifstream _in;
enum SubfileFlags {
SF_compressed = 0x0008,
SF_encrypted = 0x0010,
SF_signature = 0x0020,
};
class Subfile {
public:
inline size_t get_last_byte_pos() const;
string _filename;
size_t _start;
size_t _length;
size_t _index_start;
size_t _index_length;
size_t _data_start;
size_t _data_length;
size_t _timestamp;
};
ifstream _in;
bool _is_open;
typedef vector<Subfile> Subfiles;
Subfiles _subfiles;
Subfiles _cert_special;
size_t _last_data_byte;
typedef vector<CertChain> Certificates;
Certificates _signatures;
static const char _header[];
static const size_t _header_size;

View File

@ -702,8 +702,15 @@ void P3DPackage::
extract_archive() {
string source_pathname = _package_dir + "/" + _uncompressed_archive.get_filename();
P3DMultifileReader reader;
if (!reader.extract_all(source_pathname, _package_dir,
this, download_portion + uncompress_portion, extract_portion)) {
if (!reader.open_read(source_pathname)) {
nout << "Couldn't read " << _uncompressed_archive.get_filename() << "\n";
report_done(false);
return;
}
if (!reader.extract_all(_package_dir, this,
download_portion + uncompress_portion,
extract_portion)) {
nout << "Failure extracting " << _uncompressed_archive.get_filename()
<< "\n";
report_done(false);

View File

@ -661,7 +661,7 @@ start_p3dpython(P3DInstance *inst) {
// Change the current directory to the standard start directory, but
// only if the runtime environment told us the original current
// directory isn't meaningful.
_use_start_dir = !inst_mgr->get_keep_cwd();
_use_start_dir = !inst_mgr->get_trusted_environment();
if (_use_start_dir) {
mkdir_complete(_start_dir, nout);
}

View File

@ -410,6 +410,7 @@ set_mouse_data(int mouse_x, int mouse_y, bool mouse_down) {
_mouse_down = mouse_down;
ButtonState bstate = BS_hidden;
bool click_detected = false;
if (!_button_active) {
// The button isn't active, so it's hidden, regardless of the
@ -439,7 +440,7 @@ set_mouse_data(int mouse_x, int mouse_y, bool mouse_down) {
// after a successful click.
bstate = BS_hidden;
_button_active = false;
button_click_detected();
click_detected = true;
} else {
bstate = BS_rollover;
}
@ -455,6 +456,13 @@ set_mouse_data(int mouse_x, int mouse_y, bool mouse_down) {
}
set_bstate(bstate);
// If we detected a click operation in the above, make the callback
// here, at the end of the method, after we have finished updating
// the button state.
if (click_detected) {
button_click_detected();
}
}
////////////////////////////////////////////////////////////////////

View File

@ -38,7 +38,7 @@ P3D_initialize(int api_version, const char *contents_filename,
const char *download_url, bool verify_contents,
const char *platform,
const char *log_directory, const char *log_basename,
bool keep_cwd) {
bool trusted_environment) {
if (api_version != P3D_API_VERSION) {
// Can't accept an incompatible version.
return false;
@ -74,7 +74,7 @@ P3D_initialize(int api_version, const char *contents_filename,
bool result = inst_mgr->initialize(contents_filename, download_url,
verify_contents, platform,
log_directory, log_basename,
keep_cwd);
trusted_environment);
RELEASE_LOCK(_api_lock);
return result;
}

View File

@ -122,11 +122,11 @@ extern "C" {
core API. Note that the individual instances also have their own
log_basename values.
Finally, keep_cwd should be set true if the current working
directory is meaningful and valuable to the user (for instance,
when this is launched via a command-line tool), or false if it
means nothing and can safely be reset. Normally, a browser plugin
should set this false.
Finally, trusted_environment should be set true to indicate that
the environment and p3d file are already trusted. If this is set,
the current working directory will remain unchanged, and the p3d
file will be run without checking its signature. Normally, a
browser plugin should set this false.
This function returns true if the core API is valid and uses a
compatible API, false otherwise. If it returns false, the host
@ -137,7 +137,7 @@ P3D_initialize_func(int api_version, const char *contents_filename,
const char *download_url, bool verify_contents,
const char *platform,
const char *log_directory, const char *log_basename,
bool keep_cwd);
bool trusted_environment);
/* This function should be called to unload the core API. It will
release all internally-allocated memory and return the core API to