| /* Copyright 2012 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * Verified boot kernel utility |
| */ |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <inttypes.h> /* For PRIu64 */ |
| #include <sys/ioctl.h> |
| #include <linux/fs.h> /* For BLKGETSIZE64 */ |
| #include <stdarg.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "cryptolib.h" |
| #include "futility.h" |
| #include "host_common.h" |
| #include "kernel_blob.h" |
| #include "util_misc.h" |
| #include "vboot_common.h" |
| |
| /* Global opts */ |
| static int opt_verbose; |
| static int opt_vblockonly; |
| static uint64_t opt_pad = 65536; |
| |
| /* Command line options */ |
| enum { |
| OPT_MODE_PACK = 1000, |
| OPT_MODE_REPACK, |
| OPT_MODE_VERIFY, |
| OPT_ARCH, |
| OPT_OLDBLOB, |
| OPT_KLOADADDR, |
| OPT_KEYBLOCK, |
| OPT_SIGNPUBKEY, |
| OPT_SIGNPRIVATE, |
| OPT_VERSION, |
| OPT_VMLINUZ, |
| OPT_BOOTLOADER, |
| OPT_CONFIG, |
| OPT_VBLOCKONLY, |
| OPT_PAD, |
| OPT_VERBOSE, |
| OPT_MINVERSION, |
| }; |
| |
| static const struct option long_opts[] = { |
| {"pack", 1, 0, OPT_MODE_PACK}, |
| {"repack", 1, 0, OPT_MODE_REPACK}, |
| {"verify", 1, 0, OPT_MODE_VERIFY}, |
| {"arch", 1, 0, OPT_ARCH}, |
| {"oldblob", 1, 0, OPT_OLDBLOB}, |
| {"kloadaddr", 1, 0, OPT_KLOADADDR}, |
| {"keyblock", 1, 0, OPT_KEYBLOCK}, |
| {"signpubkey", 1, 0, OPT_SIGNPUBKEY}, |
| {"signprivate", 1, 0, OPT_SIGNPRIVATE}, |
| {"version", 1, 0, OPT_VERSION}, |
| {"minversion", 1, 0, OPT_MINVERSION}, |
| {"vmlinuz", 1, 0, OPT_VMLINUZ}, |
| {"bootloader", 1, 0, OPT_BOOTLOADER}, |
| {"config", 1, 0, OPT_CONFIG}, |
| {"vblockonly", 0, 0, OPT_VBLOCKONLY}, |
| {"pad", 1, 0, OPT_PAD}, |
| {"verbose", 0, &opt_verbose, 1}, |
| {"debug", 0, &debugging_enabled, 1}, |
| {NULL, 0, 0, 0} |
| }; |
| |
| |
| |
| static const char usage[] = |
| "\n" |
| "Usage: " MYNAME " %s --pack <file> [PARAMETERS]\n" |
| "\n" |
| " Required parameters:\n" |
| " --keyblock <file> Key block in .keyblock format\n" |
| " --signprivate <file> Private key to sign kernel data,\n" |
| " in .vbprivk format\n" |
| " --version <number> Kernel version\n" |
| " --vmlinuz <file> Linux kernel bzImage file\n" |
| " --bootloader <file> Bootloader stub\n" |
| " --config <file> Command line file\n" |
| " --arch <arch> Cpu architecture (default x86)\n" |
| "\n" |
| " Optional:\n" |
| " --kloadaddr <address> Assign kernel body load address\n" |
| " --pad <number> Verification padding size in bytes\n" |
| " --vblockonly Emit just the verification blob\n" |
| "\nOR\n\n" |
| "Usage: " MYNAME " %s --repack <file> [PARAMETERS]\n" |
| "\n" |
| " Required parameters:\n" |
| " --signprivate <file> Private key to sign kernel data,\n" |
| " in .vbprivk format\n" |
| " --oldblob <file> Previously packed kernel blob\n" |
| " (including verfication blob)\n" |
| "\n" |
| " Optional:\n" |
| " --keyblock <file> Key block in .keyblock format\n" |
| " --config <file> New command line file\n" |
| " --version <number> Kernel version\n" |
| " --kloadaddr <address> Assign kernel body load address\n" |
| " --pad <number> Verification blob size in bytes\n" |
| " --vblockonly Emit just the verification blob\n" |
| "\nOR\n\n" |
| "Usage: " MYNAME " %s --verify <file> [PARAMETERS]\n" |
| "\n" |
| " Optional:\n" |
| " --signpubkey <file>" |
| " Public key to verify kernel keyblock,\n" |
| " in .vbpubk format\n" |
| " --verbose Print a more detailed report\n" |
| " --keyblock <file> Outputs the verified key block,\n" |
| " in .keyblock format\n" |
| " --pad <number> Verification padding size in bytes\n" |
| " --minversion <number> Minimum combined kernel key version\n" |
| " and kernel version\n" |
| "\n"; |
| |
| |
| /* Print help and return error */ |
| static void print_help(const char *progname) |
| { |
| printf(usage, progname, progname, progname); |
| } |
| |
| static void Fatal(const char *format, ...) |
| { |
| va_list ap; |
| va_start(ap, format); |
| fprintf(stderr, "ERROR: "); |
| vfprintf(stderr, format, ap); |
| va_end(ap); |
| exit(1); |
| } |
| |
| /* Return an explanation when fread() fails. */ |
| static const char *error_fread(FILE *fp) |
| { |
| const char *retval = "beats me why"; |
| if (feof(fp)) |
| retval = "EOF"; |
| else if (ferror(fp)) |
| retval = strerror(errno); |
| clearerr(fp); |
| return retval; |
| } |
| |
| /* Return the smallest integral multiple of [alignment] that is equal |
| * to or greater than [val]. Used to determine the number of |
| * pages/sectors/blocks/whatever needed to contain [val] |
| * items/bytes/etc. */ |
| static uint64_t roundup(uint64_t val, uint64_t alignment) |
| { |
| uint64_t rem = val % alignment; |
| if (rem) |
| return val + (alignment - rem); |
| return val; |
| } |
| |
| /* Match regexp /\b--\b/ to delimit the start of the kernel commandline. If we |
| * don't find one, we'll use the whole thing. */ |
| static unsigned int find_cmdline_start(char *input, unsigned int max_len) |
| { |
| int start = 0; |
| int i; |
| for (i = 0; i < max_len - 1 && input[i]; i++) { |
| if ('-' == input[i] && '-' == input[i + 1]) { |
| if ((i == 0 || ' ' == input[i - 1]) && |
| (i + 2 >= max_len || ' ' == input[i + 2])) { |
| /* found "--" with nothing before or after it */ |
| start = i + 2; /* hope for a trailing '\0' */ |
| break; |
| } |
| } |
| } |
| while (' ' == input[start]) /* skip leading spaces */ |
| start++; |
| |
| return start; |
| } |
| |
| /****************************************************************************/ |
| /* Here are globals containing all the bits & pieces I'm working on. */ |
| |
| /* The individual parts that go into the kernel blob */ |
| static uint8_t *g_kernel_data; |
| static uint64_t g_kernel_size; |
| static uint8_t *g_param_data; |
| static uint64_t g_param_size; |
| static uint8_t *g_config_data; |
| static uint64_t g_config_size; |
| static uint8_t *g_bootloader_data; |
| static uint64_t g_bootloader_size; |
| static uint64_t g_bootloader_address; |
| |
| /* The individual parts of the verification blob (including the data that |
| * immediately follows the headers) */ |
| static VbKeyBlockHeader *g_keyblock; |
| static VbKernelPreambleHeader *g_preamble; |
| |
| /****************************************************************************/ |
| |
| /* |
| * Read the kernel command line from a file. Get rid of \n characters along |
| * the way and verify that the line fits into a 4K buffer. |
| * |
| * Return the buffer contaning the line on success (and set the line length |
| * using the passed in parameter), or NULL in case something goes wrong. |
| */ |
| static uint8_t *sReadConfigFile(const char *config_file, uint64_t *config_size) |
| { |
| uint8_t *config_buf; |
| int ii; |
| |
| config_buf = ReadFile(config_file, config_size); |
| Debug(" config file size=0x%" PRIx64 "\n", *config_size); |
| if (CROS_CONFIG_SIZE <= *config_size) { /* room for trailing '\0' */ |
| VbExError("Config file %s is too large (>= %d bytes)\n", |
| config_file, CROS_CONFIG_SIZE); |
| return NULL; |
| } |
| |
| /* Replace newlines with spaces */ |
| for (ii = 0; ii < *config_size; ii++) |
| if ('\n' == config_buf[ii]) |
| config_buf[ii] = ' '; |
| |
| return config_buf; |
| } |
| |
| /* Offset of kernel command line string from start of packed kernel blob */ |
| static uint64_t CmdLineOffset(VbKernelPreambleHeader *preamble) |
| { |
| return preamble->bootloader_address - preamble->body_load_address - |
| CROS_CONFIG_SIZE - CROS_PARAMS_SIZE; |
| } |
| |
| /* This initializes g_vmlinuz and g_param from a standard vmlinuz file. |
| * It returns 0 on error. */ |
| static int ImportVmlinuzFile(const char *vmlinuz_file, enum arch_t arch, |
| uint64_t kernel_body_load_address) |
| { |
| uint8_t *kernel_buf; |
| uint64_t kernel_size; |
| uint64_t kernel32_start = 0; |
| uint64_t kernel32_size = 0; |
| struct linux_kernel_params *params = NULL, *lh = NULL; |
| |
| /* Read the kernel */ |
| Debug("Reading %s\n", vmlinuz_file); |
| kernel_buf = ReadFile(vmlinuz_file, &kernel_size); |
| if (!kernel_buf) |
| return 0; |
| Debug(" kernel file size=0x%" PRIx64 "\n", kernel_size); |
| if (!kernel_size) |
| Fatal("Empty kernel file\n"); |
| |
| /* Go ahead and allocate the param region anyway. I don't think we need |
| * it for non-x86, but let's keep it for now. */ |
| g_param_size = CROS_PARAMS_SIZE; |
| g_param_data = VbExMalloc(g_param_size); |
| Memset(g_param_data, 0, g_param_size); |
| |
| /* Unless we're handling x86, the kernel is the kernel; we're done. */ |
| if (arch != ARCH_X86) { |
| g_kernel_data = kernel_buf; |
| g_kernel_size = kernel_size; |
| return 1; |
| } |
| |
| /* The first part of the x86 vmlinuz is a header, followed by a |
| * real-mode boot stub. We only want the 32-bit part. */ |
| lh = (struct linux_kernel_params *)kernel_buf; |
| kernel32_start = (lh->setup_sects + 1) << 9; |
| if (kernel32_start >= kernel_size) |
| Fatal("Malformed kernel\n"); |
| kernel32_size = kernel_size - kernel32_start; |
| |
| Debug(" kernel32_start=0x%" PRIx64 "\n", kernel32_start); |
| Debug(" kernel32_size=0x%" PRIx64 "\n", kernel32_size); |
| |
| /* Keep just the 32-bit kernel. */ |
| if (kernel32_size) { |
| g_kernel_size = kernel32_size; |
| g_kernel_data = VbExMalloc(g_kernel_size); |
| Memcpy(g_kernel_data, kernel_buf + kernel32_start, |
| kernel32_size); |
| } |
| |
| /* Copy the original zeropage data from kernel_buf into g_param_data, |
| * then tweak a few fields for our purposes */ |
| params = (struct linux_kernel_params *)(g_param_data); |
| Memcpy(&(params->setup_sects), &(lh->setup_sects), |
| offsetof(struct linux_kernel_params, e820_entries) |
| - offsetof(struct linux_kernel_params, setup_sects)); |
| params->boot_flag = 0; |
| params->ramdisk_image = 0; /* we don't support initrd */ |
| params->ramdisk_size = 0; |
| params->type_of_loader = 0xff; |
| /* We need to point to the kernel commandline arg. On disk, it will come |
| * right after the 32-bit part of the kernel. */ |
| params->cmd_line_ptr = kernel_body_load_address + |
| roundup(kernel32_size, CROS_ALIGN) + |
| find_cmdline_start((char *)g_config_data, g_config_size); |
| Debug(" cmdline_addr=0x%x\n", params->cmd_line_ptr); |
| Debug(" version=0x%x\n", params->version); |
| Debug(" kernel_alignment=0x%x\n", params->kernel_alignment); |
| Debug(" relocatable_kernel=0x%x\n", params->relocatable_kernel); |
| /* A fake e820 memory map with 2 entries */ |
| params->n_e820_entry = 2; |
| params->e820_entries[0].start_addr = 0x00000000; |
| params->e820_entries[0].segment_size = 0x00001000; |
| params->e820_entries[0].segment_type = E820_TYPE_RAM; |
| params->e820_entries[1].start_addr = 0xfffff000; |
| params->e820_entries[1].segment_size = 0x00001000; |
| params->e820_entries[1].segment_type = E820_TYPE_RESERVED; |
| |
| /* done */ |
| free(kernel_buf); |
| return 1; |
| } |
| |
| /* This returns just the kernel blob, with the verification blob separated |
| * and copied to new memory in g_keyblock and g_preamble. */ |
| static uint8_t *ReadOldBlobFromFileOrDie(const char *filename, |
| uint64_t *size_ptr) |
| { |
| FILE *fp = NULL; |
| struct stat statbuf; |
| VbKeyBlockHeader *key_block; |
| VbKernelPreambleHeader *preamble; |
| uint64_t now = 0; |
| uint8_t *buf; |
| uint8_t *kernel_blob_data; |
| uint64_t kernel_blob_size; |
| uint64_t file_size = 0; |
| |
| if (0 != stat(filename, &statbuf)) |
| Fatal("Unable to stat %s: %s\n", filename, strerror(errno)); |
| |
| if (S_ISBLK(statbuf.st_mode)) { |
| int fd = open(filename, O_RDONLY); |
| if (fd >= 0) { |
| ioctl(fd, BLKGETSIZE64, &file_size); |
| close(fd); |
| } |
| } else { |
| file_size = statbuf.st_size; |
| } |
| Debug("%s size is 0x%" PRIx64 "\n", filename, file_size); |
| if (file_size < opt_pad) |
| Fatal("%s is too small to be a valid kernel blob\n"); |
| |
| Debug("Reading %s\n", filename); |
| fp = fopen(filename, "rb"); |
| if (!fp) |
| Fatal("Unable to open file %s: %s\n", filename, |
| strerror(errno)); |
| |
| buf = VbExMalloc(opt_pad); |
| if (1 != fread(buf, opt_pad, 1, fp)) |
| Fatal("Unable to read header from %s: %s\n", filename, |
| error_fread(fp)); |
| |
| /* Sanity-check the key_block */ |
| key_block = (VbKeyBlockHeader *) buf; |
| Debug("Keyblock is 0x%" PRIx64 " bytes\n", key_block->key_block_size); |
| now += key_block->key_block_size; |
| if (now > file_size) |
| Fatal("key_block_size advances past the end of the blob\n"); |
| if (now > opt_pad) |
| Fatal("key_block_size advances past %" PRIu64 " byte padding\n", |
| opt_pad); |
| /* LGTM */ |
| g_keyblock = (VbKeyBlockHeader *) VbExMalloc(key_block->key_block_size); |
| Memcpy(g_keyblock, key_block, key_block->key_block_size); |
| |
| /* And the preamble */ |
| preamble = (VbKernelPreambleHeader *) (buf + now); |
| Debug("Preamble is 0x%" PRIx64 " bytes\n", preamble->preamble_size); |
| now += preamble->preamble_size; |
| if (now > file_size) |
| Fatal("preamble_size advances past the end of the blob\n"); |
| if (now > opt_pad) |
| Fatal("preamble_size advances past %" PRIu64 " byte padding\n", |
| opt_pad); |
| /* LGTM */ |
| Debug(" kernel_version = %d\n", preamble->kernel_version); |
| Debug(" bootloader_address = 0x%" PRIx64 "\n", |
| preamble->bootloader_address); |
| Debug(" bootloader_size = 0x%" PRIx64 "\n", preamble->bootloader_size); |
| Debug(" kern_blob_size = 0x%" PRIx64 "\n", |
| preamble->body_signature.data_size); |
| g_preamble = |
| (VbKernelPreambleHeader *) VbExMalloc(preamble->preamble_size); |
| Memcpy(g_preamble, preamble, preamble->preamble_size); |
| |
| /* Now for the kernel blob */ |
| Debug("kernel blob is at offset 0x%" PRIx64 "\n", now); |
| if (0 != fseek(fp, now, SEEK_SET)) |
| Fatal("Unable to seek to 0x%" PRIx64 " in %s: %s\n", now, |
| filename, strerror(errno)); |
| |
| /* Sanity check */ |
| kernel_blob_size = file_size - now; |
| if (!kernel_blob_size) |
| Fatal("No kernel blob found\n"); |
| if (kernel_blob_size < preamble->body_signature.data_size) |
| fprintf(stderr, |
| "Warning: kernel file only has 0x%" PRIx64 " bytes\n", |
| kernel_blob_size); |
| kernel_blob_data = VbExMalloc(kernel_blob_size); |
| |
| /* Read it in */ |
| if (1 != fread(kernel_blob_data, kernel_blob_size, 1, fp)) |
| Fatal("Unable to read kernel blob from %s: %s\n", filename, |
| error_fread(fp)); |
| |
| /* Done */ |
| VbExFree(buf); |
| |
| if (size_ptr) |
| *size_ptr = kernel_blob_size; |
| |
| return kernel_blob_data; |
| } |
| |
| /* Split a kernel blob into separate g_kernel, g_param, g_config, and |
| * g_bootloader parts. */ |
| static void UnpackKernelBlob(uint8_t *kernel_blob_data, |
| uint64_t kernel_blob_size) |
| { |
| |
| uint64_t k_blob_size = g_preamble->body_signature.data_size; |
| uint64_t k_blob_ofs = 0; |
| uint64_t b_size = g_preamble->bootloader_size; |
| uint64_t b_ofs = k_blob_ofs + g_preamble->bootloader_address - |
| g_preamble->body_load_address; |
| uint64_t p_ofs = b_ofs - CROS_CONFIG_SIZE; |
| uint64_t c_ofs = p_ofs - CROS_PARAMS_SIZE; |
| |
| Debug("k_blob_size = 0x%" PRIx64 "\n", k_blob_size); |
| Debug("k_blob_ofs = 0x%" PRIx64 "\n", k_blob_ofs); |
| Debug("b_size = 0x%" PRIx64 "\n", b_size); |
| Debug("b_ofs = 0x%" PRIx64 "\n", b_ofs); |
| Debug("p_ofs = 0x%" PRIx64 "\n", p_ofs); |
| Debug("c_ofs = 0x%" PRIx64 "\n", c_ofs); |
| |
| g_kernel_size = c_ofs; |
| g_kernel_data = VbExMalloc(g_kernel_size); |
| Memcpy(g_kernel_data, kernel_blob_data, g_kernel_size); |
| |
| g_param_size = CROS_PARAMS_SIZE; |
| g_param_data = VbExMalloc(g_param_size); |
| Memcpy(g_param_data, kernel_blob_data + p_ofs, g_param_size); |
| |
| g_config_size = CROS_CONFIG_SIZE; |
| g_config_data = VbExMalloc(g_config_size); |
| Memcpy(g_config_data, kernel_blob_data + c_ofs, g_config_size); |
| |
| g_bootloader_size = b_size; |
| g_bootloader_data = VbExMalloc(g_bootloader_size); |
| Memcpy(g_bootloader_data, kernel_blob_data + b_ofs, g_bootloader_size); |
| } |
| |
| /****************************************************************************/ |
| |
| static uint8_t *CreateKernBlob(uint64_t kernel_body_load_address, |
| enum arch_t arch, uint64_t *size_ptr) |
| { |
| uint8_t *kern_blob; |
| uint64_t kern_blob_size; |
| uint64_t now; |
| uint64_t bootloader_size = roundup(g_bootloader_size, CROS_ALIGN); |
| |
| /* Put the kernel blob together */ |
| kern_blob_size = roundup(g_kernel_size, CROS_ALIGN) + |
| CROS_CONFIG_SIZE + CROS_PARAMS_SIZE + bootloader_size; |
| Debug("kern_blob_size=0x%" PRIx64 "\n", kern_blob_size); |
| kern_blob = VbExMalloc(kern_blob_size); |
| Memset(kern_blob, 0, kern_blob_size); |
| now = 0; |
| |
| Debug("kernel goes at kern_blob+0x%" PRIx64 "\n", now); |
| |
| Memcpy(kern_blob + now, g_kernel_data, g_kernel_size); |
| now += roundup(g_kernel_size, CROS_ALIGN); |
| |
| Debug("config goes at kern_blob+0x%" PRIx64 "\n", now); |
| if (g_config_size) |
| Memcpy(kern_blob + now, g_config_data, g_config_size); |
| now += CROS_CONFIG_SIZE; |
| |
| Debug("params goes at kern_blob+0x%" PRIx64 "\n", now); |
| if (g_param_size) |
| Memcpy(kern_blob + now, g_param_data, g_param_size); |
| now += CROS_PARAMS_SIZE; |
| |
| Debug("bootloader goes at kern_blob+0x%" PRIx64 "\n", now); |
| g_bootloader_address = kernel_body_load_address + now; |
| Debug(" bootloader_address=0x%" PRIx64 "\n", g_bootloader_address); |
| Debug(" bootloader_size=0x%" PRIx64 "\n", bootloader_size); |
| if (bootloader_size) |
| Memcpy(kern_blob + now, g_bootloader_data, g_bootloader_size); |
| now += bootloader_size; |
| Debug("end of kern_blob at kern_blob+0x%" PRIx64 "\n", now); |
| |
| /* Done */ |
| if (size_ptr) |
| *size_ptr = kern_blob_size; |
| |
| return kern_blob; |
| } |
| |
| static int Pack(const char *outfile, |
| uint8_t *kernel_blob, |
| uint64_t kernel_size, |
| int version, |
| uint64_t kernel_body_load_address, |
| VbPrivateKey *signpriv_key) |
| { |
| VbSignature *body_sig; |
| FILE *f; |
| uint64_t i; |
| uint64_t written = 0; |
| |
| /* Sign the kernel data */ |
| body_sig = CalculateSignature(kernel_blob, kernel_size, signpriv_key); |
| if (!body_sig) |
| Fatal("Error calculating body signature\n"); |
| |
| /* Create preamble */ |
| g_preamble = CreateKernelPreamble(version, |
| kernel_body_load_address, |
| g_bootloader_address, |
| roundup(g_bootloader_size, |
| CROS_ALIGN), body_sig, |
| opt_pad - g_keyblock->key_block_size, |
| signpriv_key); |
| if (!g_preamble) { |
| VbExError("Error creating preamble.\n"); |
| return 1; |
| } |
| /* Write the output file */ |
| Debug("writing %s...\n", outfile); |
| f = fopen(outfile, "wb"); |
| if (!f) { |
| VbExError("Can't open output file %s\n", outfile); |
| return 1; |
| } |
| Debug("0x%" PRIx64 " bytes of key_block\n", g_keyblock->key_block_size); |
| Debug("0x%" PRIx64 " bytes of preamble\n", g_preamble->preamble_size); |
| i = ((1 != fwrite(g_keyblock, g_keyblock->key_block_size, 1, f)) || |
| (1 != fwrite(g_preamble, g_preamble->preamble_size, 1, f))); |
| if (i) { |
| VbExError("Can't write output file %s\n", outfile); |
| fclose(f); |
| unlink(outfile); |
| return 1; |
| } |
| written += g_keyblock->key_block_size; |
| written += g_preamble->preamble_size; |
| |
| if (!opt_vblockonly) { |
| Debug("0x%" PRIx64 " bytes of kern_blob\n", kernel_size); |
| i = (1 != fwrite(kernel_blob, kernel_size, 1, f)); |
| if (i) { |
| fclose(f); |
| unlink(outfile); |
| Fatal("Can't write output file %s\n", outfile); |
| } |
| written += kernel_size; |
| } |
| Debug("0x%" PRIx64 " bytes total\n", written); |
| fclose(f); |
| |
| /* Success */ |
| return 0; |
| } |
| |
| static int Verify(uint8_t *kernel_blob, |
| uint64_t kernel_size, |
| VbPublicKey *signpub_key, |
| const char *keyblock_outfile, |
| uint64_t min_version) |
| { |
| VbPublicKey *data_key; |
| RSAPublicKey *rsa; |
| |
| if (0 != KeyBlockVerify(g_keyblock, g_keyblock->key_block_size, |
| signpub_key, (0 == signpub_key))) |
| Fatal("Error verifying key block.\n"); |
| |
| printf("Key block:\n"); |
| data_key = &g_keyblock->data_key; |
| if (opt_verbose) |
| printf(" Signature: %s\n", |
| signpub_key ? "valid" : "ignored"); |
| printf(" Size: 0x%" PRIx64 "\n", |
| g_keyblock->key_block_size); |
| printf(" Flags: %" PRIu64 " ", |
| g_keyblock->key_block_flags); |
| if (g_keyblock->key_block_flags & KEY_BLOCK_FLAG_DEVELOPER_0) |
| printf(" !DEV"); |
| if (g_keyblock->key_block_flags & KEY_BLOCK_FLAG_DEVELOPER_1) |
| printf(" DEV"); |
| if (g_keyblock->key_block_flags & KEY_BLOCK_FLAG_RECOVERY_0) |
| printf(" !REC"); |
| if (g_keyblock->key_block_flags & KEY_BLOCK_FLAG_RECOVERY_1) |
| printf(" REC"); |
| printf("\n"); |
| printf(" Data key algorithm: %" PRIu64 " %s\n", data_key->algorithm, |
| (data_key->algorithm < kNumAlgorithms ? |
| algo_strings[data_key->algorithm] : "(invalid)")); |
| printf(" Data key version: %" PRIu64 "\n", data_key->key_version); |
| printf(" Data key sha1sum: "); |
| PrintPubKeySha1Sum(data_key); |
| printf("\n"); |
| |
| if (keyblock_outfile) { |
| FILE *f = NULL; |
| f = fopen(keyblock_outfile, "wb"); |
| if (!f) |
| Fatal("Can't open key block file %s\n", |
| keyblock_outfile); |
| if (1 != fwrite(g_keyblock, g_keyblock->key_block_size, 1, f)) |
| Fatal("Can't write key block file %s\n", |
| keyblock_outfile); |
| fclose(f); |
| } |
| |
| if (data_key->key_version < (min_version >> 16)) |
| Fatal("Data key version %" PRIu64 |
| " is lower than minimum %" PRIu64 ".\n", |
| data_key->key_version, (min_version >> 16)); |
| |
| rsa = PublicKeyToRSA(data_key); |
| if (!rsa) |
| Fatal("Error parsing data key.\n"); |
| |
| /* Verify preamble */ |
| if (0 != |
| VerifyKernelPreamble(g_preamble, g_preamble->preamble_size, rsa)) |
| Fatal("Error verifying preamble.\n"); |
| |
| printf("Preamble:\n"); |
| printf(" Size: 0x%" PRIx64 "\n", |
| g_preamble->preamble_size); |
| printf(" Header version: %" PRIu32 ".%" PRIu32 "\n", |
| g_preamble->header_version_major, |
| g_preamble->header_version_minor); |
| printf(" Kernel version: %" PRIu64 "\n", |
| g_preamble->kernel_version); |
| printf(" Body load address: 0x%" PRIx64 "\n", |
| g_preamble->body_load_address); |
| printf(" Body size: 0x%" PRIx64 "\n", |
| g_preamble->body_signature.data_size); |
| printf(" Bootloader address: 0x%" PRIx64 "\n", |
| g_preamble->bootloader_address); |
| printf(" Bootloader size: 0x%" PRIx64 "\n", |
| g_preamble->bootloader_size); |
| |
| if (g_preamble->kernel_version < (min_version & 0xFFFF)) |
| Fatal("Kernel version %" PRIu64 " is lower than minimum %" |
| PRIu64 ".\n", g_preamble->kernel_version, |
| (min_version & 0xFFFF)); |
| |
| /* Verify body */ |
| if (0 != VerifyData(kernel_blob, kernel_size, |
| &g_preamble->body_signature, rsa)) |
| Fatal("Error verifying kernel body.\n"); |
| printf("Body verification succeeded.\n"); |
| |
| if (opt_verbose) |
| printf("Config:\n%s\n", |
| kernel_blob + CmdLineOffset(g_preamble)); |
| |
| return 0; |
| } |
| |
| /****************************************************************************/ |
| |
| static int do_vbutil_kernel(int argc, char *argv[]) |
| { |
| char *filename = NULL; |
| char *oldfile = NULL; |
| char *keyblock_file = NULL; |
| char *signpubkey_file = NULL; |
| char *signprivkey_file = NULL; |
| char *version_str = NULL; |
| int version = -1; |
| char *vmlinuz_file = NULL; |
| char *bootloader_file = NULL; |
| char *config_file = NULL; |
| enum arch_t arch = ARCH_X86; |
| char *address_str = NULL; |
| uint64_t kernel_body_load_address = CROS_32BIT_ENTRY_ADDR; |
| int mode = 0; |
| int parse_error = 0; |
| uint64_t min_version = 0; |
| char *e; |
| int i; |
| VbPrivateKey *signpriv_key = NULL; |
| VbPublicKey *signpub_key = NULL; |
| uint8_t *kernel_blob = NULL; |
| uint64_t kernel_size = 0; |
| |
| while (((i = getopt_long(argc, argv, ":", long_opts, NULL)) != -1) && |
| !parse_error) { |
| switch (i) { |
| default: |
| case '?': |
| /* Unhandled option */ |
| parse_error = 1; |
| break; |
| |
| case 0: |
| /* silently handled option */ |
| break; |
| |
| case OPT_MODE_PACK: |
| case OPT_MODE_REPACK: |
| case OPT_MODE_VERIFY: |
| if (mode && (mode != i)) { |
| fprintf(stderr, |
| "Only one mode can be specified\n"); |
| parse_error = 1; |
| break; |
| } |
| mode = i; |
| filename = optarg; |
| break; |
| |
| case OPT_ARCH: |
| /* check the first 3 characters to also detect x86_64 */ |
| if ((!strncasecmp(optarg, "x86", 3)) || |
| (!strcasecmp(optarg, "amd64"))) |
| arch = ARCH_X86; |
| else if ((!strcasecmp(optarg, "arm")) || |
| (!strcasecmp(optarg, "aarch64"))) |
| arch = ARCH_ARM; |
| else if (!strcasecmp(optarg, "mips")) |
| arch = ARCH_MIPS; |
| else { |
| fprintf(stderr, |
| "Unknown architecture string: %s\n", |
| optarg); |
| parse_error = 1; |
| } |
| break; |
| |
| case OPT_OLDBLOB: |
| oldfile = optarg; |
| break; |
| |
| case OPT_KLOADADDR: |
| address_str = optarg; |
| kernel_body_load_address = strtoul(optarg, &e, 0); |
| if (!*optarg || (e && *e)) { |
| fprintf(stderr, "Invalid --kloadaddr\n"); |
| parse_error = 1; |
| } |
| break; |
| |
| case OPT_KEYBLOCK: |
| keyblock_file = optarg; |
| break; |
| |
| case OPT_SIGNPUBKEY: |
| signpubkey_file = optarg; |
| break; |
| |
| case OPT_SIGNPRIVATE: |
| signprivkey_file = optarg; |
| break; |
| |
| case OPT_VMLINUZ: |
| vmlinuz_file = optarg; |
| break; |
| |
| case OPT_BOOTLOADER: |
| bootloader_file = optarg; |
| break; |
| |
| case OPT_CONFIG: |
| config_file = optarg; |
| break; |
| |
| case OPT_VBLOCKONLY: |
| opt_vblockonly = 1; |
| break; |
| |
| case OPT_VERSION: |
| version_str = optarg; |
| version = strtoul(optarg, &e, 0); |
| if (!*optarg || (e && *e)) { |
| fprintf(stderr, "Invalid --version\n"); |
| parse_error = 1; |
| } |
| break; |
| |
| case OPT_MINVERSION: |
| min_version = strtoul(optarg, &e, 0); |
| if (!*optarg || (e && *e)) { |
| fprintf(stderr, "Invalid --minversion\n"); |
| parse_error = 1; |
| } |
| break; |
| |
| case OPT_PAD: |
| opt_pad = strtoul(optarg, &e, 0); |
| if (!*optarg || (e && *e)) { |
| fprintf(stderr, "Invalid --pad\n"); |
| parse_error = 1; |
| } |
| break; |
| } |
| } |
| |
| if (parse_error) { |
| print_help(argv[0]); |
| return 1; |
| } |
| |
| switch (mode) { |
| case OPT_MODE_PACK: |
| |
| /* Required */ |
| |
| if (!keyblock_file) |
| Fatal("Missing required keyblock file.\n"); |
| |
| g_keyblock = (VbKeyBlockHeader *) ReadFile(keyblock_file, 0); |
| if (!g_keyblock) |
| Fatal("Error reading key block.\n"); |
| |
| if (!signprivkey_file) |
| Fatal("Missing required signprivate file.\n"); |
| |
| signpriv_key = PrivateKeyRead(signprivkey_file); |
| if (!signpriv_key) |
| Fatal("Error reading signing key.\n"); |
| |
| /* Optional */ |
| |
| if (config_file) { |
| Debug("Reading %s\n", config_file); |
| g_config_data = |
| sReadConfigFile(config_file, &g_config_size); |
| if (!g_config_data) |
| Fatal("Error reading config file.\n"); |
| } |
| |
| if (vmlinuz_file) |
| if (!ImportVmlinuzFile |
| (vmlinuz_file, arch, kernel_body_load_address)) |
| Fatal("Error reading kernel file.\n"); |
| |
| if (bootloader_file) { |
| Debug("Reading %s\n", bootloader_file); |
| g_bootloader_data = |
| ReadFile(bootloader_file, &g_bootloader_size); |
| if (!g_bootloader_data) |
| Fatal("Error reading bootloader file.\n"); |
| Debug(" bootloader file size=0x%" PRIx64 "\n", |
| g_bootloader_size); |
| } |
| |
| /* Do it */ |
| |
| kernel_blob = CreateKernBlob(kernel_body_load_address, arch, |
| &kernel_size); |
| |
| return Pack(filename, kernel_blob, kernel_size, |
| version, kernel_body_load_address, signpriv_key); |
| |
| case OPT_MODE_REPACK: |
| |
| /* Required */ |
| |
| if (!signprivkey_file) |
| Fatal("Missing required signprivate file.\n"); |
| |
| signpriv_key = PrivateKeyRead(signprivkey_file); |
| if (!signpriv_key) |
| Fatal("Error reading signing key.\n"); |
| |
| if (!oldfile) |
| Fatal("Missing previously packed blob.\n"); |
| |
| /* Load the old blob */ |
| |
| kernel_blob = ReadOldBlobFromFileOrDie(oldfile, &kernel_size); |
| if (0 != Verify(kernel_blob, kernel_size, 0, 0, 0)) |
| Fatal("The oldblob doesn't verify\n"); |
| |
| /* Take it apart */ |
| |
| UnpackKernelBlob(kernel_blob, kernel_size); |
| free(kernel_blob); |
| |
| /* Load optional params */ |
| |
| if (!version_str) |
| version = g_preamble->kernel_version; |
| |
| if (!address_str) |
| kernel_body_load_address = |
| g_preamble->body_load_address; |
| |
| if (config_file) { |
| if (g_config_data) |
| free(g_config_data); |
| Debug("Reading %s\n", config_file); |
| g_config_data = |
| sReadConfigFile(config_file, &g_config_size); |
| if (!g_config_data) |
| Fatal("Error reading config file.\n"); |
| } |
| |
| if (keyblock_file) { |
| if (g_keyblock) |
| free(g_keyblock); |
| g_keyblock = |
| (VbKeyBlockHeader *) ReadFile(keyblock_file, 0); |
| if (!g_keyblock) |
| Fatal("Error reading key block.\n"); |
| } |
| |
| /* Put it back together */ |
| |
| kernel_blob = CreateKernBlob(kernel_body_load_address, arch, |
| &kernel_size); |
| |
| return Pack(filename, kernel_blob, kernel_size, |
| version, kernel_body_load_address, signpriv_key); |
| |
| case OPT_MODE_VERIFY: |
| |
| /* Optional */ |
| |
| if (signpubkey_file) { |
| signpub_key = PublicKeyRead(signpubkey_file); |
| if (!signpub_key) |
| Fatal("Error reading public key.\n"); |
| } |
| |
| /* Do it */ |
| |
| kernel_blob = ReadOldBlobFromFileOrDie(filename, &kernel_size); |
| |
| return Verify(kernel_blob, kernel_size, signpub_key, |
| keyblock_file, min_version); |
| } |
| |
| fprintf(stderr, |
| "You must specify a mode: --pack, --repack or --verify\n"); |
| print_help(argv[0]); |
| return 1; |
| } |
| |
| DECLARE_FUTIL_COMMAND(vbutil_kernel, do_vbutil_kernel, |
| "Creates, signs, and verifies the kernel blob", |
| print_help); |