Panda3D
makePrcKey.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file makePrcKey.cxx
10  * @author drose
11  * @date 2004-10-19
12  */
13 
14 #include "dtoolbase.h"
15 #include "prcKeyRegistry.h"
16 #include "filename.h"
17 #include "pvector.h"
18 #include "panda_getopt.h"
19 #include "preprocess_argv.h"
20 #include <stdio.h>
21 
22 // Pick up the public key definitions.
23 #ifdef PRC_PUBLIC_KEYS_INCLUDE
24 #include PRC_PUBLIC_KEYS_INCLUDE
25 #endif
26 
27 #include <openssl/rsa.h>
28 #include <openssl/err.h>
29 #include <openssl/pem.h>
30 #include <openssl/rand.h>
31 #include <openssl/bio.h>
32 
33 using std::cerr;
34 using std::string;
35 
36 class KeyNumber {
37 public:
38  int _number;
39  bool _got_pass_phrase;
40  string _pass_phrase;
41 };
43 
44 /**
45  * A convenience function that is itself a wrapper around the OpenSSL
46  * convenience function to output the recent OpenSSL errors. This function
47  * sends the error string to cerr.
48  */
49 void
51  cerr << "Error occurred in SSL routines.\n";
52 
53  static bool strings_loaded = false;
54  if (!strings_loaded) {
55  ERR_load_crypto_strings();
56  strings_loaded = true;
57  }
58 
59  unsigned long e = ERR_get_error();
60  while (e != 0) {
61  static const size_t buffer_len = 256;
62  char buffer[buffer_len];
63  ERR_error_string_n(e, buffer, buffer_len);
64  cerr << buffer << "\n";
65  e = ERR_get_error();
66  }
67 }
68 
69 /**
70  * Extracts the data written to the indicated memory bio and writes it to the
71  * indicated stream, formatting it to be compiled into a C or C++ program as a
72  * string.
73  */
74 void
75 output_c_string(std::ostream &out, const string &string_name,
76  size_t index, BIO *mbio) {
77  char *data_ptr;
78  size_t data_size = BIO_get_mem_data(mbio, &data_ptr);
79 
80  out << "static const char * const " << string_name
81  << index << "_data =\n"
82  << " \"";
83 
84  bool last_nl = false;
85  for (size_t i = 0; i < data_size; i++) {
86  if (data_ptr[i] == '\n') {
87  out << "\\n";
88  last_nl = true;
89 
90  } else {
91  if (last_nl) {
92  out << "\"\n \"";
93  last_nl = false;
94  }
95 
96  if (isprint(data_ptr[i])) {
97  out << data_ptr[i];
98 
99  } else {
100  out << "\\x" << std::hex << std::setw(2) << std::setfill('0')
101  << (unsigned int)(unsigned char)data_ptr[i] << std::dec;
102  }
103  }
104  }
105  out << "\";\nstatic const unsigned int " << string_name << index
106  << "_length = " << data_size << ";\n";
107 }
108 
109 /**
110  * Generates a new public and private key pair.
111  */
112 EVP_PKEY *
114  RSA *rsa = RSA_new();
115  BIGNUM *e = BN_new();
116  if (rsa == nullptr || e == nullptr) {
118  exit(1);
119  }
120 
121  BN_set_word(e, 7);
122 
123  if (!RSA_generate_key_ex(rsa, 1024, e, nullptr)) {
124  BN_free(e);
125  RSA_free(rsa);
127  exit(1);
128  }
129  BN_free(e);
130 
131  EVP_PKEY *pkey = EVP_PKEY_new();
132  EVP_PKEY_assign_RSA(pkey, rsa);
133  return pkey;
134 }
135 
136 /**
137  * Writes the list of public keys stored in the PrcKeyRegistry to the
138  * indicated output filename as a compilable list of KeyDef entries, suitable
139  * for passing to PrcKeyRegistry::record_keys().
140  */
141 void
143  outfile.set_text();
144  cerr << "Rewriting " << outfile << "\n";
145 
146  pofstream out;
147  if (!outfile.open_write(out)) {
148  cerr << "Unable to open " << outfile << " for writing.\n";
149  exit(1);
150  }
151 
152  out <<
153  "\n"
154  "// This file was generated by make-prc-key. It defines the public keys\n"
155  "// that will be used to validate signed prc files.\n"
156  "\n"
157  "#include \"prcKeyRegistry.h\"\n"
158  "\n";
159 
160  PrcKeyRegistry *pkr = PrcKeyRegistry::get_global_ptr();
161 
162  BIO *mbio = BIO_new(BIO_s_mem());
163 
164  size_t num_keys = pkr->get_num_keys();
165  size_t i;
166  for (i = 0; i < num_keys; i++) {
167  EVP_PKEY *pkey = pkr->get_key(i);
168 
169  if (pkey != nullptr) {
170  if (!PEM_write_bio_PUBKEY(mbio, pkey)) {
172  exit(1);
173  }
174 
175  output_c_string(out, "prc_pubkey", i, mbio);
176  (void)BIO_reset(mbio);
177  out << "\n";
178  }
179  }
180 
181  BIO_free(mbio);
182 
183  // Now output the table that indexes all of the above.
184  out << "static PrcKeyRegistry::KeyDef const prc_pubkeys[" << num_keys << "] = {\n";
185 
186  for (i = 0; i < num_keys; i++) {
187  EVP_PKEY *pkey = pkr->get_key(i);
188  time_t generated_time = pkr->get_generated_time(i);
189 
190  if (pkey != nullptr) {
191  out << " { prc_pubkey" << i << "_data, prc_pubkey" << i
192  << "_length, " << generated_time << " },\n";
193  } else {
194  out << " { nullptr, 0, 0 },\n";
195  }
196  };
197 
198  out << "};\n"
199  << "static const int num_prc_pubkeys = " << num_keys << ";\n\n";
200 }
201 
202 /**
203  * Generates a C++ program that can be used to sign a prc file with the
204  * indicated private key into the given output filename.
205  */
206 void
207 write_private_key(EVP_PKEY *pkey, Filename outfile, int n, time_t now,
208  const char *pp) {
209  outfile.set_text();
210  cerr << "Rewriting " << outfile << "\n";
211 
212  pofstream out;
213  if (!outfile.open_write(out)) {
214  cerr << "Unable to open " << outfile << " for writing.\n";
215  exit(1);
216  }
217 
218  out <<
219  "\n"
220  "// This file was generated by make-prc-key. It can be compiled against\n"
221  "// dtool to produce a program that will sign a prc file using key number " << n << ".\n\n";
222 
223  BIO *mbio = BIO_new(BIO_s_mem());
224 
225  int write_result;
226  if (pp != nullptr && *pp == '\0') {
227  // The supplied password was the empty string. This means not to encrypt
228  // the private key.
229  write_result =
230  PEM_write_bio_PKCS8PrivateKey(mbio, pkey, nullptr, nullptr, 0, nullptr, nullptr);
231 
232  } else {
233  // Otherwise, the default is to encrypt it.
234  write_result =
235  PEM_write_bio_PKCS8PrivateKey(mbio, pkey, EVP_des_ede3_cbc(),
236  nullptr, 0, nullptr, (void *)pp);
237  }
238 
239  if (!write_result) {
241  exit(1);
242  }
243 
244  output_c_string(out, "prc_privkey", n, mbio);
245 
246  BIO_free(mbio);
247 
248  out <<
249  "\n\n"
250  "#define KEY_NUMBER " << n << "\n"
251  "#define KEY_DATA prc_privkey" << n << "_data\n"
252  "#define KEY_LENGTH prc_privkey" << n << "_length\n"
253  "#define PROGNAME \"" << outfile.get_basename_wo_extension() << "\"\n"
254  "#define GENERATED_TIME " << now << "\n\n"
255 
256  "#include \"signPrcFile_src.cxx\"\n\n";
257 }
258 
259 /**
260  *
261  */
262 void
263 usage() {
264  cerr <<
265  "\nmake-prc-key [opts] 1[,\"pass_phrase\"] [2[,\"pass phrase\"] 3 ...]\n\n"
266 
267  "This program generates one or more new keys to be used for signing\n"
268  "a prc file. The key itself is a completely arbitrary random bit\n"
269  "sequence. It is divided into a public and a private key; the public\n"
270  "key is not secret and will be compiled into libdtool, while the private\n"
271  "key should be safeguarded and will be written into a .cxx file that\n"
272  "can be compiled as a standalone application.\n\n"
273 
274  "The output is a public and private key pair for each trust level. The\n"
275  "form of the output for both public and private keys will be compilable\n"
276  "C++ code; see -a and -b, below, for a complete description.\n\n"
277 
278  "After the options, the remaining arguments list the individual trust\n"
279  "level keys to generate. For each integer specified, a different key\n"
280  "will be created. There should be one key for each trust level\n"
281  "required; a typical application will only need one or two keys.\n\n"
282 
283  "Options:\n\n"
284 
285  " -a pub_outfile.cxx\n"
286  " Specifies the name and location of the public key output file\n"
287  " to generate. This file must then be named by the Config.pp\n"
288  " variable PRC_PUBLIC_KEYS_FILENAME so that it will be compiled\n"
289  " in with libdtool and available to verify signatures. If this\n"
290  " option is omitted, the previously-compiled value is used.\n\n"
291 
292  " -b priv_outfile#.cxx\n"
293  " Specifies the name and location of the private key output file(s)\n"
294  " to generate. A different output file will be generated for each\n"
295  " different trust level; the hash mark '#' appearing in the file\n"
296  " name will be filled in with the corresponding numeric trust level.\n"
297  " The hash mark may be omitted if you only require one trust level.\n"
298  " When compiled against dtool, each of these files will generate\n"
299  " a program that can be used to sign a prc file with the corresponding\n"
300  " trust level.\n\n"
301 
302  " -p \"[pass phrase]\"\n"
303  " Uses the indicated pass phrase to encrypt the private key.\n"
304  " This specifies an overall pass phrase; you may also specify\n"
305  " a different pass phrase for each key by using the key,\"pass phrase\"\n"
306  " syntax.\n\n"
307 
308  " If a pass phrase is not specified on the command line, you will be\n"
309  " prompted interactively. Every user of the signing programs\n"
310  " (outfile_sign1.cxx, etc.) will need to know the pass phrase\n"
311  " in order to sign prc files.\n\n"
312 
313  " If this is specified as the empty string (\"\"), then the key\n"
314  " will not be encrypted, and anyone can run the signing\n"
315  " programs without having to supply a pass phrase.\n\n";
316 }
317 
318 /**
319  *
320  */
321 int
322 main(int argc, char **argv) {
323  extern char *optarg;
324  extern int optind;
325  const char *optstr = "a:b:p:h";
326 
327  Filename pub_outfile;
328  bool got_pub_outfile = false;
329  Filename priv_outfile;
330  bool got_priv_outfile = false;
331  string pass_phrase;
332  bool got_pass_phrase = false;
333 
334  preprocess_argv(argc, argv);
335  int flag = getopt(argc, argv, optstr);
336 
337  while (flag != EOF) {
338  switch (flag) {
339  case 'a':
340  pub_outfile = optarg;
341  got_pub_outfile = true;
342  break;
343 
344  case 'b':
345  priv_outfile = optarg;
346  got_priv_outfile = true;
347  break;
348 
349  case 'p':
350  pass_phrase = optarg;
351  got_pass_phrase = true;
352  break;
353 
354  case 'h':
355  usage();
356  exit(0);
357 
358  default:
359  exit(1);
360  }
361  flag = getopt(argc, argv, optstr);
362  }
363 
364  argc -= (optind-1);
365  argv += (optind-1);
366 
367  if (argc < 2) {
368  usage();
369  exit(1);
370  }
371 
372  if (got_pub_outfile) {
373  if (pub_outfile.get_extension() != "cxx") {
374  cerr << "Public key output file '" << pub_outfile
375  << "' should have a .cxx extension.\n";
376  exit(1);
377  }
378  } else {
379 #ifdef PRC_PUBLIC_KEYS_INCLUDE
380  PrcKeyRegistry::get_global_ptr()->record_keys(prc_pubkeys, num_prc_pubkeys);
381  pub_outfile = PRC_PUBLIC_KEYS_FILENAME;
382 #endif
383 
384  if (pub_outfile.empty()) {
385  cerr << "No -a specified, and no PRC_PUBLIC_KEYS_FILENAME variable\n"
386  << "compiled in.\n\n";
387  exit(1);
388  }
389  }
390 
391  if (got_priv_outfile) {
392  if (priv_outfile.get_extension() != "cxx") {
393  cerr << "Private key output file '" << priv_outfile
394  << "' should have a .cxx extension.\n";
395  exit(1);
396  }
397 
398  } else {
399  cerr << "You must use the -b option to specify the private key output filenames.\n";
400  exit(1);
401  }
402 
403  KeyNumbers key_numbers;
404  for (int i = 1; i < argc; i++) {
405  KeyNumber key;
406  char *endptr;
407  key._number = (int)strtol(argv[i], &endptr, 0);
408  key._got_pass_phrase = got_pass_phrase;
409  key._pass_phrase = pass_phrase;
410 
411  if (*endptr == ',') {
412  // Here's a pass phrase for this particular key.
413  key._got_pass_phrase = true;
414  key._pass_phrase = endptr + 1;
415  } else if (*endptr) {
416  cerr << "Parameter '" << argv[i] << "' should be an integer.\n";
417  exit(1);
418  }
419  if (key._number <= 0) {
420  cerr << "Key numbers must be greater than 0; you specified "
421  << key._number << ".\n";
422  exit(1);
423  }
424  key_numbers.push_back(key);
425  }
426 
427  // Seed the random number generator.
428  RAND_status();
429 
430  // Load the OpenSSL algorithms.
431  OpenSSL_add_all_algorithms();
432 
433  time_t now = time(nullptr);
434 
435  string name = priv_outfile.get_fullpath_wo_extension();
436  string prefix, suffix;
437  bool got_hash;
438 
439  size_t hash = name.find('#');
440  if (hash == string::npos) {
441  prefix = name;
442  suffix = ".cxx";
443  got_hash = false;
444 
445  } else {
446  prefix = name.substr(0, hash);
447  suffix = name.substr(hash + 1) + ".cxx";
448  got_hash = true;
449  }
450 
451  KeyNumbers::iterator ki;
452  for (ki = key_numbers.begin(); ki != key_numbers.end(); ++ki) {
453  int n = (*ki)._number;
454  const char *pp = nullptr;
455  if ((*ki)._got_pass_phrase) {
456  pp = (*ki)._pass_phrase.c_str();
457  }
458 
459  EVP_PKEY *pkey = generate_key();
460  PrcKeyRegistry::get_global_ptr()->set_key(n, pkey, now);
461 
462  std::ostringstream strm;
463  if (got_hash || n != 1) {
464  // If we got an explicit hash mark, we always output the number. If we
465  // did not get an explicit hash mark, we output the number only if it is
466  // other than 1.
467  strm << prefix << n << suffix;
468 
469  } else {
470  // If we did not get an explicit hash mark in the filename, we omit the
471  // number for key 1 (this might be the only key, and so maybe the user
472  // doesn't require a number designator).
473  strm << prefix << suffix;
474  }
475 
476  write_private_key(pkey, strm.str(), n, now, pp);
477  }
478 
479  write_public_keys(pub_outfile);
480 
481  return (0);
482 }
void set_text()
Indicates that the filename represents a text file.
Definition: filename.I:424
void write_public_keys(Filename outfile)
Writes the list of public keys stored in the PrcKeyRegistry to the indicated output filename as a com...
Definition: makePrcKey.cxx:142
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void output_ssl_errors()
A convenience function that is itself a wrapper around the OpenSSL convenience function to output the...
Definition: makePrcKey.cxx:50
void write_private_key(EVP_PKEY *pkey, Filename outfile, int n, time_t now, const char *pp)
Generates a C++ program that can be used to sign a prc file with the indicated private key into the g...
Definition: makePrcKey.cxx:207
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
EVP_PKEY * generate_key()
Generates a new public and private key pair.
Definition: makePrcKey.cxx:113
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::string get_extension() const
Returns the file extension.
Definition: filename.I:400
void preprocess_argv(int &argc, char **&argv)
Processes the argc, argv pair as needed before passing it to getopt().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void output_c_string(std::ostream &out, const string &string_name, size_t index, BIO *mbio)
Extracts the data written to the indicated memory bio and writes it to the indicated stream,...
Definition: makePrcKey.cxx:75
std::string get_fullpath_wo_extension() const
Returns the full filename–directory and basename parts–except for the extension.
Definition: filename.I:377
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.