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