483 lines
13 KiB
C++
483 lines
13 KiB
C++
/**
|
|
* 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."
|
|
*
|
|
* @file makePrcKey.cxx
|
|
* @author drose
|
|
* @date 2004-10-19
|
|
*/
|
|
|
|
#include "dtoolbase.h"
|
|
#include "prcKeyRegistry.h"
|
|
#include "filename.h"
|
|
#include "pvector.h"
|
|
#include "panda_getopt.h"
|
|
#include "preprocess_argv.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>
|
|
|
|
using std::cerr;
|
|
using std::string;
|
|
|
|
class KeyNumber {
|
|
public:
|
|
int _number;
|
|
bool _got_pass_phrase;
|
|
string _pass_phrase;
|
|
};
|
|
typedef pvector<KeyNumber> KeyNumbers;
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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(std::ostream &out, const string &string_name,
|
|
size_t 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" << std::hex << std::setw(2) << std::setfill('0')
|
|
<< (unsigned int)(unsigned char)data_ptr[i] << std::dec;
|
|
}
|
|
}
|
|
}
|
|
out << "\";\nstatic const unsigned int " << string_name << index
|
|
<< "_length = " << data_size << ";\n";
|
|
}
|
|
|
|
/**
|
|
* Generates a new public and private key pair.
|
|
*/
|
|
EVP_PKEY *
|
|
generate_key() {
|
|
RSA *rsa = RSA_new();
|
|
BIGNUM *e = BN_new();
|
|
if (rsa == nullptr || e == nullptr) {
|
|
output_ssl_errors();
|
|
exit(1);
|
|
}
|
|
|
|
BN_set_word(e, 7);
|
|
|
|
if (!RSA_generate_key_ex(rsa, 1024, e, nullptr)) {
|
|
BN_free(e);
|
|
RSA_free(rsa);
|
|
output_ssl_errors();
|
|
exit(1);
|
|
}
|
|
BN_free(e);
|
|
|
|
EVP_PKEY *pkey = EVP_PKEY_new();
|
|
EVP_PKEY_assign_RSA(pkey, rsa);
|
|
return pkey;
|
|
}
|
|
|
|
/**
|
|
* 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());
|
|
|
|
size_t num_keys = pkr->get_num_keys();
|
|
size_t i;
|
|
for (i = 0; i < num_keys; i++) {
|
|
EVP_PKEY *pkey = pkr->get_key(i);
|
|
|
|
if (pkey != nullptr) {
|
|
if (!PEM_write_bio_PUBKEY(mbio, pkey)) {
|
|
output_ssl_errors();
|
|
exit(1);
|
|
}
|
|
|
|
output_c_string(out, "prc_pubkey", i, mbio);
|
|
(void)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 != nullptr) {
|
|
out << " { prc_pubkey" << i << "_data, prc_pubkey" << i
|
|
<< "_length, " << generated_time << " },\n";
|
|
} else {
|
|
out << " { nullptr, 0, 0 },\n";
|
|
}
|
|
};
|
|
|
|
out << "};\n"
|
|
<< "static const int num_prc_pubkeys = " << num_keys << ";\n\n";
|
|
}
|
|
|
|
/**
|
|
* 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 != nullptr && *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, nullptr, nullptr, 0, nullptr, nullptr);
|
|
|
|
} else {
|
|
// Otherwise, the default is to encrypt it.
|
|
write_result =
|
|
PEM_write_bio_PKCS8PrivateKey(mbio, pkey, EVP_des_ede3_cbc(),
|
|
nullptr, 0, nullptr, (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";
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
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";
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
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;
|
|
|
|
preprocess_argv(argc, argv);
|
|
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 = (int)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(nullptr);
|
|
|
|
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 = nullptr;
|
|
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);
|
|
|
|
std::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);
|
|
}
|