Browse Source

support encryption/decryption streams

David Rose 21 years ago
parent
commit
e51ecacdee

+ 20 - 0
panda/src/downloadertools/Sources.pp

@@ -88,3 +88,23 @@
     pdecompress.cxx
 
 #end bin_target
+
+#begin bin_target
+  #define TARGET pencrypt
+  #define BUILD_TARGET $[HAVE_SSL]
+  #define USE_PACKAGES $[USE_PACKAGES] ssl
+
+  #define SOURCES \
+    pencrypt.cxx
+
+#end bin_target
+
+#begin bin_target
+  #define TARGET pdecrypt
+  #define BUILD_TARGET $[HAVE_SSL]
+  #define USE_PACKAGES $[USE_PACKAGES] ssl
+
+  #define SOURCES \
+    pdecrypt.cxx
+
+#end bin_target

+ 94 - 22
panda/src/downloadertools/multify.cxx

@@ -28,21 +28,24 @@
 #include <stdio.h>
 
 
-bool create = false;      // -c
-bool append = false;      // -r
-bool update = false;      // -u
-bool list = false;        // -t
-bool extract = false;     // -x
-bool verbose = false;     // -v
-bool compress = false;    // -z
+bool create = false;           // -c
+bool append = false;           // -r
+bool update = false;           // -u
+bool list = false;             // -t
+bool extract = false;          // -x
+bool verbose = false;          // -v
+bool compress = false;         // -z
 int default_compression_level = 6;
-Filename multifile_name;  // -f
+Filename multifile_name;       // -f
 bool got_multifile_name = false;
-bool to_stdout = false;   // -O
-Filename chdir_to;        // -C
+bool to_stdout = false;        // -O
+bool encryption_flag = false;  // -e
+string password;               // -p
+bool got_password = false;
+Filename chdir_to;             // -C
 bool got_chdir_to = false;
-size_t scale_factor = 0;  // -F
-pset<string> dont_compress; // -Z
+size_t scale_factor = 0;       // -F
+pset<string> dont_compress;    // -Z
 
 // Default extensions not to compress.  May be overridden with -Z.
 string dont_compress_str = "jpg,mp3";
@@ -120,6 +123,21 @@ help() {
     "      decompressed automatically.  Also see -Z, which restricts which\n"
     "      subfiles will be compressed based on the filename extension.\n\n"
 
+    "  -e\n"
+    "      Encrypt subfiles as they are written to the Multifile using the password\n"
+    "      specified with -p, below.  Subfiles are encrypted individually, rather\n"
+    "      than encrypting the entire multifile, and different subfiles may be\n"
+    "      encrypted using different passwords (although this requires running\n"
+    "      multify multiple times).  It is not possible to encrypt the multifile's\n"
+    "      table of contents using this interface, but see the pencrypt program to\n"
+    "      encrypt the entire multifile after it has been generated).\n\n"
+
+
+    "  -p \"password\"\n"
+    "      Specifies the password to encrypt or decrypt subfiles.  If this is not\n"
+    "      specified, and passwords are required, the user will be prompted from\n"
+    "      standard input.\n\n"
+
     "  -F <scale_factor>\n"
     "      Specify a Multifile scale factor.  This is only necessary to support\n"
     "      Multifiles that will exceed 4GB in size.  The default scale factor is\n"
@@ -146,6 +164,18 @@ help() {
     "      default is -" << default_compression_level << ".\n\n";
 }
 
+const string &
+get_password() {
+  if (!got_password) {
+    cerr << "Enter password: ";
+    getline(cin, password);
+    got_password = true;
+  }
+
+  return password;
+}
+
+
 bool
 is_named(const string &subfile_name, int argc, char *argv[]) {
   // Returns true if the indicated subfile appears on the list of
@@ -243,6 +273,11 @@ add_files(int argc, char *argv[]) {
     }
   }
 
+  if (encryption_flag) {
+    multifile.set_encryption_flag(true);
+    multifile.set_encryption_password(get_password());
+  }
+
   if (scale_factor != 0 && scale_factor != multifile.get_scale_factor()) {
     cerr << "Setting scale factor to " << scale_factor << "\n";
     multifile.set_scale_factor(scale_factor);
@@ -309,7 +344,26 @@ extract_files(int argc, char *argv[]) {
 
   int num_subfiles = multifile.get_num_subfiles();
 
-  for (int i = 0; i < num_subfiles; i++) {
+  // First, check to see whether any of the named subfiles have been
+  // encrypted.  If any have, we may need to prompt the user to enter
+  // a password before we can extract them.
+  int i;
+  bool any_encrypted = false;
+  for (i = 0; i < num_subfiles && !any_encrypted; i++) {
+    string subfile_name = multifile.get_subfile_name(i);
+    if (is_named(subfile_name, argc, argv)) {
+      if (multifile.is_subfile_encrypted(i)) {
+        any_encrypted = true;
+      }
+    }
+  }
+
+  if (any_encrypted) {
+    multifile.set_encryption_password(get_password());
+  }
+
+  // Now walk back through the list and this time do the extraction.
+  for (i = 0; i < num_subfiles; i++) {
     string subfile_name = multifile.get_subfile_name(i);
     if (is_named(subfile_name, argc, argv)) {
       Filename filename = subfile_name;
@@ -352,21 +406,32 @@ list_files(int argc, char *argv[]) {
     for (int i = 0; i < num_subfiles; i++) {
       string subfile_name = multifile.get_subfile_name(i);
       if (is_named(subfile_name, argc, argv)) {
+        char encrypted_symbol = ' ';
+        if (multifile.is_subfile_encrypted(i)) {
+          encrypted_symbol = 'e';
+        }
         if (multifile.is_subfile_compressed(i)) {
           size_t orig_length = multifile.get_subfile_length(i);
-          size_t compressed_length = multifile.get_subfile_compressed_length(i);
+          size_t internal_length = multifile.get_subfile_internal_length(i);
           double ratio = 1.0;
           if (orig_length != 0) {
-            ratio = (double)compressed_length / (double)orig_length;
+            ratio = (double)internal_length / (double)orig_length;
+          }
+          if (ratio > 1.0) {
+            printf("%12d worse %c %s\n",
+                   multifile.get_subfile_length(i),
+                   encrypted_symbol,
+                   subfile_name.c_str());
+          } else {
+            printf("%12d  %3.0f%% %c %s\n",
+                   multifile.get_subfile_length(i),
+                   100.0 - ratio * 100.0, encrypted_symbol,
+                   subfile_name.c_str());
           }
-          printf("%12d %3.0f%%  %s\n",
-                 multifile.get_subfile_length(i),
-                 100.0 - ratio * 100.0,
-                 subfile_name.c_str());
         } else {
-          printf("%12d       %s\n", 
+          printf("%12d       %c %s\n", 
                  multifile.get_subfile_length(i),
-                 subfile_name.c_str());
+                 encrypted_symbol, subfile_name.c_str());
         }
       }
     }
@@ -424,7 +489,7 @@ main(int argc, char *argv[]) {
 
   extern char *optarg;
   extern int optind;
-  static const char *optflags = "crutxvz123456789Z:f:OC:F:h";
+  static const char *optflags = "crutxvz123456789Z:f:OC:ep:F:h";
   int flag = getopt(argc, argv, optflags);
   Filename rel_path;
   while (flag != EOF) {
@@ -500,6 +565,13 @@ main(int argc, char *argv[]) {
     case 'O':
       to_stdout = true;
       break;
+    case 'e':
+      encryption_flag = true;
+      break;
+    case 'p':
+      password = optarg;
+      got_password = true;
+      break;
     case 'F':
       {
         char *endptr;

+ 10 - 12
panda/src/downloadertools/pcompress.cxx

@@ -46,16 +46,6 @@ main(int argc, char *argv[]) {
     return 1;
   }
 
-  // Determine source file length
-  read_stream.seekg(0, ios::end);
-  int source_file_length = read_stream.tellg();
-  read_stream.seekg(0, ios::beg);
-
-  if (source_file_length == 0) {
-    cerr << "zero length file: " << source_file << endl;
-    return 1;
-  }
-
   // Open destination file
   ofstream write_stream;
   dest_file.set_binary();
@@ -64,6 +54,7 @@ main(int argc, char *argv[]) {
     return 1;
   }
 
+  bool fail = false;
   {
     OCompressStream compress(&write_stream, false);
     
@@ -72,13 +63,20 @@ main(int argc, char *argv[]) {
       compress.put(ch);
       ch = read_stream.get();
     }
+
+    fail = compress.fail() && !compress.eof();
   }
 
   read_stream.close();
   write_stream.close();
 
-  if (implicit_dest_file) {
-    source_file.unlink();
+  if (fail) {
+    dest_file.unlink();
+
+  } else {
+    if (implicit_dest_file) {
+      source_file.unlink();
+    }
   }
 
   return 0;

+ 151 - 0
panda/src/downloadertools/pdecrypt.cxx

@@ -0,0 +1,151 @@
+// Filename: pdecrypt.cxx
+// Created by:  drose (01Sep04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "filename.h"
+#include "encryptStream.h"
+#include "notify.h"
+
+#ifndef HAVE_GETOPT
+  #include "gnu_getopt.h"
+#else
+  #ifdef HAVE_GETOPT_H
+    #include <getopt.h>
+  #endif
+#endif
+
+void 
+usage() {
+  cerr
+    << "\n"
+    << "Usage: pdecrypt [opts] <file> [<dest_file>]\n\n"
+    
+    << "This program reverses the operation of a previous pencrypt command.  It\n"
+    << "decrypts the contents of the source file by applying the indicated password.\n"
+    << "The encryption algorithm need not be specified; it can be determined by\n"
+    << "examining the header of the encrypted file.  The password must match exactly.\n"
+    << "If it does not, an error may or may not be reported; but the file will not be\n"
+    << "decrypted correctly even if no error is reported.\n\n"
+
+    << "Options:\n\n"
+    
+    << "  -p \"password\"\n"
+    << "      Specifies the password to use for descryption.  If this is not specified,\n"
+    << "      the user is prompted from standard input.\n\n";
+}
+
+int
+main(int argc, char *argv[]) {
+  extern char *optarg;
+  extern int optind;
+  const char *optstr = "p:h";
+
+  string password;
+  bool got_password = false;
+
+  int flag = getopt(argc, argv, optstr);
+
+  while (flag != EOF) {
+    switch (flag) {
+    case 'p':
+      password = optarg;
+      got_password = true;
+      break;
+
+    case 'h':
+    case '?':
+    default:
+      usage();
+      return 1;
+    }
+    flag = getopt(argc, argv, optstr);
+  }
+
+  argc -= (optind-1);
+  argv += (optind-1);
+
+  if (argc < 2) {
+    usage();
+    return 1;
+  }
+
+  bool implicit_dest_file;
+  Filename source_file = Filename::from_os_specific(argv[1]);
+  Filename dest_file;
+  if (argc < 3) {
+    if (source_file.get_extension() == "pe") {
+      dest_file = source_file;
+      dest_file.set_extension("");
+    } else {
+      cerr << "Input filename doesn't end in .pe; can't derive filename of output file.\n";
+      return 1;
+    }
+    implicit_dest_file = true;
+  } else {
+    dest_file = Filename::from_os_specific(argv[2]);
+    implicit_dest_file = false;
+  }
+
+  // Open source file
+  ifstream read_stream;
+  source_file.set_binary();
+  if (!source_file.open_read(read_stream)) {
+    cerr << "failed to open: " << source_file << endl;
+    return 1;
+  }
+
+  // Open destination file
+  ofstream write_stream;
+  dest_file.set_binary();
+  if (!dest_file.open_write(write_stream, true)) {
+    cerr << "failed to open: " << dest_file << endl;
+    return 1;
+  }
+
+  // Prompt for password.
+  if (!got_password) {
+    cerr << "Enter password: ";
+    getline(cin, password);
+  }
+
+  bool fail = false;
+  {
+    IDecryptStream decrypt(&read_stream, false, password);
+    
+    int ch = decrypt.get();
+    while (!decrypt.eof() && !decrypt.fail()) {
+      write_stream.put(ch);
+      ch = decrypt.get();
+    }
+
+    fail = decrypt.fail() && !decrypt.eof();
+  }
+
+  read_stream.close();
+  write_stream.close();
+
+  if (fail) {
+    dest_file.unlink();
+
+  } else {
+    if (implicit_dest_file) {
+      source_file.unlink();
+    }
+  }
+
+  return 0;
+}

+ 156 - 0
panda/src/downloadertools/pencrypt.cxx

@@ -0,0 +1,156 @@
+// Filename: pencrypt.cxx
+// Created by:  drose (01Sep04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "filename.h"
+#include "encryptStream.h"
+#include "notify.h"
+
+#ifndef HAVE_GETOPT
+  #include "gnu_getopt.h"
+#else
+  #ifdef HAVE_GETOPT_H
+    #include <getopt.h>
+  #endif
+#endif
+
+void 
+usage() {
+  cerr
+    << "\n"
+    << "Usage: pencrypt [opts] <file> [<dest_file>]\n\n"
+    
+    << "This program will apply an encryption algorithm to a file, creating an\n"
+    << "encrypted version of the file which can only be recovered using pdecrypt and\n"
+    << "the same password that was supplied to pencrypt.  If the dest_file name is\n"
+    << "not specified, a default output name is generated by appending .pe to the\n"
+    << "input file name.\n\n"
+
+    << "Options:\n\n"
+
+    << "  -a algorithm\n"
+    << "      Specifies the particular encryption algorithm to use.  Available\n"
+    << "      algorithm names are defined by OpenSSL.  If this is unspecified a\n"
+    << "      default algorithm is selected.\n\n"
+    
+    << "  -p \"password\"\n"
+    << "      Specifies the password to use for encryption.  There are no\n"
+    << "      restrictions on the password length or contents, but longer passwords\n"
+    << "      are more secure.  If this is not specified, the user is prompted from\n"
+    << "      standard input.\n\n";
+}
+
+int
+main(int argc, char *argv[]) {
+  extern char *optarg;
+  extern int optind;
+  const char *optstr = "a:p:h";
+
+  string algorithm;
+  string password;
+  bool got_password = false;
+
+  int flag = getopt(argc, argv, optstr);
+
+  while (flag != EOF) {
+    switch (flag) {
+    case 'a':
+      algorithm = optarg;
+      break;
+
+    case 'p':
+      password = optarg;
+      got_password = true;
+      break;
+
+    case 'h':
+    case '?':
+    default:
+      usage();
+      return 1;
+    }
+    flag = getopt(argc, argv, optstr);
+  }
+
+  argc -= (optind-1);
+  argv += (optind-1);
+
+  if (argc < 2) {
+    usage();
+    return 1;
+  }
+
+  bool implicit_dest_file;
+  Filename source_file = Filename::from_os_specific(argv[1]);
+  Filename dest_file;
+  if (argc < 3) {
+    dest_file = source_file.get_fullpath() + ".pe";
+    implicit_dest_file = true;
+  } else {
+    dest_file = Filename::from_os_specific(argv[2]);
+    implicit_dest_file = false;
+  }
+
+  // Open source file
+  ifstream read_stream;
+  source_file.set_binary();
+  if (!source_file.open_read(read_stream)) {
+    cerr << "failed to open: " << source_file << endl;
+    return 1;
+  }
+
+  // Open destination file
+  ofstream write_stream;
+  dest_file.set_binary();
+  if (!dest_file.open_write(write_stream, true)) {
+    cerr << "failed to open: " << dest_file << endl;
+    return 1;
+  }
+
+  // Prompt for password.
+  if (!got_password) {
+    cerr << "Enter password: ";
+    getline(cin, password);
+  }
+    
+  bool fail = false;
+  {
+    OEncryptStream encrypt(&write_stream, false, password, algorithm);
+    
+    int ch = read_stream.get();
+    while (!read_stream.eof() && !read_stream.fail()) {
+      encrypt.put(ch);
+      ch = read_stream.get();
+    }
+
+    fail = encrypt.fail() && !encrypt.eof();
+  }
+
+  read_stream.close();
+  write_stream.close();
+
+  if (fail) {
+    dest_file.unlink();
+
+  } else {
+    if (implicit_dest_file) {
+      source_file.unlink();
+    }
+  }
+
+  return 0;
+}

+ 8 - 1
panda/src/express/Sources.pp

@@ -23,6 +23,7 @@
     datagramGenerator.h \
     datagramIterator.I datagramIterator.h datagramSink.I datagramSink.h \
     dcast.T dcast.h \
+    encryptStreamBuf.h encryptStream.h encryptStream.I \
     error_utils.h \
     get_config_path.h \
     hashGeneratorBase.I hashGeneratorBase.h \
@@ -40,6 +41,7 @@
     namable.h nativeNumericData.I nativeNumericData.h \
     numeric_types.h \
     ordered_vector.h ordered_vector.I ordered_vector.T \
+    password_hash.h \
     patchfile.I patchfile.h \
     pointerTo.I pointerTo.h \
     pointerToArray.I pointerToArray.h \
@@ -79,7 +81,9 @@
     conditionVar.cxx conditionVarDummyImpl.cxx conditionVarNsprImpl.cxx \
     config_express.cxx datagram.cxx datagramGenerator.cxx \
     datagramIterator.cxx \
-    datagramSink.cxx dcast.cxx error_utils.cxx \
+    datagramSink.cxx dcast.cxx \
+    encryptStreamBuf.cxx encryptStream.cxx \
+    error_utils.cxx \
     get_config_path.cxx \
     hashGeneratorBase.cxx hashVal.cxx indent.cxx \
     memoryInfo.cxx memoryUsage.cxx memoryUsagePointerCounts.cxx \
@@ -88,6 +92,7 @@
     namable.cxx \
     nativeNumericData.cxx \
     ordered_vector.cxx \
+    password_hash.cxx \
     patchfile.cxx \
     profileTimer.cxx \
     pta_uchar.cxx \
@@ -125,6 +130,7 @@
     datagramGenerator.I datagramGenerator.h \
     datagramIterator.I datagramIterator.h \
     datagramSink.I datagramSink.h dcast.T dcast.h \
+    encryptStreamBuf.h encryptStream.h encryptStream.I \
     error_utils.h get_config_path.h \
     hashGeneratorBase.I \
     hashGeneratorBase.h hashVal.I hashVal.h \
@@ -139,6 +145,7 @@
     namable.I namable.h \
     nativeNumericData.I nativeNumericData.h numeric_types.h \
     ordered_vector.h ordered_vector.I ordered_vector.T \
+    password_hash.h \
     patchfile.I patchfile.h pointerTo.I pointerTo.h \
     pointerToArray.I pointerToArray.h profileTimer.I \
     profileTimer.h pta_uchar.h \

+ 111 - 0
panda/src/express/encryptStream.I

@@ -0,0 +1,111 @@
+// Filename: encryptStream.I
+// Created by:  drose (01Sep04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: IDecryptStream::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE IDecryptStream::
+IDecryptStream() : istream(&_buf) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: IDecryptStream::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE IDecryptStream::
+IDecryptStream(istream *source, bool owns_source,
+               const string &password) : istream(&_buf) {
+  open(source, owns_source, password);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: IDecryptStream::open
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE IDecryptStream &IDecryptStream::
+open(istream *source, bool owns_source, const string &password) {
+  clear((ios_iostate)0);
+  _buf.open_read(source, owns_source, password);
+  return *this;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: IDecryptStream::close
+//       Access: Public
+//  Description: Resets the EncryptStream to empty, but does not actually
+//               close the source istream unless owns_source was true.
+////////////////////////////////////////////////////////////////////
+INLINE IDecryptStream &IDecryptStream::
+close() {
+  _buf.close_read();
+  return *this;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: OEncryptStream::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE OEncryptStream::
+OEncryptStream() : ostream(&_buf) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: OEncryptStream::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE OEncryptStream::
+OEncryptStream(ostream *dest, bool owns_dest, const string &password,
+               const string &encryption_algorithm) :
+  ostream(&_buf) 
+{
+  open(dest, owns_dest, password, encryption_algorithm);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: OEncryptStream::open
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE OEncryptStream &OEncryptStream::
+open(ostream *dest, bool owns_dest, const string &password,
+     const string &encryption_algorithm) {
+  clear((ios_iostate)0);
+  _buf.open_write(dest, owns_dest, password, encryption_algorithm);
+  return *this;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: OEncryptStream::close
+//       Access: Public
+//  Description: Resets the EncryptStream to empty, but does not actually
+//               close the dest ostream unless owns_dest was true.
+////////////////////////////////////////////////////////////////////
+INLINE OEncryptStream &OEncryptStream::
+close() {
+  _buf.close_write();
+  return *this;
+}
+

+ 68 - 0
panda/src/express/encryptStream.cxx

@@ -0,0 +1,68 @@
+// Filename: encryptStream.cxx
+// Created by:  drose (01Sep04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "encryptStream.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: encrypt_string
+//       Access: Published
+//  Description: Encrypts the indicated source string using the given
+//               password and algorithm (or empty string for the
+//               default algorithm).  Returns the encrypted string.
+////////////////////////////////////////////////////////////////////
+string
+encrypt_string(const string &source, const string &password,
+               const string &algorithm) {
+  ostringstream output;
+
+  {
+    OEncryptStream encrypt(&output, false, password, algorithm);
+    encrypt.write(source.data(), source.length());
+  }
+
+  return output.str();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: decrypt_string
+//       Access: Published
+//  Description: Decrypts the previously-encrypted string using the
+//               given password (which must be the same password
+//               passed to encrypt()).  The return value is the
+//               decrypted string.  Note that a decryption error,
+//               including an incorrect password, cannot easily be
+//               detected, and the return value may simply be a
+//               garbage string.
+////////////////////////////////////////////////////////////////////
+string
+decrypt_string(const string &source, const string &password) {
+  istringstream input(source);
+  ostringstream output;
+
+  {
+    IDecryptStream decrypt(&input, false, password);
+    
+    int ch = decrypt.get();
+    while (!decrypt.eof() && !decrypt.fail()) {
+      output.put(ch);
+      ch = decrypt.get();
+    }
+  }
+
+  return output.str();
+}

+ 95 - 0
panda/src/express/encryptStream.h

@@ -0,0 +1,95 @@
+// Filename: encryptStream.h
+// Created by:  drose (01Sep04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef ENCRYPTSTREAM_H
+#define ENCRYPTSTREAM_H
+
+#include "pandabase.h"
+
+// This module is not compiled if OpenSSL is not available.
+#ifdef HAVE_SSL
+
+#include "encryptStreamBuf.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : IDecryptStream
+// Description : An input stream object that uses OpenSSL to decrypt
+//               the input from another source stream on-the-fly.
+//
+//               Attach an IDecryptStream to an existing istream that
+//               provides encrypted data, as generated by an
+//               OEncryptStream, and read the corresponding
+//               unencrypted data from the IDecryptStream.
+//
+//               Seeking is not supported.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS IDecryptStream : public istream {
+public:
+  INLINE IDecryptStream();
+  INLINE IDecryptStream(istream *source, bool owns_source,
+                        const string &password);
+
+  INLINE IDecryptStream &open(istream *source, bool owns_source,
+                              const string &password);
+  INLINE IDecryptStream &close();
+
+private:
+  EncryptStreamBuf _buf;
+};
+
+////////////////////////////////////////////////////////////////////
+//       Class : OEncryptStream
+// Description : An input stream object that uses OpenSSL to encrypt
+//               data to another destination stream on-the-fly.
+//
+//               Attach an OEncryptStream to an existing ostream that
+//               will accept encrypted data, and write your
+//               unencrypted source data to the OEncryptStream.
+//
+//               Seeking is not supported.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS OEncryptStream : public ostream {
+public:
+  INLINE OEncryptStream();
+  INLINE OEncryptStream(ostream *dest, bool owns_dest, 
+                        const string &password,
+                        const string &encryption_algorithm = "");
+
+  INLINE OEncryptStream &open(ostream *dest, bool owns_dest, 
+                              const string &password,
+                              const string &encryption_algorithm = "");
+  INLINE OEncryptStream &close();
+
+private:
+  EncryptStreamBuf _buf;
+};
+
+BEGIN_PUBLISH
+string encrypt_string(const string &source, const string &password,
+                      const string &algorithm = "");
+string decrypt_string(const string &source, const string &password);
+END_PUBLISH
+
+#include "encryptStream.I"
+
+#endif  // HAVE_SSL
+
+
+#endif
+
+

+ 423 - 0
panda/src/express/encryptStreamBuf.cxx

@@ -0,0 +1,423 @@
+// Filename: encryptStreamBuf.cxx
+// Created by:  drose (01Sep04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "encryptStreamBuf.h"
+#include "config_express.h"
+#include "streamReader.h"
+#include "streamWriter.h"
+
+#ifdef HAVE_SSL
+
+#include <openssl/rand.h>
+
+#ifndef HAVE_STREAMSIZE
+// Some compilers (notably SGI) don't define this for us
+typedef int streamsize;
+#endif /* HAVE_STREAMSIZE */
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EncryptStreamBuf::
+EncryptStreamBuf() {
+  _source = (istream *)NULL;
+  _owns_source = false;
+  _dest = (ostream *)NULL;
+  _owns_dest = false;
+
+  _read_valid = false;
+  _write_valid = false;
+
+  _read_overflow_buffer = NULL;
+  _in_read_overflow_buffer = 0;
+
+#ifdef HAVE_IOSTREAM
+  char *buf = new char[4096];
+  char *ebuf = buf + 4096;
+  setg(buf, ebuf, ebuf);
+  setp(buf, ebuf);
+
+#else
+  allocate();
+  setg(base(), ebuf(), ebuf());
+  setp(base(), ebuf());
+#endif
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::Destructor
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+EncryptStreamBuf::
+~EncryptStreamBuf() {
+  close_read();
+  close_write();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::open_read
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void EncryptStreamBuf::
+open_read(istream *source, bool owns_source, const string &password) {
+  OpenSSL_add_all_algorithms();
+
+  _source = source;
+  _owns_source = owns_source;
+  _read_valid = false;
+
+  // Now read the header information.
+  StreamReader sr(_source, false);
+  int nid = sr.get_uint16();
+
+  const EVP_CIPHER *cipher = EVP_get_cipherbynid(nid);
+
+  if (cipher == NULL) {
+    express_cat.error()
+      << "Unknown encryption algorithm in stream.\n";
+    return;
+  }
+
+  express_cat.debug()
+    << "Using decryption algorithm " << OBJ_nid2sn(nid) << "\n";
+
+  int iv_length = EVP_CIPHER_iv_length(cipher);
+  int key_length = EVP_CIPHER_key_length(cipher);
+  _read_block_size = EVP_CIPHER_block_size(cipher);
+
+  string iv = sr.extract_bytes(iv_length);
+
+  unsigned char *key = (unsigned char *)alloca(key_length);
+
+  // Hash the supplied password into a key of the appropriate length.
+  int result;
+  result =
+    PKCS5_PBKDF2_HMAC_SHA1((const char *)password.data(), password.length(),
+                           (unsigned char *)iv.data(), iv.length(), 1, 
+                           key_length, key);
+  nassertv(result > 0);
+
+  result = EVP_DecryptInit(&_read_ctx, cipher, key, (unsigned char *)iv.data());
+  nassertv(result > 0);
+
+  _read_valid = true;
+
+  _read_overflow_buffer = new unsigned char[_read_block_size];
+  _in_read_overflow_buffer = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::close_read
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void EncryptStreamBuf::
+close_read() {
+  if (_read_valid) {
+    EVP_CIPHER_CTX_cleanup(&_read_ctx);
+    _read_valid = false;
+  }
+
+  if (_read_overflow_buffer != (unsigned char *)NULL) {
+    delete[] _read_overflow_buffer;
+    _read_overflow_buffer = NULL;
+  }
+
+  if (_source != (istream *)NULL) {
+    if (_owns_source) {
+      delete _source;
+      _owns_source = false;
+    }
+    _source = (istream *)NULL;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::open_write
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void EncryptStreamBuf::
+open_write(ostream *dest, bool owns_dest, const string &password,
+           const string &encryption_algorithm) {
+  OpenSSL_add_all_algorithms();
+
+  close_write();
+  _dest = dest;
+  _owns_dest = owns_dest;
+  _write_valid = false;
+
+  const EVP_CIPHER *cipher;
+  if (encryption_algorithm.empty()) {
+    // Blowfish is the default algorithm.
+    cipher = EVP_bf_cbc();
+
+  } else {
+    cipher = EVP_get_cipherbyname(encryption_algorithm.c_str());
+  }
+
+  if (cipher == NULL) {
+    express_cat.error()
+      << "Unknown encryption algorithm: " << encryption_algorithm << "\n";
+    return;
+  };
+
+  int nid = EVP_CIPHER_nid(cipher);
+  express_cat.debug()
+    << "Using encryption algorithm " << OBJ_nid2sn(nid) << "\n";
+    
+  int key_length = EVP_CIPHER_key_length(cipher);
+  int iv_length = EVP_CIPHER_iv_length(cipher);
+  _write_block_size = EVP_CIPHER_block_size(cipher);
+
+  unsigned char *key = (unsigned char *)alloca(key_length);
+  unsigned char *iv = (unsigned char *)alloca(iv_length);
+
+  // Generate a random IV.  It doesn't need to be cryptographically
+  // secure, just unique.
+  RAND_pseudo_bytes(iv, iv_length);
+
+  // Hash the supplied password into a key of the appropriate length.
+  int result;
+  result =
+    PKCS5_PBKDF2_HMAC_SHA1((const char *)password.data(), password.length(),
+                           iv, iv_length, 1, key_length, key);
+  nassertv(result > 0);
+
+  result = EVP_EncryptInit(&_write_ctx, cipher, key, iv);
+  nassertv(result > 0);
+
+  _write_valid = true;
+
+  // Now write the header information to the stream.
+  StreamWriter sw(_dest);
+  sw.add_uint16(nid);
+  sw.append_data(iv, iv_length);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::close_write
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void EncryptStreamBuf::
+close_write() {
+  if (_dest != (ostream *)NULL) {
+    size_t n = pptr() - pbase();
+    write_chars(pbase(), n);
+    pbump(-(int)n);
+    
+    if (_write_valid) {
+      unsigned char *write_buffer = (unsigned char *)alloca(_write_block_size);
+      int bytes_written = 0;
+      EVP_EncryptFinal(&_write_ctx, write_buffer, &bytes_written);
+      
+      _dest->write((const char *)write_buffer, bytes_written);
+      
+      _write_valid = false;
+    }
+
+    if (_owns_dest) {
+      delete _dest;
+      _owns_dest = false;
+    }
+    _dest = (ostream *)NULL;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::overflow
+//       Access: Protected, Virtual
+//  Description: Called by the system ostream implementation when its
+//               internal buffer is filled, plus one character.
+////////////////////////////////////////////////////////////////////
+int EncryptStreamBuf::
+overflow(int ch) {
+  size_t n = pptr() - pbase();
+  if (n != 0) {
+    write_chars(pbase(), n);
+    pbump(-(int)n);
+  }
+
+  if (ch != EOF) {
+    // Write one more character.
+    char c = ch;
+    write_chars(&c, 1);
+  }
+
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::sync
+//       Access: Protected, Virtual
+//  Description: Called by the system iostream implementation to
+//               implement a flush operation.
+////////////////////////////////////////////////////////////////////
+int EncryptStreamBuf::
+sync() {
+  if (_source != (istream *)NULL) {
+    size_t n = egptr() - gptr();
+    gbump(n);
+  }
+
+  if (_dest != (ostream *)NULL) {
+    size_t n = pptr() - pbase();
+    write_chars(pbase(), n);
+    pbump(-(int)n);
+  }
+
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::underflow
+//       Access: Protected, Virtual
+//  Description: Called by the system istream implementation when its
+//               internal buffer needs more characters.
+////////////////////////////////////////////////////////////////////
+int EncryptStreamBuf::
+underflow() {
+  // Sometimes underflow() is called even if the buffer is not empty.
+  if (gptr() >= egptr()) {
+    size_t buffer_size = egptr() - eback();
+    gbump(-(int)buffer_size);
+
+    size_t num_bytes = buffer_size;
+    size_t read_count = read_chars(gptr(), buffer_size);
+
+    if (read_count != num_bytes) {
+      // Oops, we didn't read what we thought we would.
+      if (read_count == 0) {
+        gbump(num_bytes);
+        return EOF;
+      }
+
+      // Slide what we did read to the top of the buffer.
+      nassertr(read_count < num_bytes, EOF);
+      size_t delta = num_bytes - read_count;
+      memmove(gptr() + delta, gptr(), read_count);
+      gbump(delta);
+    }
+  }
+
+  return (unsigned char)*gptr();
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::read_chars
+//       Access: Private
+//  Description: Gets some characters from the source stream.
+////////////////////////////////////////////////////////////////////
+size_t EncryptStreamBuf::
+read_chars(char *start, size_t length) {
+  if (_in_read_overflow_buffer != 0) {
+    // Take from the overflow buffer.
+    length = min(length, _in_read_overflow_buffer);
+    memcpy(start, _read_overflow_buffer, length);
+    _in_read_overflow_buffer -= length;
+    memcpy(_read_overflow_buffer + length, _read_overflow_buffer, _in_read_overflow_buffer);
+    return length;
+  }
+
+  unsigned char *source_buffer = (unsigned char *)alloca(length);
+  size_t max_read_buffer = length + _read_block_size;
+  unsigned char *read_buffer = (unsigned char *)alloca(max_read_buffer);
+
+  int bytes_read = 0;
+    
+  do {
+    // Get more bytes from the stream.    
+    if (!_read_valid) {
+      return 0;
+    }
+    
+    _source->read((char *)source_buffer, length);
+    size_t source_length = _source->gcount();
+
+    bytes_read = 0;
+    int result;
+    if (source_length != 0) {
+      result =
+        EVP_DecryptUpdate(&_read_ctx, read_buffer, &bytes_read,
+                          source_buffer, source_length);
+    } else {
+      result =
+        EVP_DecryptFinal(&_read_ctx, read_buffer, &bytes_read);
+      _read_valid = false;
+    }
+
+    if (result <= 0) {
+      express_cat.error()
+        << "Error decrypting stream.\n";
+      if (_read_valid) {
+        EVP_CIPHER_CTX_cleanup(&_read_ctx);
+        _read_valid = false;
+      }
+    }
+
+  } while (bytes_read == 0);
+
+  // Now store the read bytes in the output stream.
+  if ((size_t)bytes_read <= length) {
+    // No overflow.
+    memcpy(start, read_buffer, bytes_read);
+    return bytes_read;
+
+  } else {
+    // We have to save some of the returned bytes in the overflow
+    // buffer.
+    _in_read_overflow_buffer = bytes_read - length;
+    nassertr(_in_read_overflow_buffer <= _read_block_size, 0);
+
+    memcpy(_read_overflow_buffer, read_buffer + length, 
+           _in_read_overflow_buffer);
+    memcpy(start, read_buffer, length);
+    return length;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EncryptStreamBuf::write_chars
+//       Access: Private
+//  Description: Sends some characters to the dest stream.
+////////////////////////////////////////////////////////////////////
+void EncryptStreamBuf::
+write_chars(const char *start, size_t length) {
+  if (_write_valid) {
+    size_t max_write_buffer = length + _write_block_size;
+    unsigned char *write_buffer = (unsigned char *)alloca(max_write_buffer);
+    
+    int bytes_written = 0;
+    int result = 
+      EVP_EncryptUpdate(&_write_ctx, write_buffer, &bytes_written,
+                        (unsigned char *)start, length);
+    if (result <= 0) {
+      express_cat.error() 
+        << "Error encrypting stream.\n";
+    }
+    _dest->write((const char *)write_buffer, bytes_written);
+  }
+}
+
+#endif  // HAVE_SSL

+ 75 - 0
panda/src/express/encryptStreamBuf.h

@@ -0,0 +1,75 @@
+// Filename: encryptStreamBuf.h
+// Created by:  drose (01Sep04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef ENCRYPTSTREAMBUF_H
+#define ENCRYPTSTREAMBUF_H
+
+#include "pandabase.h"
+
+// This module is not compiled if OpenSSL is not available.
+#ifdef HAVE_SSL
+
+#include <openssl/evp.h>
+
+////////////////////////////////////////////////////////////////////
+//       Class : EncryptStreamBuf
+// Description : The streambuf object that implements
+//               IDecompressStream and OCompressStream.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS EncryptStreamBuf : public streambuf {
+public:
+  EncryptStreamBuf();
+  virtual ~EncryptStreamBuf();
+
+  void open_read(istream *source, bool owns_source, const string &password);
+  void close_read();
+
+  void open_write(ostream *dest, bool owns_dest, const string &password,
+                  const string &encryption_algorithm);
+  void close_write();
+
+protected:
+  virtual int overflow(int c);
+  virtual int sync(void);
+  virtual int underflow(void);
+
+private:
+  size_t read_chars(char *start, size_t length);
+  void write_chars(const char *start, size_t length);
+
+private:
+  istream *_source;
+  bool _owns_source;
+
+  ostream *_dest;
+  bool _owns_dest;
+  
+  bool _read_valid;
+  EVP_CIPHER_CTX _read_ctx;
+  size_t _read_block_size;
+  unsigned char *_read_overflow_buffer;
+  size_t _in_read_overflow_buffer;
+
+  bool _write_valid;
+  EVP_CIPHER_CTX _write_ctx;
+  size_t _write_block_size;
+};
+
+#endif  // HAVE_SSL
+
+#endif

+ 2 - 0
panda/src/express/express_composite1.cxx

@@ -14,6 +14,8 @@
 #include "datagramSink.cxx"
 #include "dcast.cxx"
 #include "error_utils.cxx"
+#include "encryptStreamBuf.cxx"
+#include "encryptStream.cxx"
 #include "get_config_path.cxx"
 #include "hashGeneratorBase.cxx"
 #include "hashVal.cxx"

+ 1 - 0
panda/src/express/express_composite2.cxx

@@ -1,4 +1,5 @@
 #include "patchfile.cxx"
+#include "password_hash.cxx"
 #include "pmutex.cxx"
 #include "profileTimer.cxx"
 #include "pta_uchar.cxx"

+ 97 - 0
panda/src/express/multifile.I

@@ -74,6 +74,103 @@ get_scale_factor() const {
   return _new_scale_factor;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Multifile::set_encryption_flag
+//       Access: Published
+//  Description: Sets the flag indicating whether subsequently-added
+//               subfiles should be encrypted before writing them to
+//               the multifile.  If true, subfiles will be encrypted;
+//               if false (the default), they will be written without
+//               encryption.
+//
+//               When true, subfiles will be encrypted with the
+//               password and algorithm specified by
+//               set_encryption_password() and
+//               set_encryption_algorithm().  It is possible to apply
+//               a different password or algorithm to different files,
+//               but you must call flush() or repack() before changing
+//               these properties.
+////////////////////////////////////////////////////////////////////
+INLINE void Multifile::
+set_encryption_flag(bool flag) {
+#ifndef HAVE_SSL
+  if (flag) {
+    express_cat.warning()
+      << "OpenSSL not compiled in; cannot generated compressed multifiles.\n";
+    flag = false;
+  }
+#endif  // HAVE_SSL
+  _encryption_flag = flag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Multifile::get_encryption_flag
+//       Access: Published
+//  Description: Returns the flag indicating whether
+//               subsequently-added subfiles should be encrypted
+//               before writing them to the multifile.  See
+//               set_encryption_flag().
+////////////////////////////////////////////////////////////////////
+INLINE bool Multifile::
+get_encryption_flag() const {
+  return _encryption_flag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Multifile::set_encryption_password
+//       Access: Published
+//  Description: Specifies the password that will be used to encrypt
+//               subfiles subsequently added to the multifile, if the
+//               encryption flag is also set true (see
+//               set_encryption_flag()).
+////////////////////////////////////////////////////////////////////
+INLINE void Multifile::
+set_encryption_password(const string &password) {
+  _encryption_password = password;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Multifile::get_encryption_password
+//       Access: Published
+//  Description: Returns the password that will be used to encrypt
+//               subfiles subsequently added to the multifile.  See
+//               set_encryption_password().
+////////////////////////////////////////////////////////////////////
+INLINE const string &Multifile::
+get_encryption_password() const {
+  return _encryption_password;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Multifile::set_encryption_algorithm
+//       Access: Published
+//  Description: Specifies the algorithm that will be used to encrypt
+//               subfiles subsequently added to the multifile, if the
+//               encryption flag is also set true (see
+//               set_encryption_flag()).
+//
+//               The empty string, which is the initial value for this
+//               parameter, implies the default encryption algorithm.
+//               The complete list of available encryption algorithms
+//               is defined by OpenSSL.
+////////////////////////////////////////////////////////////////////
+INLINE void Multifile::
+set_encryption_algorithm(const string &algorithm) {
+  _encryption_algorithm = algorithm;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Multifile::get_encryption_algorithm
+//       Access: Published
+//  Description: Returns the algorithm that will be used to encrypt
+//               subfiles subsequently added to the multifile.  See
+//               set_encryption_algorithm().
+////////////////////////////////////////////////////////////////////
+INLINE const string &Multifile::
+get_encryption_algorithm() const {
+  return _encryption_algorithm;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Multifile::read_subfile
 //       Access: Published

+ 125 - 38
panda/src/express/multifile.cxx

@@ -23,6 +23,7 @@
 #include "streamReader.h"
 #include "datagram.h"
 #include "zStream.h"
+#include "encryptStream.h"
 
 #include <algorithm>
 
@@ -37,6 +38,18 @@ const size_t Multifile::_header_size = 6;
 const int Multifile::_current_major_ver = 1;
 const int Multifile::_current_minor_ver = 0;
 
+// To confirm that the supplied password matches, we write the
+// Mutifile magic header at the beginning of the encrypted stream.
+// I suppose this does compromise the encryption security a tiny
+// bit by making it easy for crackers to validate that a
+// particular password guess matches or doesn't match, but the
+// encryption algorithm doesn't depend on this being difficult
+// anyway.
+const char Multifile::_encrypt_header[] = "crypty";
+const size_t Multifile::_encrypt_header_size = 6;
+
+
+
 //
 // A Multifile consists of the following elements:
 //
@@ -65,9 +78,10 @@ const int Multifile::_current_minor_ver = 0;
 //   uint32     The address of this subfile's data record.
 //   uint32     The length in bytes of this subfile's data record.
 //   uint16     The Subfile::_flags member.
-//  [uint32]    The original, uncompressed length of the subfile, if it
-//               is compressed.  This field is only present if the
-//               SF_compressed bit is set in _flags.
+//  [uint32]    The original, uncompressed and unencrypted length of the
+//               subfile, if it is compressed or encrypted.  This field
+//               is only present if one or both of the SF_compressed
+//               or SF_encrypted bits are set in _flags.
 //   uint16     The length in bytes of the subfile's name.
 //   char[n]    The subfile's name.
 //
@@ -93,6 +107,7 @@ Multifile() {
   _needs_repack = false;
   _scale_factor = 1;
   _new_scale_factor = 1;
+  _encryption_flag = false;
   _file_major_ver = 0;
   _file_minor_ver = 0;
 }
@@ -441,6 +456,7 @@ flush() {
       Subfile *subfile = (*pi);
       _last_index = _next_index;
       _next_index = subfile->write_index(*_write, _next_index, this);
+      nassertr(_next_index == _write->tellp(), false);
       _next_index = pad_to_streampos(_next_index);
       nassertr(_next_index == _write->tellp(), false);
     }
@@ -450,12 +466,15 @@ flush() {
     StreamWriter writer(_write);
     writer.add_uint32(0);
     _next_index += 4;
+    nassertr(_next_index == _write->tellp(), false);
     _next_index = pad_to_streampos(_next_index);
 
     // All right, now write out each subfile's data.
     for (pi = _new_subfiles.begin(); pi != _new_subfiles.end(); ++pi) {
       Subfile *subfile = (*pi);
-      _next_index = subfile->write_data(*_write, _read, _next_index);
+      _next_index = subfile->write_data(*_write, _read, _next_index,
+                                        this);
+      nassertr(_next_index == _write->tellp(), false);
       _next_index = pad_to_streampos(_next_index);
       if (subfile->is_data_invalid()) {
         wrote_ok = false;
@@ -751,16 +770,31 @@ is_subfile_compressed(int index) const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: Multifile::get_subfile_compressed_length
+//     Function: Multifile::is_subfile_encrypted
+//       Access: Published
+//  Description: Returns true if the indicated subfile has been
+//               encrypted when stored within the archive, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool Multifile::
+is_subfile_encrypted(int index) const {
+  nassertr(index >= 0 && index < (int)_subfiles.size(), 0);
+  return (_subfiles[index]->_flags & SF_encrypted) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Multifile::get_subfile_internal_length
 //       Access: Published
 //  Description: Returns the number of bytes the indicated subfile
 //               consumes within the archive.  For compressed
 //               subfiles, this will generally be smaller than
-//               get_subfile_length(); for noncompressed subfiles, it
-//               will be equal.
+//               get_subfile_length(); for encrypted (but
+//               noncompressed) subfiles, it may be slightly
+//               different, for noncompressed and nonencrypted
+//               subfiles, it will be equal.
 ////////////////////////////////////////////////////////////////////
 size_t Multifile::
-get_subfile_compressed_length(int index) const {
+get_subfile_internal_length(int index) const {
   nassertr(index >= 0 && index < (int)_subfiles.size(), 0);
   return _subfiles[index]->_data_length;
 }
@@ -811,6 +845,33 @@ open_read_subfile(int index) {
     new ISubStream(_read, subfile->_data_start,
                    subfile->_data_start + (streampos)subfile->_data_length); 
   
+  if ((subfile->_flags & SF_encrypted) != 0) {
+#ifndef HAVE_SSL
+    express_cat.error()
+      << "OpenSSL not compiled in; cannot read encrypted multifiles.\n";
+    delete stream;
+    return NULL;
+#else  // HAVE_SSL
+    // The subfile is encrypted.  So actually, return an
+    // IDecryptStream that wraps around the ISubStream.
+    IDecryptStream *wrapper = 
+      new IDecryptStream(stream, true, _encryption_password);
+    stream = wrapper;
+
+    // Validate the password by confirming that the encryption header
+    // matches.
+    char this_header[_encrypt_header_size];
+    stream->read(this_header, _encrypt_header_size);
+    if (stream->fail() || stream->gcount() != (unsigned)_encrypt_header_size ||
+        memcmp(this_header, _encrypt_header, _encrypt_header_size) != 0) {
+      express_cat.error()
+        << "Unable to decrypt subfile " << subfile->_name << ".\n";
+      delete stream;
+      return NULL;
+    }
+#endif  // HAVE_SSL
+  }
+
   if ((subfile->_flags & SF_compressed) != 0) {
 #ifndef HAVE_ZLIB
     express_cat.error()
@@ -1086,8 +1147,8 @@ extract_subfile_to(int index, ostream &out) {
 //  Description: Assumes the _write pointer is at the indicated fpos,
 //               rounds the fpos up to the next legitimate address
 //               (using normalize_streampos()), and writes enough
-//               zeros to the stream to fill the gap.  Returns the new
-//               fpos.
+//               zeroes to the stream to fill the gap.  Returns the
+//               new fpos.
 ////////////////////////////////////////////////////////////////////
 streampos Multifile::
 pad_to_streampos(streampos fpos) {
@@ -1098,6 +1159,7 @@ pad_to_streampos(streampos fpos) {
     _write->put(0);
     fpos += 1; // VC++ doesn't define streampos++ (!)
   }
+  nassertr(_write->tellp() == fpos, fpos);
   return fpos;
 }
 
@@ -1120,6 +1182,12 @@ add_new_subfile(Subfile *subfile, int compression_level) {
 #endif  // HAVE_ZLIB
   }
 
+#ifdef HAVE_SSL
+  if (_encryption_flag) {
+    subfile->_flags |= SF_encrypted;
+  }
+#endif  // HAVE_SSL
+
   if (_next_index != (streampos)0) {
     // If we're adding a Subfile to an already-existing Multifile, we
     // will eventually need to repack the file.
@@ -1348,7 +1416,7 @@ read_index(istream &read, streampos fpos, Multifile *multifile) {
   _data_start = multifile->word_to_streampos(reader.get_uint32());
   _data_length = reader.get_uint32();
   _flags = reader.get_uint16();
-  if ((_flags & SF_compressed) != 0) {
+  if ((_flags & (SF_compressed | SF_encrypted)) != 0) {
     _uncompressed_length = reader.get_uint32();
   } else {
     _uncompressed_length = _data_length;
@@ -1400,7 +1468,7 @@ write_index(ostream &write, streampos fpos, Multifile *multifile) {
   dg.add_uint32(multifile->streampos_to_word(_data_start));
   dg.add_uint32(_data_length);
   dg.add_uint16(_flags);
-  if ((_flags & SF_compressed) != 0) {
+  if ((_flags & (SF_compressed | SF_encrypted)) != 0) {
     dg.add_uint32(_uncompressed_length);
   }
   dg.add_uint16(_name.length());
@@ -1449,7 +1517,8 @@ write_index(ostream &write, streampos fpos, Multifile *multifile) {
 //               contents of the Subfile during a repack() operation.
 ////////////////////////////////////////////////////////////////////
 streampos Multifile::Subfile::
-write_data(ostream &write, istream *read, streampos fpos) {
+write_data(ostream &write, istream *read, streampos fpos,
+           Multifile *multifile) {
   nassertr(write.tellp() == fpos, fpos);
 
   istream *source = _source;
@@ -1494,6 +1563,28 @@ write_data(ostream &write, istream *read, streampos fpos) {
   } else {
     // We do have source data.  Copy it in, and also measure its
     // length.
+    ostream *putter = &write;
+    bool delete_putter = false;
+
+#ifndef HAVE_SSL
+    // Without OpenSSL, we can't support encryption.  The flag had
+    // better not be set.
+    nassertr((_flags & SF_encrypted) == 0, fpos);
+#else  // HAVE_ZLIB
+    if ((_flags & SF_encrypted) != 0) {
+      // Write it encrypted.
+      putter = new OEncryptStream(putter, delete_putter, 
+                                  multifile->_encryption_password, 
+                                  multifile->_encryption_algorithm);
+      delete_putter = true;
+
+      // Also write the encrypt_header to the beginning of the
+      // encrypted stream, so we can validate the password on
+      // decryption.
+      putter->write(_encrypt_header, _encrypt_header_size);
+    }
+#endif  // HAVE_ZLIB
+
 #ifndef HAVE_ZLIB
     // Without ZLIB, we can't support compression.  The flag had
     // better not be set.
@@ -1501,31 +1592,27 @@ write_data(ostream &write, istream *read, streampos fpos) {
 #else  // HAVE_ZLIB
     if ((_flags & SF_compressed) != 0) {
       // Write it compressed.
-      streampos write_start = write.tellp();
-      _uncompressed_length = 0;
-      OCompressStream zstream(&write, false, _compression_level);
-      int byte = source->get();
-      while (!source->eof() && !source->fail()) {
-        _uncompressed_length++;
-        zstream.put(byte);
-        byte = source->get();
-      }
-      zstream.close();
-      streampos write_end = write.tellp();
-      _data_length = (size_t)(write_end - write_start);
-    } else
+      putter = new OCompressStream(putter, delete_putter, _compression_level);
+      delete_putter = true;
+    }
 #endif  // HAVE_ZLIB
-      {
-        // Write it uncompressed.
-        _uncompressed_length = 0;
-        int byte = source->get();
-        while (!source->eof() && !source->fail()) {
-          _uncompressed_length++;
-          write.put(byte);
-          byte = source->get();
-        }
-        _data_length = _uncompressed_length;
-      }
+
+    streampos write_start = fpos;
+    _uncompressed_length = 0;
+
+    int byte = source->get();
+    while (!source->eof() && !source->fail()) {
+      _uncompressed_length++;
+      putter->put(byte);
+      byte = source->get();
+    }
+
+    if (delete_putter) {
+      delete putter;
+    }
+
+    streampos write_end = write.tellp();
+    _data_length = (size_t)(write_end - write_start);
   }
 
   // We can't set _data_start until down here, after we have read the
@@ -1559,7 +1646,7 @@ rewrite_index_data_start(ostream &write, Multifile *multifile) {
   writer.add_uint32(multifile->streampos_to_word(_data_start));
   writer.add_uint32(_data_length);
   writer.add_uint16(_flags);
-  if ((_flags & SF_compressed) != 0) {
+  if ((_flags & (SF_compressed | SF_encrypted)) != 0) {
     writer.add_uint32(_uncompressed_length);
   }
 }

+ 19 - 2
panda/src/express/multifile.h

@@ -55,6 +55,13 @@ PUBLISHED:
   void set_scale_factor(size_t scale_factor);
   INLINE size_t get_scale_factor() const;
 
+  INLINE void set_encryption_flag(bool flag);
+  INLINE bool get_encryption_flag() const;
+  INLINE void set_encryption_password(const string &password);
+  INLINE const string &get_encryption_password() const;
+  INLINE void set_encryption_algorithm(const string &algorithm);
+  INLINE const string &get_encryption_algorithm() const;
+
   string add_subfile(const string &subfile_name, const Filename &filename,
                      int compression_level);
   string update_subfile(const string &subfile_name, const Filename &filename,
@@ -71,7 +78,8 @@ PUBLISHED:
   const string &get_subfile_name(int index) const;
   size_t get_subfile_length(int index) const;
   bool is_subfile_compressed(int index) const;
-  size_t get_subfile_compressed_length(int index) const;
+  bool is_subfile_encrypted(int index) const;
+  size_t get_subfile_internal_length(int index) const;
 
   INLINE string read_subfile(int index);
   istream *open_read_subfile(int index);
@@ -99,6 +107,7 @@ private:
     SF_index_invalid  = 0x0002,
     SF_data_invalid   = 0x0004,
     SF_compressed     = 0x0008,
+    SF_encrypted      = 0x0010,
   };
 
   class Subfile {
@@ -109,7 +118,8 @@ private:
                          Multifile *multfile);
     streampos write_index(ostream &write, streampos fpos,
                           Multifile *multifile);
-    streampos write_data(ostream &write, istream *read, streampos fpos);
+    streampos write_data(ostream &write, istream *read, streampos fpos,
+                         Multifile *multifile);
     void rewrite_index_data_start(ostream &write, Multifile *multifile);
     void rewrite_index_flags(ostream &write);
     INLINE bool is_deleted() const;
@@ -155,6 +165,10 @@ private:
   size_t _scale_factor;
   size_t _new_scale_factor;
 
+  bool _encryption_flag;
+  string _encryption_password;
+  string _encryption_algorithm;
+
   ifstream _read_file;
   ofstream _write_file;
   fstream _read_write_file;
@@ -168,6 +182,9 @@ private:
   static const int _current_major_ver;
   static const int _current_minor_ver;
 
+  static const char _encrypt_header[];
+  static const size_t _encrypt_header_size;
+
   friend class Subfile;
 };
 

+ 79 - 0
panda/src/express/password_hash.cxx

@@ -0,0 +1,79 @@
+// Filename: password_hash.cxx
+// Created by:  drose (01Sep04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "password_hash.h"
+
+// The functions defined within this file rely on algorithms defined
+// within OpenSSL.
+#ifdef HAVE_SSL
+
+#include <openssl/evp.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: password_hash
+//       Access: Published
+//  Description: Generates a non-reversible hash of a particular
+//               length based on an arbitrary password and a random
+//               salt.  This is much stronger than the algorithm
+//               implemented by the standard Unix crypt().
+//
+//               The resulting hash can be useful for two primary
+//               purposes: (1) the hash may be recorded to disk in
+//               lieu of recording plaintext passwords, for validation
+//               against a password entered by the user later (which
+//               should produce the same hash given a particular
+//               salt), or (2) the hash may be used as input to an
+//               encryption algorithm that requires a key of a
+//               particular length.
+//
+//               password is the text password provided by a user.
+//
+//               salt should be a string of arbitrary random bytes (it
+//               need not be crypotographically secure, just different
+//               for each different hash).
+//
+//               iters should be a number in the thousands to indicate
+//               the number of times the hash algorithm should be
+//               applied.  In general, iters should be chosen to make
+//               the computation as expensive as it can be and still
+//               be tolerable, to reduce the attractiveness of a
+//               brute-force attack.
+//
+//               keylen is the length in bytes of the required key
+//               hash.
+////////////////////////////////////////////////////////////////////
+string
+password_hash(const string &password, const string &salt,
+              int iters, int keylen) {
+  nassertr(iters > 0 && keylen > 0, string());
+  unsigned char *dk = new unsigned char[keylen];
+  int result =
+    PKCS5_PBKDF2_HMAC_SHA1((const char *)password.data(), password.length(),
+                           (unsigned char *)salt.data(), salt.length(),
+                           iters, keylen, dk);
+  nassertr(result > 0, string());
+
+  string hash((char *)dk, keylen);
+  delete[] dk;
+  return hash;
+}
+
+
+
+#endif  // HAVE_SSL
+

+ 37 - 0
panda/src/express/password_hash.h

@@ -0,0 +1,37 @@
+// Filename: password_hash.h
+// Created by:  drose (01Sep04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PASSWORD_HASH_H
+#define PASSWORD_HASH_H
+
+#include "pandabase.h"
+
+// The functions defined within this file rely on algorithms defined
+// within OpenSSL.
+#ifdef HAVE_SSL
+
+BEGIN_PUBLISH
+
+string password_hash(const string &password, const string &salt, 
+                     int iters, int keylen);
+
+END_PUBLISH
+
+#endif // HAVE_SSL
+
+#endif

+ 40 - 2
panda/src/express/virtualFileSystem.cxx

@@ -83,7 +83,7 @@ mount(Multifile *multifile, const string &mount_point, int flags) {
 ////////////////////////////////////////////////////////////////////
 bool VirtualFileSystem::
 mount(const Filename &physical_filename, const string &mount_point, 
-      int flags) {
+      int flags, const string &password) {
   if (!physical_filename.exists()) {
     express_cat.warning()
       << "Attempt to mount " << physical_filename << ", not found.\n";
@@ -103,6 +103,8 @@ mount(const Filename &physical_filename, const string &mount_point,
     // It's not a directory; it must be a Multifile.
     Multifile *multifile = new Multifile;
 
+    multifile->set_encryption_password(password);
+
     // For now these are always opened read only.  Maybe later we'll
     // support read-write on Multifiles.
     flags |= MF_read_only;
@@ -550,7 +552,21 @@ get_global_ptr() {
           mount_desc = ExecutionEnvironment::expand_string(mount_desc);
           Filename physical_filename = Filename::from_os_specific(mount_desc);
 
-          _global_ptr->mount(physical_filename, mount_point, 0);
+          int flags = 0;
+          string password;
+
+          // Split the options up by commas.
+          size_t p = 0;
+          size_t q = options.find(',', p);
+          while (q != string::npos) {
+            parse_option(options.substr(p, q - p),
+                         flags, password);
+            p = q + 1;
+            q = options.find(',', p);
+          }
+          parse_option(options.substr(p), flags, password);
+
+          _global_ptr->mount(physical_filename, mount_point, flags, password);
         }
       }
     }
@@ -668,3 +684,25 @@ found_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSystem::parse_option
+//       Access: Private, Static
+//  Description: Parses one of the option flags in the options list on
+//               the vfs-mount Config.prc line.
+////////////////////////////////////////////////////////////////////
+void VirtualFileSystem::
+parse_option(const string &option, int &flags, string &password) {
+  if (option == "0" || option.empty()) {
+    // 0 is the null option.
+
+  } else if (option == "ro") {
+    flags |= MF_read_only;
+
+  } else if (option.substr(0, 3) == "pw:") {
+    password = option.substr(3);
+
+  } else {
+    express_cat.warning()
+      << "Invalid option on vfs-mount: \"" << option << "\"\n";
+  }
+}

+ 4 - 1
panda/src/express/virtualFileSystem.h

@@ -54,7 +54,8 @@ PUBLISHED:
   };
 
   bool mount(Multifile *multifile, const string &mount_point, int flags);
-  bool mount(const Filename &physical_filename, const string &mount_point, int flags);
+  bool mount(const Filename &physical_filename, const string &mount_point, 
+             int flags, const string &password = "");
   int unmount(Multifile *multifile);
   int unmount(const Filename &physical_filename);
   int unmount_point(const string &mount_point);
@@ -94,6 +95,8 @@ private:
   Filename normalize_mount_point(const string &mount_point) const;
   bool found_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
                    VirtualFileMount *mount, const string &local_filename) const;
+  static void parse_option(const string &option,
+                           int &flags, string &password);
 
   typedef pvector<VirtualFileMount *> Mounts;
   Mounts _mounts;