346 lines
8.6 KiB
C++
346 lines
8.6 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 signPrcFile_src.cxx
|
|
* @author drose
|
|
* @date 2004-10-19
|
|
*/
|
|
|
|
// 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 "panda_getopt.h"
|
|
#include "preprocess_argv.h"
|
|
|
|
#include <time.h>
|
|
|
|
#include <openssl/err.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/bio.h>
|
|
#include <openssl/evp.h>
|
|
|
|
using std::cerr;
|
|
using std::string;
|
|
|
|
string progname = PROGNAME;
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Reads the entire contents of the file, less any previous signatures, to the
|
|
* indicated string.
|
|
*/
|
|
void
|
|
read_file(std::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 == nullptr) {
|
|
// 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 != nullptr) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Outputs the indicated data stream as a series of hex digits.
|
|
*/
|
|
void
|
|
output_hex(std::ostream &out, const unsigned char *data, size_t size) {
|
|
}
|
|
|
|
/**
|
|
* Applies the signature to the named file.
|
|
*/
|
|
void
|
|
sign_prc(Filename filename, bool no_comments, EVP_PKEY *pkey) {
|
|
filename.set_text();
|
|
|
|
pifstream 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).
|
|
std::ostringstream strm;
|
|
strm << "##!\n";
|
|
if (!no_comments) {
|
|
time_t now = time(nullptr);
|
|
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 = EVP_MD_CTX_create();
|
|
|
|
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);
|
|
|
|
EVP_MD_CTX_destroy(md_ctx);
|
|
|
|
// Now open the file in write mode and rewrite it with the new signature.
|
|
pofstream out;
|
|
if (!filename.open_write(out)) {
|
|
cerr << "Unable to rewrite file " << filename << "\n";
|
|
exit(1);
|
|
}
|
|
cerr << "Rewriting " << filename << "\n";
|
|
|
|
out << data << std::hex << std::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 << std::setw(2) << (unsigned int)sig_data[q];
|
|
}
|
|
out << "\n";
|
|
}
|
|
out << std::dec;
|
|
|
|
delete[] sig_data;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
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";
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
int
|
|
main(int argc, char **argv) {
|
|
preprocess_argv(argc, argv);
|
|
if (argv[0] != nullptr && *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 = nullptr;
|
|
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, nullptr, nullptr, (void *)pp);
|
|
BIO_free(mbio);
|
|
|
|
if (pkey == nullptr) {
|
|
// 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(Filename::from_os_specific(argv[1]), no_comments, pkey);
|
|
}
|
|
|
|
return (0);
|
|
}
|