diff --git a/panda/src/downloadertools/Sources.pp b/panda/src/downloadertools/Sources.pp index 125f393857..e2f360af2a 100644 --- a/panda/src/downloadertools/Sources.pp +++ b/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 diff --git a/panda/src/downloadertools/multify.cxx b/panda/src/downloadertools/multify.cxx index 9b124a1e68..37f0f57961 100644 --- a/panda/src/downloadertools/multify.cxx +++ b/panda/src/downloadertools/multify.cxx @@ -28,21 +28,24 @@ #include -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 dont_compress; // -Z +size_t scale_factor = 0; // -F +pset 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 \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; diff --git a/panda/src/downloadertools/pcompress.cxx b/panda/src/downloadertools/pcompress.cxx index ce7f2a6da2..8fb90b491a 100644 --- a/panda/src/downloadertools/pcompress.cxx +++ b/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; diff --git a/panda/src/downloadertools/pdecrypt.cxx b/panda/src/downloadertools/pdecrypt.cxx new file mode 100644 index 0000000000..6cb28ebcfd --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "filename.h" +#include "encryptStream.h" +#include "notify.h" + +#ifndef HAVE_GETOPT + #include "gnu_getopt.h" +#else + #ifdef HAVE_GETOPT_H + #include + #endif +#endif + +void +usage() { + cerr + << "\n" + << "Usage: pdecrypt [opts] []\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; +} diff --git a/panda/src/downloadertools/pencrypt.cxx b/panda/src/downloadertools/pencrypt.cxx new file mode 100644 index 0000000000..a470ea65dc --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "filename.h" +#include "encryptStream.h" +#include "notify.h" + +#ifndef HAVE_GETOPT + #include "gnu_getopt.h" +#else + #ifdef HAVE_GETOPT_H + #include + #endif +#endif + +void +usage() { + cerr + << "\n" + << "Usage: pencrypt [opts] []\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; +} diff --git a/panda/src/express/Sources.pp b/panda/src/express/Sources.pp index 351da27a95..b702648a6f 100644 --- a/panda/src/express/Sources.pp +++ b/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 \ diff --git a/panda/src/express/encryptStream.I b/panda/src/express/encryptStream.I new file mode 100644 index 0000000000..4ed7951e12 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// 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; +} + diff --git a/panda/src/express/encryptStream.cxx b/panda/src/express/encryptStream.cxx new file mode 100644 index 0000000000..de10d08860 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#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(); +} diff --git a/panda/src/express/encryptStream.h b/panda/src/express/encryptStream.h new file mode 100644 index 0000000000..e27d35c133 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#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 + + diff --git a/panda/src/express/encryptStreamBuf.cxx b/panda/src/express/encryptStreamBuf.cxx new file mode 100644 index 0000000000..d49267193f --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "encryptStreamBuf.h" +#include "config_express.h" +#include "streamReader.h" +#include "streamWriter.h" + +#ifdef HAVE_SSL + +#include + +#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 diff --git a/panda/src/express/encryptStreamBuf.h b/panda/src/express/encryptStreamBuf.h new file mode 100644 index 0000000000..9dc527712f --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#ifndef ENCRYPTSTREAMBUF_H +#define ENCRYPTSTREAMBUF_H + +#include "pandabase.h" + +// This module is not compiled if OpenSSL is not available. +#ifdef HAVE_SSL + +#include + +//////////////////////////////////////////////////////////////////// +// 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 diff --git a/panda/src/express/express_composite1.cxx b/panda/src/express/express_composite1.cxx index 3e54761198..eded14a89e 100644 --- a/panda/src/express/express_composite1.cxx +++ b/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" diff --git a/panda/src/express/express_composite2.cxx b/panda/src/express/express_composite2.cxx index eb66eba9fe..ceedf97fd5 100644 --- a/panda/src/express/express_composite2.cxx +++ b/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" diff --git a/panda/src/express/multifile.I b/panda/src/express/multifile.I index 2e954ffa6d..58c8153c6b 100644 --- a/panda/src/express/multifile.I +++ b/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 diff --git a/panda/src/express/multifile.cxx b/panda/src/express/multifile.cxx index d65b08ed46..92527235ab 100644 --- a/panda/src/express/multifile.cxx +++ b/panda/src/express/multifile.cxx @@ -23,6 +23,7 @@ #include "streamReader.h" #include "datagram.h" #include "zStream.h" +#include "encryptStream.h" #include @@ -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); } } diff --git a/panda/src/express/multifile.h b/panda/src/express/multifile.h index ee5fba9c57..3923faa74d 100644 --- a/panda/src/express/multifile.h +++ b/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; }; diff --git a/panda/src/express/password_hash.cxx b/panda/src/express/password_hash.cxx new file mode 100644 index 0000000000..4f40fa9ae5 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "password_hash.h" + +// The functions defined within this file rely on algorithms defined +// within OpenSSL. +#ifdef HAVE_SSL + +#include + +//////////////////////////////////////////////////////////////////// +// 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 + diff --git a/panda/src/express/password_hash.h b/panda/src/express/password_hash.h new file mode 100644 index 0000000000..ecb8f62940 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#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 diff --git a/panda/src/express/virtualFileSystem.cxx b/panda/src/express/virtualFileSystem.cxx index 1ce1d364da..f446cb2c81 100644 --- a/panda/src/express/virtualFileSystem.cxx +++ b/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"; + } +} diff --git a/panda/src/express/virtualFileSystem.h b/panda/src/express/virtualFileSystem.h index 186eb329d2..c6cf0b7784 100644 --- a/panda/src/express/virtualFileSystem.h +++ b/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 Mounts; Mounts _mounts;