open_toontown_panda3d/dtool/src/prckeys/makePrcKey.cxx

480 lines
14 KiB
C++

// Filename: makePrcKey.cxx
// Created by: drose (19Oct04)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#include "dtoolbase.h"
#include "prcKeyRegistry.h"
#include "filename.h"
#include "pvector.h"
#include "panda_getopt.h"
#include <stdio.h>
// Pick up the public key definitions.
#ifdef PRC_PUBLIC_KEYS_INCLUDE
#include PRC_PUBLIC_KEYS_INCLUDE
#endif
#include "openssl/rsa.h"
#include "openssl/err.h"
#include "openssl/pem.h"
#include "openssl/rand.h"
#include "openssl/bio.h"
class KeyNumber {
public:
int _number;
bool _got_pass_phrase;
string _pass_phrase;
};
typedef pvector<KeyNumber> KeyNumbers;
////////////////////////////////////////////////////////////////////
// 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: output_c_string
// Description: Extracts the data written to the indicated memory bio
// and writes it to the indicated stream, formatting it
// to be compiled into a C or C++ program as a string.
////////////////////////////////////////////////////////////////////
void
output_c_string(ostream &out, const string &string_name,
int index, BIO *mbio) {
char *data_ptr;
size_t data_size = BIO_get_mem_data(mbio, &data_ptr);
out << "static const char * const " << string_name
<< index << "_data =\n"
<< " \"";
bool last_nl = false;
for (size_t i = 0; i < data_size; i++) {
if (data_ptr[i] == '\n') {
out << "\\n";
last_nl = true;
} else {
if (last_nl) {
out << "\"\n \"";
last_nl = false;
}
if (isprint(data_ptr[i])) {
out << data_ptr[i];
} else {
out << "\\x" << hex << setw(2) << setfill('0')
<< (unsigned int)(unsigned char)data_ptr[i] << dec;
}
}
}
out << "\";\nstatic const unsigned int " << string_name << index
<< "_length = " << data_size << ";\n";
}
////////////////////////////////////////////////////////////////////
// Function: generate_key
// Description: Generates a new public and private key pair.
////////////////////////////////////////////////////////////////////
EVP_PKEY *
generate_key() {
RSA *rsa = RSA_generate_key(1024, 7, NULL, NULL);
if (rsa == (RSA *)NULL) {
output_ssl_errors();
exit(1);
}
EVP_PKEY *pkey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(pkey, rsa);
return pkey;
}
////////////////////////////////////////////////////////////////////
// Function: write_public_keys
// Description: Writes the list of public keys stored in the
// PrcKeyRegistry to the indicated output filename as a
// compilable list of KeyDef entries, suitable for
// passing to PrcKeyRegistry::record_keys().
////////////////////////////////////////////////////////////////////
void
write_public_keys(Filename outfile) {
outfile.set_text();
cerr << "Rewriting " << outfile << "\n";
pofstream out;
if (!outfile.open_write(out)) {
cerr << "Unable to open " << outfile << " for writing.\n";
exit(1);
}
out <<
"\n"
"// This file was generated by make-prc-key. It defines the public keys\n"
"// that will be used to validate signed prc files.\n"
"\n"
"#include \"prcKeyRegistry.h\"\n"
"\n";
PrcKeyRegistry *pkr = PrcKeyRegistry::get_global_ptr();
BIO *mbio = BIO_new(BIO_s_mem());
int num_keys = pkr->get_num_keys();
int i;
for (i = 0; i < num_keys; i++) {
EVP_PKEY *pkey = pkr->get_key(i);
if (pkey != (EVP_PKEY *)NULL) {
if (!PEM_write_bio_PUBKEY(mbio, pkey)) {
output_ssl_errors();
exit(1);
}
output_c_string(out, "prc_pubkey", i, mbio);
BIO_reset(mbio);
out << "\n";
}
}
BIO_free(mbio);
// Now output the table that indexes all of the above.
out << "static PrcKeyRegistry::KeyDef const prc_pubkeys[" << num_keys << "] = {\n";
for (i = 0; i < num_keys; i++) {
EVP_PKEY *pkey = pkr->get_key(i);
time_t generated_time = pkr->get_generated_time(i);
if (pkey != (EVP_PKEY *)NULL) {
out << " { prc_pubkey" << i << "_data, prc_pubkey" << i
<< "_length, " << generated_time << " },\n";
} else {
out << " { NULL, 0, 0 },\n";
}
};
out << "};\n"
<< "static const int num_prc_pubkeys = " << num_keys << ";\n\n";
}
////////////////////////////////////////////////////////////////////
// Function: write_private_key
// Description: Generates a C++ program that can be used to sign a
// prc file with the indicated private key into the
// given output filename.
////////////////////////////////////////////////////////////////////
void
write_private_key(EVP_PKEY *pkey, Filename outfile, int n, time_t now,
const char *pp) {
outfile.set_text();
cerr << "Rewriting " << outfile << "\n";
pofstream out;
if (!outfile.open_write(out)) {
cerr << "Unable to open " << outfile << " for writing.\n";
exit(1);
}
out <<
"\n"
"// This file was generated by make-prc-key. It can be compiled against\n"
"// dtool to produce a program that will sign a prc file using key number " << n << ".\n\n";
BIO *mbio = BIO_new(BIO_s_mem());
int write_result;
if (pp != NULL && *pp == '\0') {
// The supplied password was the empty string. This means not to
// encrypt the private key.
write_result =
PEM_write_bio_PKCS8PrivateKey(mbio, pkey, NULL, NULL, 0, NULL, NULL);
} else {
// Otherwise, the default is to encrypt it.
write_result =
PEM_write_bio_PKCS8PrivateKey(mbio, pkey, EVP_des_ede3_cbc(),
NULL, 0, NULL, (void *)pp);
}
if (!write_result) {
output_ssl_errors();
exit(1);
}
output_c_string(out, "prc_privkey", n, mbio);
BIO_free(mbio);
out <<
"\n\n"
"#define KEY_NUMBER " << n << "\n"
"#define KEY_DATA prc_privkey" << n << "_data\n"
"#define KEY_LENGTH prc_privkey" << n << "_length\n"
"#define PROGNAME \"" << outfile.get_basename_wo_extension() << "\"\n"
"#define GENERATED_TIME " << now << "\n\n"
"#include \"signPrcFile_src.cxx\"\n\n";
}
////////////////////////////////////////////////////////////////////
// Function: usage
// Description:
////////////////////////////////////////////////////////////////////
void
usage() {
cerr <<
"\nmake-prc-key [opts] 1[,\"pass_phrase\"] [2[,\"pass phrase\"] 3 ...]\n\n"
"This program generates one or more new keys to be used for signing\n"
"a prc file. The key itself is a completely arbitrary random bit\n"
"sequence. It is divided into a public and a private key; the public\n"
"key is not secret and will be compiled into libdtool, while the private\n"
"key should be safeguarded and will be written into a .cxx file that\n"
"can be compiled as a standalone application.\n\n"
"The output is a public and private key pair for each trust level. The\n"
"form of the output for both public and private keys will be compilable\n"
"C++ code; see -a and -b, below, for a complete description.\n\n"
"After the options, the remaining arguments list the individual trust\n"
"level keys to generate. For each integer specified, a different key\n"
"will be created. There should be one key for each trust level\n"
"required; a typical application will only need one or two keys.\n\n"
"Options:\n\n"
" -a pub_outfile.cxx\n"
" Specifies the name and location of the public key output file\n"
" to generate. This file must then be named by the Config.pp\n"
" variable PRC_PUBLIC_KEYS_FILENAME so that it will be compiled\n"
" in with libdtool and available to verify signatures. If this\n"
" option is omitted, the previously-compiled value is used.\n\n"
" -b priv_outfile#.cxx\n"
" Specifies the name and location of the private key output file(s)\n"
" to generate. A different output file will be generated for each\n"
" different trust level; the hash mark '#' appearing in the file\n"
" name will be filled in with the corresponding numeric trust level.\n"
" The hash mark may be omitted if you only require one trust level.\n"
" When compiled against dtool, each of these files will generate\n"
" a program that can be used to sign a prc file with the corresponding\n"
" trust level.\n\n"
" -p \"[pass phrase]\"\n"
" Uses the indicated pass phrase to encrypt the private key.\n"
" This specifies an overall pass phrase; you may also specify\n"
" a different pass phrase for each key by using the key,\"pass phrase\"\n"
" syntax.\n\n"
" If a pass phrase is not specified on the command line, you will be\n"
" prompted interactively. Every user of the signing programs\n"
" (outfile_sign1.cxx, etc.) will need to know the pass phrase\n"
" in order to sign prc files.\n\n"
" If this is specified as the empty string (\"\"), then the key\n"
" will not be encrypted, and anyone can run the signing\n"
" programs without having to supply a pass phrase.\n\n";
}
////////////////////////////////////////////////////////////////////
// Function: main
// Description:
////////////////////////////////////////////////////////////////////
int
main(int argc, char *argv[]) {
extern char *optarg;
extern int optind;
const char *optstr = "a:b:p:h";
Filename pub_outfile;
bool got_pub_outfile = false;
Filename priv_outfile;
bool got_priv_outfile = false;
string pass_phrase;
bool got_pass_phrase = false;
int flag = getopt(argc, argv, optstr);
while (flag != EOF) {
switch (flag) {
case 'a':
pub_outfile = optarg;
got_pub_outfile = true;
break;
case 'b':
priv_outfile = optarg;
got_priv_outfile = 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);
}
if (got_pub_outfile) {
if (pub_outfile.get_extension() != "cxx") {
cerr << "Public key output file '" << pub_outfile
<< "' should have a .cxx extension.\n";
exit(1);
}
} else {
#ifdef PRC_PUBLIC_KEYS_INCLUDE
PrcKeyRegistry::get_global_ptr()->record_keys(prc_pubkeys, num_prc_pubkeys);
pub_outfile = PRC_PUBLIC_KEYS_FILENAME;
#endif
if (pub_outfile.empty()) {
cerr << "No -a specified, and no PRC_PUBLIC_KEYS_FILENAME variable\n"
<< "compiled in.\n\n";
exit(1);
}
}
if (got_priv_outfile) {
if (priv_outfile.get_extension() != "cxx") {
cerr << "Private key output file '" << priv_outfile
<< "' should have a .cxx extension.\n";
exit(1);
}
} else {
cerr << "You must use the -b option to specify the private key output filenames.\n";
exit(1);
}
KeyNumbers key_numbers;
for (int i = 1; i < argc; i++) {
KeyNumber key;
char *endptr;
key._number = strtol(argv[i], &endptr, 0);
key._got_pass_phrase = got_pass_phrase;
key._pass_phrase = pass_phrase;
if (*endptr == ',') {
// Here's a pass phrase for this particular key.
key._got_pass_phrase = true;
key._pass_phrase = endptr + 1;
} else if (*endptr) {
cerr << "Parameter '" << argv[i] << "' should be an integer.\n";
exit(1);
}
if (key._number <= 0) {
cerr << "Key numbers must be greater than 0; you specified "
<< key._number << ".\n";
exit(1);
}
key_numbers.push_back(key);
}
// Seed the random number generator.
RAND_status();
// Load the OpenSSL algorithms.
OpenSSL_add_all_algorithms();
time_t now = time(NULL);
string name = priv_outfile.get_fullpath_wo_extension();
string prefix, suffix;
bool got_hash;
size_t hash = name.find('#');
if (hash == string::npos) {
prefix = name;
suffix = ".cxx";
got_hash = false;
} else {
prefix = name.substr(0, hash);
suffix = name.substr(hash + 1) + ".cxx";
got_hash = true;
}
KeyNumbers::iterator ki;
for (ki = key_numbers.begin(); ki != key_numbers.end(); ++ki) {
int n = (*ki)._number;
const char *pp = NULL;
if ((*ki)._got_pass_phrase) {
pp = (*ki)._pass_phrase.c_str();
}
EVP_PKEY *pkey = generate_key();
PrcKeyRegistry::get_global_ptr()->set_key(n, pkey, now);
ostringstream strm;
if (got_hash || n != 1) {
// If we got an explicit hash mark, we always output the number.
// If we did not get an explicit hash mark, we output the number
// only if it is other than 1.
strm << prefix << n << suffix;
} else {
// If we did not get an explicit hash mark in the filename, we
// omit the number for key 1 (this might be the only key, and
// so maybe the user doesn't require a number designator).
strm << prefix << suffix;
}
write_private_key(pkey, strm.str(), n, now, pp);
}
write_public_keys(pub_outfile);
return (0);
}