// Filename: signPrcFile_src.cxx // Created by: drose (19Oct04) // //////////////////////////////////////////////////////////////////// // // 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 . // //////////////////////////////////////////////////////////////////// // This file is intended to be #included by a generated *_sign?.cxx // file, one of the output files of make-prc-key. This contains the // common code to sign a prc file with the given signature. #include "dtoolbase.h" #include "filename.h" #include "executionEnvironment.h" #include #include "openssl/err.h" #include "openssl/pem.h" #include "openssl/rand.h" #include "openssl/bio.h" #include "openssl/evp.h" #ifndef HAVE_GETOPT #include "gnu_getopt.h" #else #ifdef HAVE_GETOPT_H #include #endif #endif string progname = PROGNAME; //////////////////////////////////////////////////////////////////// // Function: output_ssl_errors // Description: A convenience function that is itself a wrapper // around the OpenSSL convenience function to output the // recent OpenSSL errors. This function sends the error // string to cerr. //////////////////////////////////////////////////////////////////// void output_ssl_errors() { cerr << "Error occurred in SSL routines.\n"; static bool strings_loaded = false; if (!strings_loaded) { ERR_load_crypto_strings(); strings_loaded = true; } unsigned long e = ERR_get_error(); while (e != 0) { static const size_t buffer_len = 256; char buffer[buffer_len]; ERR_error_string_n(e, buffer, buffer_len); cerr << buffer << "\n"; e = ERR_get_error(); } } //////////////////////////////////////////////////////////////////// // Function: read_prc_line // Description: Reads a single line of the prc file. //////////////////////////////////////////////////////////////////// void read_prc_line(const string &line, string &data) { // Strip out lines with this prefix. These are from a previous // signature. if (line.substr(0, 3) == "##!") { return; } // Other lines are counted. data += line; return; } //////////////////////////////////////////////////////////////////// // Function: read_file // Description: Reads the entire contents of the file, less any // previous signatures, to the indicated string. //////////////////////////////////////////////////////////////////// void read_file(istream &in, string &data) { // We avoid getline() here because of its notorious problem with // last lines that lack a trailing newline character. static const size_t buffer_size = 1024; char buffer[buffer_size]; string prev_line; in.read(buffer, buffer_size); size_t count = in.gcount(); while (count != 0) { char *buffer_end = buffer + count; // Look for the first line in the buffer.. char *newline = (char *)memchr((void *)buffer, '\n', count); if (newline == NULL) { // The buffer was one long line. Huh. prev_line += string(buffer, count); } else { // Process the first line in the buffer. size_t length = newline - buffer; read_prc_line(prev_line + string(buffer, length + 1), data); // Now look for the next line, etc. char *start = newline + 1; newline = (char *)memchr((void *)start, '\n', buffer_end - start); while (newline != NULL) { length = newline - start; read_prc_line(string(start, length + 1), data); start = newline + 1; newline = (char *)memchr((void *)start, '\n', buffer_end - start); } // The remaining text in the buffer is the start of the next // line. length = buffer_end - start; prev_line = string(start, length); } in.read(buffer, buffer_size); count = in.gcount(); } if (!prev_line.empty()) { read_prc_line(prev_line, data); } } //////////////////////////////////////////////////////////////////// // Function: output_hex // Description: Outputs the indicated data stream as a series of hex // digits. //////////////////////////////////////////////////////////////////// void output_hex(ostream &out, const unsigned char *data, size_t size) { } //////////////////////////////////////////////////////////////////// // Function: sign_prc // Description: Applies the signature to the named file. //////////////////////////////////////////////////////////////////// void sign_prc(Filename filename, bool no_comments, EVP_PKEY *pkey) { filename.set_text(); ifstream in; if (!filename.open_read(in)) { cerr << "Unable to read file " << filename << "\n"; exit(1); } string data; read_file(in, data); in.close(); // If the file didn't end with a newline, make it do so. if (!data.empty() && data[data.length() - 1] != '\n') { data += '\n'; } // Append the comments before the signature (these get signed too). ostringstream strm; strm << "##!\n"; if (!no_comments) { time_t now = time(NULL); struct tm *t = localtime(&now); char formatted[128]; strftime(formatted, 128, "%I:%M %p %B %d, %Y", t); strm << "##! Signed on " << formatted; if (ExecutionEnvironment::has_environment_variable("USER")) { strm << " by " << ExecutionEnvironment::get_environment_variable("USER"); } time_t generated_time = GENERATED_TIME; t = localtime(&generated_time); strftime(formatted, 128, "%I:%M %p %B %d, %Y", t); strm << "\n" << "##! Signed with level " << KEY_NUMBER << " key generated on " << formatted << "\n" << "##!\n"; } data += strm.str(); EVP_MD_CTX *md_ctx; #ifdef SSL_097 md_ctx = EVP_MD_CTX_create(); #else md_ctx = new EVP_MD_CTX; #endif EVP_SignInit(md_ctx, EVP_sha1()); EVP_SignUpdate(md_ctx, data.data(), data.size()); unsigned int max_size = EVP_PKEY_size(pkey); unsigned char *sig_data = new unsigned char[max_size]; unsigned int sig_size; if (!EVP_SignFinal(md_ctx, sig_data, &sig_size, pkey)) { output_ssl_errors(); exit(1); } assert(sig_size <= max_size); #ifdef SSL_097 EVP_MD_CTX_destroy(md_ctx); #else delete md_ctx; #endif // Now open the file in write mode and rewrite it with the new // signature. ofstream out; if (!filename.open_write(out)) { cerr << "Unable to rewrite file " << filename << "\n"; exit(1); } cerr << "Rewriting " << filename << "\n"; out << data << hex << setfill('0'); static const size_t row_width = 32; for (size_t p = 0; p < sig_size; p += row_width) { out << "##!sig "; // size_t end = min(sig_size, p + row_width); size_t end = sig_size; if(end > p+row_width) end = p+row_width; for (size_t q = p; q < end; q++) { out << setw(2) << (unsigned int)sig_data[q]; } out << "\n"; } out << dec; delete[] sig_data; } //////////////////////////////////////////////////////////////////// // Function: usage // Description: //////////////////////////////////////////////////////////////////// void usage() { time_t generated_time = GENERATED_TIME; struct tm *t = localtime(&generated_time); char formatted[128]; strftime(formatted, 128, "%I:%M %p %B %d, %Y", t); cerr << "\n" << progname << " [opts] file.prc [file.prc ...]\n\n" "This program will sign the named prc file(s) with the trust level " << KEY_NUMBER << "\n" "key that was generated on " << formatted << ". This is necessary\n" "for any prc file that defines one or more config variables that require a\n" "trust level of " << KEY_NUMBER << ".\n\n" "Once the prc file has been signed, it may not be changed without invalidating\n" "the signature; if you change the file, you will have to sign it again.\n\n" "Each prc file is modified in-place with the new signature. Any previous\n" "signatures in the file are removed.\n\n" "Options:\n\n" " -n Avoids writing a comment to the file that describes the date and\n" " time the signature was applied.\n\n" " -p \"[pass phrase]\"\n" " Uses the indicated pass phrase to decrypt the private key.\n" " If this is not specified on the command line, you will be\n" " prompted interactively.\n\n"; } //////////////////////////////////////////////////////////////////// // Function: main // Description: //////////////////////////////////////////////////////////////////// int main(int argc, char *argv[]) { if (argv[0] != NULL && *argv[0]) { // Get the program name from the command-line arguments, if the OS // provides it. Filename progfile = Filename::from_os_specific(argv[0]); progname = progfile.get_basename_wo_extension(); } extern char *optarg; extern int optind; const char *optstr = "np:h"; bool no_comments = false; string pass_phrase; bool got_pass_phrase = false; int flag = getopt(argc, argv, optstr); while (flag != EOF) { switch (flag) { case 'n': no_comments = true; break; case 'p': pass_phrase = optarg; got_pass_phrase = true; break; case 'h': usage(); exit(0); default: exit(1); } flag = getopt(argc, argv, optstr); } argc -= (optind-1); argv += (optind-1); if (argc < 2) { usage(); exit(1); } // Seed the random number generator. RAND_status(); // Load the OpenSSL algorithms. OpenSSL_add_all_algorithms(); // Convert the compiled-in data to an EVP_PKEY. const char *pp = NULL; if (got_pass_phrase) { pp = pass_phrase.c_str(); } BIO *mbio = BIO_new_mem_buf((void *)KEY_DATA, KEY_LENGTH); EVP_PKEY *pkey = PEM_read_bio_PrivateKey(mbio, NULL, NULL, (void *)pp); BIO_free(mbio); if (pkey == (EVP_PKEY *)NULL) { // Actually, we're not 100% sure this was the problem, but we // can't really tell why it failed, and we're 99% sure anyway. cerr << "Invalid pass phrase.\n"; exit(1); } for (int i = 1; i < argc; i++) { sign_prc(argv[1], no_comments, pkey); } return (0); }