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