Browse Source

more security work

David Rose 16 years ago
parent
commit
b52dfe14dd

+ 8 - 1
direct/src/p3d/AppRunner.py

@@ -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

+ 2 - 1
direct/src/p3d/panda3d.pdef

@@ -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)

+ 2 - 2
direct/src/plugin/load_plugin.cxx

@@ -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();

+ 1 - 1
direct/src/plugin/load_plugin.h

@@ -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();
 

+ 0 - 14
direct/src/plugin/p3dHost.I

@@ -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';
-}

+ 2 - 2
direct/src/plugin/p3dHost.cxx

@@ -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);
   }
 }
 

+ 0 - 1
direct/src/plugin/p3dHost.h

@@ -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;

+ 201 - 55
direct/src/plugin/p3dInstance.cxx

@@ -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);
   }
 }
 

+ 14 - 0
direct/src/plugin/p3dInstance.h

@@ -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;
 

+ 26 - 8
direct/src/plugin/p3dInstanceManager.I

@@ -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';
+}

+ 182 - 3
direct/src/plugin/p3dInstanceManager.cxx

@@ -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

+ 17 - 3
direct/src/plugin/p3dInstanceManager.h

@@ -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;
 

+ 56 - 0
direct/src/plugin/p3dMultifileReader.I

@@ -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);
+}

+ 222 - 20
direct/src/plugin/p3dMultifileReader.cxx

@@ -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();
+}

+ 38 - 6
direct/src/plugin/p3dMultifileReader.h

@@ -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;

+ 9 - 2
direct/src/plugin/p3dPackage.cxx

@@ -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);

+ 1 - 1
direct/src/plugin/p3dSession.cxx

@@ -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);
   }

+ 9 - 1
direct/src/plugin/p3dSplashWindow.cxx

@@ -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();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////

+ 2 - 2
direct/src/plugin/p3d_plugin.cxx

@@ -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;
 }

+ 6 - 6
direct/src/plugin/p3d_plugin.h

@@ -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