blob: a960abd6ab545d3c92381cbcda851557b23b87e5 [file] [log] [blame]
Bill Richardson15dc6fc2014-09-02 14:45:44 -07001/*
2 * Copyright 2014 The Chromium OS Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6#include <errno.h>
7#include <fcntl.h>
8#include <getopt.h>
9#include <inttypes.h>
10#include <limits.h>
11#include <stddef.h>
12#include <stdint.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <sys/stat.h>
17#include <sys/types.h>
18#include <unistd.h>
19
20#include "bmpblk_header.h"
Bill Richardson25593382015-01-30 12:22:28 -080021#include "file_type.h"
Bill Richardson15dc6fc2014-09-02 14:45:44 -070022#include "fmap.h"
23#include "futility.h"
24#include "gbb_header.h"
25#include "host_common.h"
Bill Richardson5f2696d2014-09-23 22:03:56 -070026#include "kernel_blob.h"
Bill Richardson15dc6fc2014-09-02 14:45:44 -070027#include "traversal.h"
28#include "util_misc.h"
Bill Richardson5f2696d2014-09-23 22:03:56 -070029#include "vb1_helper.h"
Bill Richardson15dc6fc2014-09-02 14:45:44 -070030#include "vboot_common.h"
31
32/* Local values for cb_area_s._flags */
33enum callback_flags {
34 AREA_IS_VALID = 0x00000001,
35};
36
37/* Local structure for args, etc. */
Bill Richardson5f2696d2014-09-23 22:03:56 -070038static struct local_data_s {
Bill Richardson15dc6fc2014-09-02 14:45:44 -070039 VbPrivateKey *signprivate;
40 VbKeyBlockHeader *keyblock;
41 VbPublicKey *kernel_subkey;
42 VbPrivateKey *devsignprivate;
43 VbKeyBlockHeader *devkeyblock;
44 uint32_t version;
Bill Richardson5f2696d2014-09-23 22:03:56 -070045 int version_specified;
Bill Richardson15dc6fc2014-09-02 14:45:44 -070046 uint32_t flags;
Bill Richardsona19b00d2014-09-04 23:20:43 -070047 int flags_specified;
Bill Richardson15dc6fc2014-09-02 14:45:44 -070048 char *loemdir;
49 char *loemid;
Bill Richardson5f2696d2014-09-23 22:03:56 -070050 uint8_t *bootloader_data;
51 uint64_t bootloader_size;
52 uint8_t *config_data;
53 uint64_t config_size;
54 enum arch_t arch;
Bill Richardsonc540f592014-09-23 22:17:02 -070055 int fv_specified;
Bill Richardson5f2696d2014-09-23 22:03:56 -070056 uint32_t kloadaddr;
57 uint32_t padding;
58 int vblockonly;
59 char *outfile;
60 int create_new_outfile;
Bill Richardsonc540f592014-09-23 22:17:02 -070061 char *pem_signpriv;
62 int pem_algo_specified;
63 uint32_t pem_algo;
64 char *pem_external;
Bill Richardson15dc6fc2014-09-02 14:45:44 -070065} option = {
66 .version = 1,
Bill Richardson5f2696d2014-09-23 22:03:56 -070067 .arch = ARCH_UNSPECIFIED,
68 .kloadaddr = CROS_32BIT_ENTRY_ADDR,
69 .padding = 65536,
Bill Richardson15dc6fc2014-09-02 14:45:44 -070070};
71
72
Bill Richardson5f2696d2014-09-23 22:03:56 -070073/* Helper to complain about invalid args. Returns num errors discovered */
74static int no_opt_if(int expr, const char *optname)
Bill Richardson15dc6fc2014-09-02 14:45:44 -070075{
Bill Richardson5f2696d2014-09-23 22:03:56 -070076 if (expr) {
77 fprintf(stderr, "Missing --%s option\n", optname);
78 return 1;
79 }
80 return 0;
Bill Richardson15dc6fc2014-09-02 14:45:44 -070081}
82
Bill Richardson5f2696d2014-09-23 22:03:56 -070083/* This wraps/signs a public key, producing a keyblock. */
84int futil_cb_sign_pubkey(struct futil_traverse_state_s *state)
Bill Richardson15dc6fc2014-09-02 14:45:44 -070085{
Bill Richardsonc540f592014-09-23 22:17:02 -070086 VbPublicKey *data_key = (VbPublicKey *)state->my_area->buf;
87 VbKeyBlockHeader *vblock;
88
89 if (option.pem_signpriv) {
90 if (option.pem_external) {
91 /* External signing uses the PEM file directly. */
92 vblock = KeyBlockCreate_external(
93 data_key,
94 option.pem_signpriv,
95 option.pem_algo, option.flags,
96 option.pem_external);
97 } else {
98 option.signprivate = PrivateKeyReadPem(
99 option.pem_signpriv, option.pem_algo);
100 if (!option.signprivate) {
101 fprintf(stderr,
102 "Unable to read PEM signing key: %s\n",
103 strerror(errno));
104 return 1;
105 }
106 vblock = KeyBlockCreate(data_key, option.signprivate,
107 option.flags);
108 }
109 } else {
110 /* Not PEM. Should already have a signing key. */
111 vblock = KeyBlockCreate(data_key, option.signprivate,
112 option.flags);
113 }
114
115 /* Write it out */
116 return WriteSomeParts(option.outfile,
117 vblock, vblock->key_block_size,
118 NULL, 0);
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700119}
120
121/*
122 * This handles FW_MAIN_A and FW_MAIN_B while processing a BIOS image.
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700123 * The data in state->my_area is just the RW firmware blob, so there's nothing
124 * useful to show about it. We'll just mark it as present so when we encounter
125 * corresponding VBLOCK area, we'll have this to verify.
126 */
127int futil_cb_sign_fw_main(struct futil_traverse_state_s *state)
128{
129 state->my_area->_flags |= AREA_IS_VALID;
130 return 0;
131}
132
Bill Richardson779796f2014-09-23 11:47:40 -0700133/*
134 * This handles VBLOCK_A and VBLOCK_B while processing a BIOS image.
135 * We don't do any signing here. We just check to see if the VBLOCK
136 * area contains a firmware preamble.
137 */
Bill Richardson5f2696d2014-09-23 22:03:56 -0700138int futil_cb_sign_fw_vblock(struct futil_traverse_state_s *state)
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700139{
140 VbKeyBlockHeader *key_block = (VbKeyBlockHeader *)state->my_area->buf;
Bill Richardsone0519752014-09-03 14:20:10 -0700141 uint32_t len = state->my_area->len;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700142
Bill Richardsone0519752014-09-03 14:20:10 -0700143 /*
144 * If we have a valid keyblock and fw_preamble, then we can use them to
145 * determine the size of the firmware body. Otherwise, we'll have to
146 * just sign the whole region.
147 */
148 if (VBOOT_SUCCESS != KeyBlockVerify(key_block, len, NULL, 1)) {
149 fprintf(stderr, "Warning: %s keyblock is invalid. "
150 "Signing the entire FW FMAP region...\n",
Bill Richardson779796f2014-09-23 11:47:40 -0700151 state->name);
Bill Richardsone0519752014-09-03 14:20:10 -0700152 goto whatever;
153 }
154
155 RSAPublicKey *rsa = PublicKeyToRSA(&key_block->data_key);
156 if (!rsa) {
157 fprintf(stderr, "Warning: %s public key is invalid. "
158 "Signing the entire FW FMAP region...\n",
159 state->name);
160 goto whatever;
161 }
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700162 uint32_t more = key_block->key_block_size;
163 VbFirmwarePreambleHeader *preamble =
164 (VbFirmwarePreambleHeader *)(state->my_area->buf + more);
165 uint32_t fw_size = preamble->body_signature.data_size;
Bill Richardsone0519752014-09-03 14:20:10 -0700166 struct cb_area_s *fw_body_area = 0;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700167
168 switch (state->component) {
169 case CB_FMAP_VBLOCK_A:
170 fw_body_area = &state->cb_area[CB_FMAP_FW_MAIN_A];
Bill Richardson08efd1e2014-09-04 22:53:41 -0700171 /* Preserve the flags if they're not specified */
Bill Richardsona19b00d2014-09-04 23:20:43 -0700172 if (!option.flags_specified)
Bill Richardson08efd1e2014-09-04 22:53:41 -0700173 option.flags = preamble->flags;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700174 break;
175 case CB_FMAP_VBLOCK_B:
176 fw_body_area = &state->cb_area[CB_FMAP_FW_MAIN_B];
177 break;
178 default:
179 DIE;
180 }
181
182 if (fw_size > fw_body_area->len) {
183 fprintf(stderr,
184 "%s says the firmware is larger than we have\n",
Bill Richardson779796f2014-09-23 11:47:40 -0700185 state->name);
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700186 return 1;
187 }
188
189 /* Update the firmware size */
190 fw_body_area->len = fw_size;
191
Bill Richardsone0519752014-09-03 14:20:10 -0700192whatever:
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700193 state->my_area->_flags |= AREA_IS_VALID;
194
195 return 0;
196}
197
Bill Richardson5f2696d2014-09-23 22:03:56 -0700198int futil_cb_create_kernel_part(struct futil_traverse_state_s *state)
199{
200 uint8_t *vmlinuz_data, *kblob_data, *vblock_data;
201 uint64_t vmlinuz_size, kblob_size, vblock_size;
202 int rv;
203
204 vmlinuz_data = state->my_area->buf;
205 vmlinuz_size = state->my_area->len;
206
207 kblob_data = CreateKernelBlob(
208 vmlinuz_data, vmlinuz_size,
209 option.arch, option.kloadaddr,
210 option.config_data, option.config_size,
211 option.bootloader_data, option.bootloader_size,
212 &kblob_size);
213 if (!kblob_data) {
214 fprintf(stderr, "Unable to create kernel blob\n");
215 return 1;
216 }
217 Debug("kblob_size = 0x%" PRIx64 "\n", kblob_size);
218
219 vblock_data = SignKernelBlob(kblob_data, kblob_size, option.padding,
220 option.version, option.kloadaddr,
221 option.keyblock, option.signprivate,
222 &vblock_size);
223 if (!vblock_data) {
224 fprintf(stderr, "Unable to sign kernel blob\n");
225 free(kblob_data);
226 return 1;
227 }
228 Debug("vblock_size = 0x%" PRIx64 "\n", vblock_size);
229
230 /* We should be creating a completely new output file.
231 * If not, something's wrong. */
232 if (!option.create_new_outfile)
233 DIE;
234
235 if (option.vblockonly)
236 rv = WriteSomeParts(option.outfile,
237 vblock_data, vblock_size,
238 NULL, 0);
239 else
240 rv = WriteSomeParts(option.outfile,
241 vblock_data, vblock_size,
242 kblob_data, kblob_size);
243
244 free(vblock_data);
245 free(kblob_data);
246 return rv;
247}
248
249int futil_cb_resign_kernel_part(struct futil_traverse_state_s *state)
250{
251 uint8_t *kpart_data, *kblob_data, *vblock_data;
252 uint64_t kpart_size, kblob_size, vblock_size;
253 VbKeyBlockHeader *keyblock = NULL;
254 VbKernelPreambleHeader *preamble = NULL;
255 int rv = 0;
256
257 kpart_data = state->my_area->buf;
258 kpart_size = state->my_area->len;
259
260 /* Note: This just sets some static pointers. It doesn't malloc. */
261 kblob_data = UnpackKPart(kpart_data, kpart_size, option.padding,
262 &keyblock, &preamble, &kblob_size);
263
264 if (!kblob_data) {
265 fprintf(stderr, "Unable to unpack kernel partition\n");
266 return 1;
267 }
268
269 /*
270 * We don't let --kloadaddr change when resigning, because the original
271 * vbutil_kernel program didn't do it right. Since obviously no one
272 * ever noticed, we'll maintain bug-compatibility by just not allowing
273 * it here either. To enable it, we'd need to update the zeropage
274 * table's cmd_line_ptr as well as the preamble.
275 */
276 option.kloadaddr = preamble->body_load_address;
277
278 /* Replace the config if asked */
279 if (option.config_data &&
280 0 != UpdateKernelBlobConfig(kblob_data, kblob_size,
281 option.config_data,
282 option.config_size)) {
283 fprintf(stderr, "Unable to update config\n");
284 return 1;
285 }
286
287 /* Preserve the version unless a new one is given */
288 if (!option.version_specified)
289 option.version = preamble->kernel_version;
290
291 /* Replace the keyblock if asked */
292 if (option.keyblock)
293 keyblock = option.keyblock;
294
295 /* Compute the new signature */
296 vblock_data = SignKernelBlob(kblob_data, kblob_size, option.padding,
297 option.version, option.kloadaddr,
298 keyblock, option.signprivate,
299 &vblock_size);
300 if (!vblock_data) {
301 fprintf(stderr, "Unable to sign kernel blob\n");
302 return 1;
303 }
304 Debug("vblock_size = 0x%" PRIx64 "\n", vblock_size);
305
Bill Richardsonb406c102014-12-03 14:10:13 -0800306 if (option.create_new_outfile) {
307 /* Write out what we've been asked for */
308 if (option.vblockonly)
309 rv = WriteSomeParts(option.outfile,
310 vblock_data, vblock_size,
311 NULL, 0);
312 else
313 rv = WriteSomeParts(option.outfile,
314 vblock_data, vblock_size,
315 kblob_data, kblob_size);
Bill Richardson5f2696d2014-09-23 22:03:56 -0700316 } else {
Bill Richardsonb406c102014-12-03 14:10:13 -0800317 /* If we're modifying an existing file, it's mmap'ed so that
318 * all our modifications to the buffer will get flushed to
319 * disk when we close it. */
Bill Richardson5f2696d2014-09-23 22:03:56 -0700320 Memcpy(kpart_data, vblock_data, vblock_size);
321 }
322
323 free(vblock_data);
324 return rv;
325}
326
327
328int futil_cb_sign_raw_firmware(struct futil_traverse_state_s *state)
329{
Bill Richardsonc540f592014-09-23 22:17:02 -0700330 VbSignature *body_sig;
331 VbFirmwarePreambleHeader *preamble;
332 int rv;
333
334 body_sig = CalculateSignature(state->my_area->buf, state->my_area->len,
335 option.signprivate);
336 if (!body_sig) {
337 fprintf(stderr, "Error calculating body signature\n");
338 return 1;
339 }
340
341 preamble = CreateFirmwarePreamble(option.version,
342 option.kernel_subkey,
343 body_sig,
344 option.signprivate,
345 option.flags);
346 if (!preamble) {
347 fprintf(stderr, "Error creating firmware preamble.\n");
348 free(body_sig);
349 return 1;
350 }
351
352 rv = WriteSomeParts(option.outfile,
353 option.keyblock, option.keyblock->key_block_size,
354 preamble, preamble->preamble_size);
355
356 free(preamble);
357 free(body_sig);
358
359 return rv;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700360}
361
362
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700363int futil_cb_sign_begin(struct futil_traverse_state_s *state)
364{
365 if (state->in_type == FILE_TYPE_UNKNOWN) {
366 fprintf(stderr, "Unable to determine type of %s\n",
367 state->in_filename);
368 return 1;
369 }
370
371 return 0;
372}
373
374static int write_new_preamble(struct cb_area_s *vblock,
375 struct cb_area_s *fw_body,
376 VbPrivateKey *signkey,
377 VbKeyBlockHeader *keyblock)
378{
379 VbSignature *body_sig;
380 VbFirmwarePreambleHeader *preamble;
381
382 body_sig = CalculateSignature(fw_body->buf, fw_body->len, signkey);
383 if (!body_sig) {
384 fprintf(stderr, "Error calculating body signature\n");
385 return 1;
386 }
387
388 preamble = CreateFirmwarePreamble(option.version,
389 option.kernel_subkey,
390 body_sig,
391 signkey,
392 option.flags);
393 if (!preamble) {
394 fprintf(stderr, "Error creating firmware preamble.\n");
395 free(body_sig);
396 return 1;
397 }
398
399 /* Write the new keyblock */
400 uint32_t more = keyblock->key_block_size;
401 memcpy(vblock->buf, keyblock, more);
402 /* and the new preamble */
403 memcpy(vblock->buf + more, preamble, preamble->preamble_size);
404
405 free(preamble);
406 free(body_sig);
407
408 return 0;
409}
410
411static int write_loem(const char *ab, struct cb_area_s *vblock)
412{
413 char filename[PATH_MAX];
414 int n;
415 n = snprintf(filename, sizeof(filename), "%s/vblock_%s.%s",
416 option.loemdir ? option.loemdir : ".",
417 ab, option.loemid);
418 if (n >= sizeof(filename)) {
419 fprintf(stderr, "LOEM args produce bogus filename\n");
420 return 1;
421 }
422
423 FILE *fp = fopen(filename, "w");
424 if (!fp) {
425 fprintf(stderr, "Can't open %s for writing: %s\n",
426 filename, strerror(errno));
427 return 1;
428 }
429
430 if (1 != fwrite(vblock->buf, vblock->len, 1, fp)) {
431 fprintf(stderr, "Can't write to %s: %s\n",
432 filename, strerror(errno));
433 fclose(fp);
434 return 1;
435 }
436 if (fclose(fp)) {
437 fprintf(stderr, "Failed closing loem output: %s\n",
438 strerror(errno));
439 return 1;
440 }
441
442 return 0;
443}
444
Bill Richardson5f2696d2014-09-23 22:03:56 -0700445/* This signs a full BIOS image after it's been traversed. */
446static int sign_bios_at_end(struct futil_traverse_state_s *state)
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700447{
448 struct cb_area_s *vblock_a = &state->cb_area[CB_FMAP_VBLOCK_A];
449 struct cb_area_s *vblock_b = &state->cb_area[CB_FMAP_VBLOCK_B];
450 struct cb_area_s *fw_a = &state->cb_area[CB_FMAP_FW_MAIN_A];
451 struct cb_area_s *fw_b = &state->cb_area[CB_FMAP_FW_MAIN_B];
452 int retval = 0;
453
454 if (state->errors ||
455 !(vblock_a->_flags & AREA_IS_VALID) ||
456 !(vblock_b->_flags & AREA_IS_VALID) ||
457 !(fw_a->_flags & AREA_IS_VALID) ||
458 !(fw_b->_flags & AREA_IS_VALID)) {
459 fprintf(stderr, "Something's wrong. Not changing anything\n");
460 return 1;
461 }
462
463 /* Do A & B differ ? */
464 if (fw_a->len != fw_b->len ||
465 memcmp(fw_a->buf, fw_b->buf, fw_a->len)) {
466 /* Yes, must use DEV keys for A */
467 if (!option.devsignprivate || !option.devkeyblock) {
468 fprintf(stderr,
469 "FW A & B differ. DEV keys are required.\n");
470 return 1;
471 }
472 retval |= write_new_preamble(vblock_a, fw_a,
473 option.devsignprivate,
474 option.devkeyblock);
475 } else {
476 retval |= write_new_preamble(vblock_a, fw_a,
477 option.signprivate,
478 option.keyblock);
479 }
480
481 /* FW B is always normal keys */
482 retval |= write_new_preamble(vblock_b, fw_b,
483 option.signprivate,
484 option.keyblock);
485
486
487
488
489 if (option.loemid) {
490 retval |= write_loem("A", vblock_a);
491 retval |= write_loem("B", vblock_b);
492 }
493
494 return retval;
495}
496
Bill Richardson5f2696d2014-09-23 22:03:56 -0700497int futil_cb_sign_end(struct futil_traverse_state_s *state)
498{
499 switch (state->in_type) {
500 case FILE_TYPE_BIOS_IMAGE:
501 case FILE_TYPE_OLD_BIOS_IMAGE:
502 return sign_bios_at_end(state);
503
504 default:
505 /* Any other cleanup needed? */
506 break;
507 }
508
509 return state->errors;
510}
511
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700512static const char usage[] = "\n"
Bill Richardson5f2696d2014-09-23 22:03:56 -0700513 "Usage: " MYNAME " %s [PARAMS] INFILE [OUTFILE]\n"
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700514 "\n"
Bill Richardson5f2696d2014-09-23 22:03:56 -0700515 "Where INFILE is a\n"
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700516 "\n"
Bill Richardsonc540f592014-09-23 22:17:02 -0700517 " public key (.vbpubk); OUTFILE is a keyblock\n"
518 " raw firmware blob (FW_MAIN_A/B); OUTFILE is a VBLOCK_A/B\n"
Bill Richardson5f2696d2014-09-23 22:03:56 -0700519 " complete firmware image (bios.bin)\n"
520 " raw linux kernel; OUTFILE is a kernel partition image\n"
521 " kernel partition image (/dev/sda2, /dev/mmcblk0p2)\n";
522
Bill Richardsonc540f592014-09-23 22:17:02 -0700523static const char usage_pubkey[] = "\n"
524 "-----------------------------------------------------------------\n"
525 "To sign a public key / create a new keyblock:\n"
526 "\n"
527 "Required PARAMS:\n"
528 " [--datapubkey] INFILE The public key to wrap\n"
529 " [--outfile] OUTFILE The resulting keyblock\n"
530 "\n"
531 "Optional PARAMS:\n"
532 " A private signing key, specified as either\n"
533 " -s|--signprivate FILE.vbprivk Signing key in .vbprivk format\n"
534 " Or\n"
535 " --pem_signpriv FILE.pem Signing key in PEM format...\n"
536 " --pem_algo NUM AND the algorithm to use (0 - %d)\n"
537 "\n"
538 " If a signing key is not given, the keyblock will not be signed (duh)."
539 "\n\n"
540 "And these, too:\n\n"
541 " -f|--flags NUM Flags specifying use conditions\n"
542 " --pem_external PROGRAM"
543 " External program to compute the signature\n"
544 " (requires a PEM signing key)\n";
545
546static const char usage_fw_main[] = "\n"
547 "-----------------------------------------------------------------\n"
548 "To sign a raw firmware blob (FW_MAIN_A/B):\n"
549 "\n"
550 "Required PARAMS:\n"
551 " -s|--signprivate FILE.vbprivk The private firmware data key\n"
552 " -b|--keyblock FILE.keyblock The keyblock containing the\n"
553 " public firmware data key\n"
554 " -k|--kernelkey FILE.vbpubk The public kernel subkey\n"
555 " -v|--version NUM The firmware version number\n"
556 " [--fv] INFILE"
557 " The raw firmware blob (FW_MAIN_A/B)\n"
558 " [--outfile] OUTFILE Output VBLOCK_A/B\n"
559 "\n"
560 "Optional PARAMS:\n"
561 " -f|--flags NUM The preamble flags value"
562 " (default is 0)\n";
563
Bill Richardson5f2696d2014-09-23 22:03:56 -0700564static const char usage_bios[] = "\n"
565 "-----------------------------------------------------------------\n"
566 "To sign a complete firmware image (bios.bin):\n"
567 "\n"
568 "Required PARAMS:\n"
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700569 " -s|--signprivate FILE.vbprivk The private firmware data key\n"
570 " -b|--keyblock FILE.keyblock The keyblock containing the\n"
571 " public firmware data key\n"
572 " -k|--kernelkey FILE.vbpubk The public kernel subkey\n"
Bill Richardson5f2696d2014-09-23 22:03:56 -0700573 " [--infile] INFILE Input firmware image (modified\n"
574 " in place if no OUTFILE given)\n"
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700575 "\n"
576 "These are required if the A and B firmware differ:\n"
577 " -S|--devsign FILE.vbprivk The DEV private firmware data key\n"
578 " -B|--devkeyblock FILE.keyblock The keyblock containing the\n"
579 " DEV public firmware data key\n"
580 "\n"
Bill Richardson5f2696d2014-09-23 22:03:56 -0700581 "Optional PARAMS:\n"
Bill Richardson08efd1e2014-09-04 22:53:41 -0700582 " -v|--version NUM The firmware version number"
583 " (default %d)\n"
584 " -f|--flags NUM The preamble flags value"
585 " (default is\n"
586 " unchanged, or 0 if unknown)\n"
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700587 " -d|--loemdir DIR Local OEM output vblock directory\n"
588 " -l|--loemid STRING Local OEM vblock suffix\n"
Bill Richardson5f2696d2014-09-23 22:03:56 -0700589 " [--outfile] OUTFILE Output firmware image\n";
590
591static const char usage_new_kpart[] = "\n"
592 "-----------------------------------------------------------------\n"
593 "To create a new kernel parition image (/dev/sda2, /dev/mmcblk0p2):\n"
594 "\n"
595 "Required PARAMS:\n"
596 " -s|--signprivate FILE.vbprivk"
597 " The private key to sign the kernel blob\n"
598 " -b|--keyblock FILE.keyblock The keyblock containing the public\n"
599 " key to verify the kernel blob\n"
600 " -v|--version NUM The kernel version number\n"
601 " --bootloader FILE Bootloader stub\n"
602 " --config FILE The kernel commandline file\n"
603 " --arch ARCH The CPU architecture (one of\n"
604 " x86|amd64, arm|aarch64, mips)\n"
605 " [--vmlinuz] INFILE Linux kernel bzImage file\n"
606 " [--outfile] OUTFILE Output kernel partition or vblock\n"
607 "\n"
608 "Optional PARAMS:\n"
609 " --kloadaddr NUM"
610 " RAM address to load the kernel body\n"
611 " (default 0x%x)\n"
612 " --pad NUM The vblock padding size in bytes\n"
613 " (default 0x%x)\n"
614 " --vblockonly Emit just the vblock (requires a\n"
615 " distinct outfile)\n";
616
617static const char usage_old_kpart[] = "\n"
618 "-----------------------------------------------------------------\n"
619 "To resign an existing kernel parition (/dev/sda2, /dev/mmcblk0p2):\n"
620 "\n"
621 "Required PARAMS:\n"
622 " -s|--signprivate FILE.vbprivk"
623 " The private key to sign the kernel blob\n"
624 " [--infile] INFILE Input kernel partition (modified\n"
625 " in place if no OUTFILE given)\n"
626 "\n"
627 "Optional PARAMS:\n"
628 " -b|--keyblock FILE.keyblock The keyblock containing the public\n"
629 " key to verify the kernel blob\n"
630 " -v|--version NUM The kernel version number\n"
631 " --config FILE The kernel commandline file\n"
632 " --pad NUM The vblock padding size in bytes\n"
633 " (default 0x%x)\n"
634 " [--outfile] OUTFILE Output kernel partition or vblock\n"
635 " --vblockonly Emit just the vblock (requires a\n"
636 " distinct OUTFILE)\n"
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700637 "\n";
638
Bill Richardson779796f2014-09-23 11:47:40 -0700639static void print_help(const char *prog)
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700640{
Bill Richardson5f2696d2014-09-23 22:03:56 -0700641 printf(usage, prog);
Bill Richardsonc540f592014-09-23 22:17:02 -0700642 printf(usage_pubkey, kNumAlgorithms - 1);
643 puts(usage_fw_main);
Bill Richardson5f2696d2014-09-23 22:03:56 -0700644 printf(usage_bios, option.version);
645 printf(usage_new_kpart, option.kloadaddr, option.padding);
646 printf(usage_old_kpart, option.padding);
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700647}
648
Bill Richardson5f2696d2014-09-23 22:03:56 -0700649enum no_short_opts {
650 OPT_FV = 1000,
651 OPT_INFILE, /* aka "--vmlinuz" */
652 OPT_OUTFILE,
653 OPT_BOOTLOADER,
654 OPT_CONFIG,
655 OPT_ARCH,
656 OPT_KLOADADDR,
657 OPT_PADDING,
Bill Richardsonc540f592014-09-23 22:17:02 -0700658 OPT_PEM_SIGNPRIV,
659 OPT_PEM_ALGO,
660 OPT_PEM_EXTERNAL,
Bill Richardson5f2696d2014-09-23 22:03:56 -0700661};
662
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700663static const struct option long_opts[] = {
664 /* name hasarg *flag val */
Bill Richardson5f2696d2014-09-23 22:03:56 -0700665 {"signprivate", 1, NULL, 's'},
666 {"keyblock", 1, NULL, 'b'},
667 {"kernelkey", 1, NULL, 'k'},
668 {"devsign", 1, NULL, 'S'},
669 {"devkeyblock", 1, NULL, 'B'},
670 {"version", 1, NULL, 'v'},
671 {"flags", 1, NULL, 'f'},
672 {"loemdir", 1, NULL, 'd'},
673 {"loemid", 1, NULL, 'l'},
674 {"fv", 1, NULL, OPT_FV},
675 {"infile", 1, NULL, OPT_INFILE},
676 {"datapubkey", 1, NULL, OPT_INFILE}, /* alias */
677 {"vmlinuz", 1, NULL, OPT_INFILE}, /* alias */
678 {"outfile", 1, NULL, OPT_OUTFILE},
679 {"bootloader", 1, NULL, OPT_BOOTLOADER},
680 {"config", 1, NULL, OPT_CONFIG},
681 {"arch", 1, NULL, OPT_ARCH},
682 {"kloadaddr", 1, NULL, OPT_KLOADADDR},
683 {"pad", 1, NULL, OPT_PADDING},
Bill Richardsonc540f592014-09-23 22:17:02 -0700684 {"pem_signpriv", 1, NULL, OPT_PEM_SIGNPRIV},
685 {"pem_algo", 1, NULL, OPT_PEM_ALGO},
686 {"pem_external", 1, NULL, OPT_PEM_EXTERNAL},
Bill Richardson5f2696d2014-09-23 22:03:56 -0700687 {"vblockonly", 0, &option.vblockonly, 1},
688 {"debug", 0, &debugging_enabled, 1},
689 {NULL, 0, NULL, 0},
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700690};
691static char *short_opts = ":s:b:k:S:B:v:f:d:l:";
692
693static int do_sign(int argc, char *argv[])
694{
695 char *infile = 0;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700696 int i;
697 int ifd = -1;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700698 int errorcnt = 0;
699 struct futil_traverse_state_s state;
Bill Richardsonb0f1cc52014-09-24 00:23:56 -0700700 uint8_t *buf;
701 uint32_t buf_len;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700702 char *e = 0;
703 enum futil_file_type type;
704 int inout_file_count = 0;
705 int mapping;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700706
707 opterr = 0; /* quiet, you */
708 while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) {
709 switch (i) {
710 case 's':
711 option.signprivate = PrivateKeyRead(optarg);
712 if (!option.signprivate) {
713 fprintf(stderr, "Error reading %s\n", optarg);
714 errorcnt++;
715 }
716 break;
717 case 'b':
718 option.keyblock = KeyBlockRead(optarg);
719 if (!option.keyblock) {
720 fprintf(stderr, "Error reading %s\n", optarg);
721 errorcnt++;
722 }
723 break;
724 case 'k':
725 option.kernel_subkey = PublicKeyRead(optarg);
726 if (!option.kernel_subkey) {
727 fprintf(stderr, "Error reading %s\n", optarg);
728 errorcnt++;
729 }
730 break;
731 case 'S':
732 option.devsignprivate = PrivateKeyRead(optarg);
733 if (!option.devsignprivate) {
734 fprintf(stderr, "Error reading %s\n", optarg);
735 errorcnt++;
736 }
737 break;
738 case 'B':
739 option.devkeyblock = KeyBlockRead(optarg);
740 if (!option.devkeyblock) {
741 fprintf(stderr, "Error reading %s\n", optarg);
742 errorcnt++;
743 }
744 break;
745 case 'v':
Bill Richardson5f2696d2014-09-23 22:03:56 -0700746 option.version_specified = 1;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700747 option.version = strtoul(optarg, &e, 0);
748 if (!*optarg || (e && *e)) {
749 fprintf(stderr,
750 "Invalid --version \"%s\"\n", optarg);
751 errorcnt++;
752 }
753 break;
754
755 case 'f':
Bill Richardsona19b00d2014-09-04 23:20:43 -0700756 option.flags_specified = 1;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700757 option.flags = strtoul(optarg, &e, 0);
758 if (!*optarg || (e && *e)) {
759 fprintf(stderr,
760 "Invalid --flags \"%s\"\n", optarg);
761 errorcnt++;
762 }
Bill Richardsona19b00d2014-09-04 23:20:43 -0700763 break;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700764 case 'd':
765 option.loemdir = optarg;
766 break;
767 case 'l':
768 option.loemid = optarg;
769 break;
Bill Richardsonc540f592014-09-23 22:17:02 -0700770 case OPT_FV:
771 option.fv_specified = 1;
772 /* fallthrough */
Bill Richardson5f2696d2014-09-23 22:03:56 -0700773 case OPT_INFILE: /* aka "--vmlinuz" */
774 inout_file_count++;
775 infile = optarg;
776 break;
777 case OPT_OUTFILE:
778 inout_file_count++;
779 option.outfile = optarg;
780 break;
781 case OPT_BOOTLOADER:
782 option.bootloader_data = ReadFile(
783 optarg, &option.bootloader_size);
784 if (!option.bootloader_data) {
785 fprintf(stderr,
786 "Error reading bootloader file: %s\n",
787 strerror(errno));
788 errorcnt++;
789 }
790 Debug("bootloader file size=0x%" PRIx64 "\n",
791 option.bootloader_size);
792 break;
793 case OPT_CONFIG:
794 option.config_data = ReadConfigFile(
795 optarg, &option.config_size);
796 if (!option.config_data) {
797 fprintf(stderr,
798 "Error reading config file: %s\n",
799 strerror(errno));
800 errorcnt++;
801 }
802 break;
803 case OPT_ARCH:
804 /* check the first 3 characters to also match x86_64 */
805 if ((!strncasecmp(optarg, "x86", 3)) ||
806 (!strcasecmp(optarg, "amd64")))
807 option.arch = ARCH_X86;
808 else if ((!strcasecmp(optarg, "arm")) ||
809 (!strcasecmp(optarg, "aarch64")))
810 option.arch = ARCH_ARM;
811 else if (!strcasecmp(optarg, "mips"))
812 option.arch = ARCH_MIPS;
813 else {
814 fprintf(stderr,
815 "Unknown architecture: \"%s\"\n",
816 optarg);
817 errorcnt++;
818 }
819 break;
820 case OPT_KLOADADDR:
821 option.kloadaddr = strtoul(optarg, &e, 0);
822 if (!*optarg || (e && *e)) {
823 fprintf(stderr,
824 "Invalid --kloadaddr \"%s\"\n", optarg);
825 errorcnt++;
826 }
827 break;
828 case OPT_PADDING:
829 option.padding = strtoul(optarg, &e, 0);
830 if (!*optarg || (e && *e)) {
831 fprintf(stderr,
832 "Invalid --padding \"%s\"\n", optarg);
833 errorcnt++;
834 }
835 break;
Bill Richardsonc540f592014-09-23 22:17:02 -0700836 case OPT_PEM_SIGNPRIV:
837 option.pem_signpriv = optarg;
838 break;
839 case OPT_PEM_ALGO:
840 option.pem_algo_specified = 1;
841 option.pem_algo = strtoul(optarg, &e, 0);
842 if (!*optarg || (e && *e) ||
843 (option.pem_algo >= kNumAlgorithms)) {
844 fprintf(stderr,
845 "Invalid --pem_algo \"%s\"\n", optarg);
846 errorcnt++;
847 }
848 break;
849 case OPT_PEM_EXTERNAL:
850 option.pem_external = optarg;
851 break;
852
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700853 case '?':
854 if (optopt)
855 fprintf(stderr, "Unrecognized option: -%c\n",
856 optopt);
857 else
Bill Richardson5f2696d2014-09-23 22:03:56 -0700858 fprintf(stderr, "Unrecognized option: %s\n",
859 argv[optind - 1]);
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700860 errorcnt++;
861 break;
862 case ':':
863 fprintf(stderr, "Missing argument to -%c\n", optopt);
864 errorcnt++;
865 break;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700866 case 0: /* handled option */
867 break;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700868 default:
Bill Richardson5f2696d2014-09-23 22:03:56 -0700869 Debug("i=%d\n", i);
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700870 DIE;
871 }
872 }
873
Bill Richardson5f2696d2014-09-23 22:03:56 -0700874 /* If we don't have an input file already, we need one */
875 if (!infile) {
876 if (argc - optind <= 0) {
877 errorcnt++;
878 fprintf(stderr, "ERROR: missing input filename\n");
879 goto done;
880 } else {
881 inout_file_count++;
882 infile = argv[optind++];
883 }
884 }
885
Bill Richardsonb406c102014-12-03 14:10:13 -0800886 /* Look for an output file if we don't have one, just in case. */
887 if (!option.outfile && argc - optind > 0) {
888 inout_file_count++;
889 option.outfile = argv[optind++];
890 }
891
Bill Richardson5f2696d2014-09-23 22:03:56 -0700892 /* What are we looking at? */
Bill Richardson25593382015-01-30 12:22:28 -0800893 if (futil_file_type(infile, &type)) {
894 errorcnt++;
895 goto done;
896 }
Bill Richardson5f2696d2014-09-23 22:03:56 -0700897
898 /* We may be able to infer the type based on the other args */
899 if (type == FILE_TYPE_UNKNOWN) {
900 if (option.bootloader_data || option.config_data
901 || option.arch != ARCH_UNSPECIFIED)
902 type = FILE_TYPE_RAW_KERNEL;
Bill Richardsonc540f592014-09-23 22:17:02 -0700903 else if (option.kernel_subkey || option.fv_specified)
904 type = FILE_TYPE_RAW_FIRMWARE;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700905 }
906
Bill Richardson25593382015-01-30 12:22:28 -0800907 Debug("type=%s\n", futil_file_type_str(type));
Bill Richardsonb406c102014-12-03 14:10:13 -0800908
Bill Richardson5f2696d2014-09-23 22:03:56 -0700909 /* Check the arguments for the type of thing we want to sign */
910 switch (type) {
911 case FILE_TYPE_UNKNOWN:
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700912 fprintf(stderr,
Bill Richardson5f2696d2014-09-23 22:03:56 -0700913 "Unable to determine the type of the input file\n");
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700914 errorcnt++;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700915 goto done;
Bill Richardsonc540f592014-09-23 22:17:02 -0700916 case FILE_TYPE_PUBKEY:
917 option.create_new_outfile = 1;
918 if (option.signprivate && option.pem_signpriv) {
919 fprintf(stderr,
920 "Only one of --signprivate and --pem_signpriv"
921 " can be specified\n");
922 errorcnt++;
923 }
924 if ((option.signprivate && option.pem_algo_specified) ||
925 (option.pem_signpriv && !option.pem_algo_specified)) {
926 fprintf(stderr, "--pem_algo must be used with"
927 " --pem_signpriv\n");
928 errorcnt++;
929 }
930 if (option.pem_external && !option.pem_signpriv) {
931 fprintf(stderr, "--pem_external must be used with"
932 " --pem_signpriv\n");
933 errorcnt++;
934 }
935 /* We'll wait to read the PEM file, since the external signer
936 * may want to read it instead. */
937 break;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700938 case FILE_TYPE_KEYBLOCK:
939 fprintf(stderr, "Resigning a keyblock is kind of pointless.\n");
940 fprintf(stderr, "Just create a new one.\n");
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700941 errorcnt++;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700942 break;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700943 case FILE_TYPE_FW_PREAMBLE:
944 fprintf(stderr,
945 "%s IS a signature. Sign the firmware instead\n",
946 infile);
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700947 break;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700948 case FILE_TYPE_GBB:
949 fprintf(stderr, "There's no way to sign a GBB\n");
950 errorcnt++;
951 break;
952 case FILE_TYPE_BIOS_IMAGE:
953 case FILE_TYPE_OLD_BIOS_IMAGE:
954 errorcnt += no_opt_if(!option.signprivate, "signprivate");
955 errorcnt += no_opt_if(!option.keyblock, "keyblock");
956 errorcnt += no_opt_if(!option.kernel_subkey, "kernelkey");
957 break;
958 case FILE_TYPE_KERN_PREAMBLE:
959 errorcnt += no_opt_if(!option.signprivate, "signprivate");
Bill Richardsonb406c102014-12-03 14:10:13 -0800960 if (option.vblockonly || inout_file_count > 1)
Bill Richardson5f2696d2014-09-23 22:03:56 -0700961 option.create_new_outfile = 1;
962 break;
Bill Richardsonc540f592014-09-23 22:17:02 -0700963 case FILE_TYPE_RAW_FIRMWARE:
964 option.create_new_outfile = 1;
965 errorcnt += no_opt_if(!option.signprivate, "signprivate");
966 errorcnt += no_opt_if(!option.keyblock, "keyblock");
967 errorcnt += no_opt_if(!option.kernel_subkey, "kernelkey");
968 errorcnt += no_opt_if(!option.version_specified, "version");
969 break;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700970 case FILE_TYPE_RAW_KERNEL:
971 option.create_new_outfile = 1;
972 errorcnt += no_opt_if(!option.signprivate, "signprivate");
973 errorcnt += no_opt_if(!option.keyblock, "keyblock");
974 errorcnt += no_opt_if(!option.version_specified, "version");
975 errorcnt += no_opt_if(!option.bootloader_data, "bootloader");
976 errorcnt += no_opt_if(!option.config_data, "config");
977 errorcnt += no_opt_if(option.arch == ARCH_UNSPECIFIED, "arch");
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700978 break;
Bill Richardson25593382015-01-30 12:22:28 -0800979 case FILE_TYPE_CHROMIUMOS_DISK:
980 fprintf(stderr, "Signing a %s is not yet supported\n",
981 futil_file_type_str(type));
982 errorcnt++;
983 break;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700984 default:
Bill Richardson5f2696d2014-09-23 22:03:56 -0700985 DIE;
Bill Richardson15dc6fc2014-09-02 14:45:44 -0700986 }
987
Bill Richardsonb406c102014-12-03 14:10:13 -0800988 Debug("infile=%s\n", infile);
989 Debug("inout_file_count=%d\n", inout_file_count);
990 Debug("option.create_new_outfile=%d\n", option.create_new_outfile);
991
992 /* Make sure we have an output file if one is needed */
Bill Richardson5f2696d2014-09-23 22:03:56 -0700993 if (!option.outfile) {
Bill Richardsonb406c102014-12-03 14:10:13 -0800994 if (option.create_new_outfile) {
995 errorcnt++;
996 fprintf(stderr, "Missing output filename\n");
997 goto done;
Bill Richardson5f2696d2014-09-23 22:03:56 -0700998 } else {
Bill Richardsonb406c102014-12-03 14:10:13 -0800999 option.outfile = infile;
Bill Richardson5f2696d2014-09-23 22:03:56 -07001000 }
Bill Richardson15dc6fc2014-09-02 14:45:44 -07001001 }
1002
Bill Richardson5f2696d2014-09-23 22:03:56 -07001003 Debug("option.outfile=%s\n", option.outfile);
1004
1005 if (argc - optind > 0) {
Bill Richardsonb0f1cc52014-09-24 00:23:56 -07001006 errorcnt++;
Bill Richardson5f2696d2014-09-23 22:03:56 -07001007 fprintf(stderr, "ERROR: too many arguments left over\n");
Bill Richardsonb0f1cc52014-09-24 00:23:56 -07001008 }
1009
Bill Richardson5f2696d2014-09-23 22:03:56 -07001010 if (errorcnt)
1011 goto done;
1012
Bill Richardson15dc6fc2014-09-02 14:45:44 -07001013 memset(&state, 0, sizeof(state));
Bill Richardson15dc6fc2014-09-02 14:45:44 -07001014 state.op = FUTIL_OP_SIGN;
1015
Bill Richardson5f2696d2014-09-23 22:03:56 -07001016 if (option.create_new_outfile) {
1017 /* The input is read-only, the output is write-only. */
1018 mapping = MAP_RO;
1019 state.in_filename = infile;
Bill Richardsonb406c102014-12-03 14:10:13 -08001020 Debug("open RO %s\n", infile);
Bill Richardson5f2696d2014-09-23 22:03:56 -07001021 ifd = open(infile, O_RDONLY);
1022 if (ifd < 0) {
1023 errorcnt++;
1024 fprintf(stderr, "Can't open %s for reading: %s\n",
1025 infile, strerror(errno));
1026 goto done;
1027 }
1028 } else {
1029 /* We'll read-modify-write the output file */
1030 mapping = MAP_RW;
1031 state.in_filename = option.outfile;
1032 if (inout_file_count > 1)
1033 futil_copy_file_or_die(infile, option.outfile);
Bill Richardsonb406c102014-12-03 14:10:13 -08001034 Debug("open RW %s\n", option.outfile);
Bill Richardson5f2696d2014-09-23 22:03:56 -07001035 ifd = open(option.outfile, O_RDWR);
1036 if (ifd < 0) {
1037 errorcnt++;
1038 fprintf(stderr, "Can't open %s for writing: %s\n",
1039 option.outfile, strerror(errno));
1040 goto done;
1041 }
1042 }
Bill Richardson15dc6fc2014-09-02 14:45:44 -07001043
Bill Richardson5f2696d2014-09-23 22:03:56 -07001044 if (0 != futil_map_file(ifd, mapping, &buf, &buf_len)) {
Bill Richardson15dc6fc2014-09-02 14:45:44 -07001045 errorcnt++;
Bill Richardson5f2696d2014-09-23 22:03:56 -07001046 goto done;
1047 }
1048
1049 errorcnt += futil_traverse(buf, buf_len, &state, type);
1050
1051 errorcnt += futil_unmap_file(ifd, MAP_RW, buf, buf_len);
1052
1053done:
1054 if (ifd >= 0 && close(ifd)) {
1055 errorcnt++;
1056 fprintf(stderr, "Error when closing ifd: %s\n",
1057 strerror(errno));
Bill Richardson15dc6fc2014-09-02 14:45:44 -07001058 }
1059
1060 if (option.signprivate)
1061 free(option.signprivate);
1062 if (option.keyblock)
1063 free(option.keyblock);
1064 if (option.kernel_subkey)
1065 free(option.kernel_subkey);
1066
Bill Richardson5f2696d2014-09-23 22:03:56 -07001067 if (errorcnt)
1068 fprintf(stderr, "Use --help for usage instructions\n");
1069
Bill Richardson15dc6fc2014-09-02 14:45:44 -07001070 return !!errorcnt;
1071}
1072
Bill Richardson779796f2014-09-23 11:47:40 -07001073DECLARE_FUTIL_COMMAND(sign, do_sign,
Bill Richardson5f2696d2014-09-23 22:03:56 -07001074 "Sign / resign various binary components",
Bill Richardson779796f2014-09-23 11:47:40 -07001075 print_help);