OLD | NEW |
(Empty) | |
| 1 /* Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 2 * Use of this source code is governed by a BSD-style license that can be |
| 3 * found in the LICENSE file. |
| 4 * |
| 5 * Verified boot kernel utility |
| 6 */ |
| 7 |
| 8 #include <getopt.h> |
| 9 #include <inttypes.h> /* For PRIu64 */ |
| 10 #include <stddef.h> |
| 11 #include <stdio.h> |
| 12 #include <stdlib.h> |
| 13 #include <unistd.h> |
| 14 |
| 15 #include "cryptolib.h" |
| 16 #include "host_common.h" |
| 17 #include "kernel_blob.h" |
| 18 #include "vboot_common.h" |
| 19 |
| 20 |
| 21 /* Command line options */ |
| 22 enum { |
| 23 OPT_MODE_PACK = 1000, |
| 24 OPT_MODE_VERIFY, |
| 25 OPT_KEYBLOCK, |
| 26 OPT_SIGNPUBKEY, |
| 27 OPT_SIGNPRIVATE, |
| 28 OPT_VERSION, |
| 29 OPT_VMLINUZ, |
| 30 OPT_BOOTLOADER, |
| 31 OPT_CONFIG, |
| 32 OPT_PAD, |
| 33 }; |
| 34 |
| 35 static struct option long_opts[] = { |
| 36 {"pack", 1, 0, OPT_MODE_PACK }, |
| 37 {"verify", 1, 0, OPT_MODE_VERIFY }, |
| 38 {"keyblock", 1, 0, OPT_KEYBLOCK }, |
| 39 {"signpubkey", 1, 0, OPT_SIGNPUBKEY }, |
| 40 {"signprivate", 1, 0, OPT_SIGNPRIVATE }, |
| 41 {"version", 1, 0, OPT_VERSION }, |
| 42 {"vmlinuz", 1, 0, OPT_VMLINUZ }, |
| 43 {"bootloader", 1, 0, OPT_BOOTLOADER }, |
| 44 {"config", 1, 0, OPT_CONFIG }, |
| 45 {"pad", 1, 0, OPT_PAD }, |
| 46 {NULL, 0, 0, 0} |
| 47 }; |
| 48 |
| 49 |
| 50 /* Print help and return error */ |
| 51 static int PrintHelp(void) { |
| 52 |
| 53 puts("vbutil_kernel - Verified boot key block utility\n" |
| 54 "\n" |
| 55 "Usage: vbutil_kernel <--pack|--verify> <file> [OPTIONS]\n" |
| 56 "\n" |
| 57 "For '--pack <file>', required OPTIONS are:\n" |
| 58 " --keyblock <file> Key block in .keyblock format\n" |
| 59 " --signprivate <file> Signing private key in .pem format\n" |
| 60 " --version <number> Kernel version\n" |
| 61 " --vmlinuz <file> Linux kernel image\n" |
| 62 " --bootloader <file> Bootloader stub\n" |
| 63 " --config <file> Config file\n" |
| 64 "Optional OPTIONS are:\n" |
| 65 " --pad <number> Padding size in bytes\n" |
| 66 "\n" |
| 67 "For '--verify <file>', required OPTIONS are:\n" |
| 68 " --signpubkey <file> Signing public key in .vbpubk format\n" |
| 69 ""); |
| 70 return 1; |
| 71 } |
| 72 |
| 73 |
| 74 /* Return the smallest integral multiple of [alignment] that is equal |
| 75 * to or greater than [val]. Used to determine the number of |
| 76 * pages/sectors/blocks/whatever needed to contain [val] |
| 77 * items/bytes/etc. */ |
| 78 static uint64_t roundup(uint64_t val, uint64_t alignment) { |
| 79 uint64_t rem = val % alignment; |
| 80 if ( rem ) |
| 81 return val + (alignment - rem); |
| 82 return val; |
| 83 } |
| 84 |
| 85 |
| 86 /* Match regexp /\b--\b/ to delimit the start of the kernel commandline. If we |
| 87 * don't find one, we'll use the whole thing. */ |
| 88 static unsigned int find_cmdline_start(char *input, unsigned int max_len) { |
| 89 int start = 0; |
| 90 int i; |
| 91 for(i = 0; i < max_len - 1 && input[i]; i++) { |
| 92 if ('-' == input[i] && '-' == input[i + 1]) { /* found a "--" */ |
| 93 if ((i == 0 || ' ' == input[i - 1]) && /* nothing before it */ |
| 94 (i + 2 >= max_len || ' ' == input[i+2])) { /* nothing after it */ |
| 95 start = i+2; /* note: hope there's a trailing '\0' */ |
| 96 break; |
| 97 } |
| 98 } |
| 99 } |
| 100 while(' ' == input[start]) /* skip leading spaces */ |
| 101 start++; |
| 102 |
| 103 return start; |
| 104 } |
| 105 |
| 106 |
| 107 /* Pack a .kernel */ |
| 108 static int Pack(const char* outfile, const char* keyblock_file, |
| 109 const char* signprivate, uint64_t version, |
| 110 const char* vmlinuz, const char* bootloader_file, |
| 111 const char* config_file, uint64_t pad) { |
| 112 |
| 113 struct linux_kernel_header *lh = 0; |
| 114 struct linux_kernel_params *params = 0; |
| 115 VbPrivateKey* signing_key; |
| 116 VbSignature* body_sig; |
| 117 VbKernelPreambleHeader* preamble; |
| 118 VbKeyBlockHeader* key_block; |
| 119 uint64_t key_block_size; |
| 120 uint8_t* config_buf; |
| 121 uint64_t config_size; |
| 122 uint8_t* bootloader_buf; |
| 123 uint64_t bootloader_size; |
| 124 uint64_t bootloader_mem_start; |
| 125 uint64_t bootloader_mem_size; |
| 126 uint8_t* kernel_buf; |
| 127 uint64_t kernel_size; |
| 128 uint64_t kernel32_start = 0; |
| 129 uint64_t kernel32_size = 0; |
| 130 uint32_t cmdline_addr; |
| 131 uint8_t* blob = NULL; |
| 132 uint64_t blob_size; |
| 133 uint64_t now = 0; |
| 134 FILE* f; |
| 135 uint64_t i; |
| 136 |
| 137 if (!outfile) { |
| 138 error("Must specify output filename\n"); |
| 139 return 1; |
| 140 } |
| 141 if (!keyblock_file || !signprivate) { |
| 142 error("Must specify all keys\n"); |
| 143 return 1; |
| 144 } |
| 145 if (!vmlinuz || !bootloader_file || !config_file) { |
| 146 error("Must specify all input files\n"); |
| 147 return 1; |
| 148 } |
| 149 |
| 150 /* Read the key block and private key */ |
| 151 key_block = (VbKeyBlockHeader*)ReadFile(keyblock_file, &key_block_size); |
| 152 if (!key_block) { |
| 153 error("Error reading key block.\n"); |
| 154 return 1; |
| 155 } |
| 156 if (pad < key_block->key_block_size) { |
| 157 error("Pad too small\n"); |
| 158 return 1; |
| 159 } |
| 160 |
| 161 signing_key = PrivateKeyRead(signprivate, key_block->data_key.algorithm); |
| 162 if (!signing_key) { |
| 163 error("Error reading signing key.\n"); |
| 164 return 1; |
| 165 } |
| 166 |
| 167 /* Read the config file */ |
| 168 config_buf = ReadFile(config_file, &config_size); |
| 169 if (!config_buf) |
| 170 return 1; |
| 171 if (CROS_CONFIG_SIZE <= config_size) { /* need room for trailing '\0' */ |
| 172 error("Config file %s is too large (>= %d bytes)\n", |
| 173 config_file, CROS_CONFIG_SIZE); |
| 174 return 1; |
| 175 } |
| 176 /* Replace newlines with spaces */ |
| 177 for (i = 0; i < config_size; i++) |
| 178 if ('\n' == config_buf[i]) |
| 179 config_buf[i] = ' '; |
| 180 |
| 181 /* Read the bootloader */ |
| 182 bootloader_buf = ReadFile(bootloader_file, &bootloader_size); |
| 183 if (!bootloader_buf) |
| 184 return 1; |
| 185 |
| 186 /* Read the kernel */ |
| 187 kernel_buf = ReadFile(vmlinuz, &kernel_size); |
| 188 if (!kernel_buf) |
| 189 return 1; |
| 190 if (!kernel_size) { |
| 191 error("Empty kernel file\n"); |
| 192 return 1; |
| 193 } |
| 194 |
| 195 /* The first part of vmlinuz is a header, followed by a real-mode |
| 196 * boot stub. We only want the 32-bit part. */ |
| 197 lh = (struct linux_kernel_header *)kernel_buf; |
| 198 kernel32_start = (lh->setup_sects + 1) << 9; |
| 199 if (kernel32_start >= kernel_size) { |
| 200 error("Malformed kernel\n"); |
| 201 return 1; |
| 202 } |
| 203 kernel32_size = kernel_size - kernel32_start; |
| 204 |
| 205 /* Allocate and zero the blob we need. */ |
| 206 blob_size = roundup(kernel32_size, CROS_ALIGN) + |
| 207 CROS_CONFIG_SIZE + |
| 208 CROS_PARAMS_SIZE + |
| 209 roundup(bootloader_size, CROS_ALIGN); |
| 210 blob = (uint8_t *)Malloc(blob_size); |
| 211 if (!blob) { |
| 212 error("Couldn't allocate %ld bytes.\n", blob_size); |
| 213 return 1; |
| 214 } |
| 215 Memset(blob, 0, blob_size); |
| 216 |
| 217 /* Copy the 32-bit kernel. */ |
| 218 if (kernel32_size) |
| 219 Memcpy(blob + now, kernel_buf + kernel32_start, kernel32_size); |
| 220 now += roundup(now + kernel32_size, CROS_ALIGN); |
| 221 |
| 222 /* Find the load address of the commandline. We'll need it later. */ |
| 223 cmdline_addr = CROS_32BIT_ENTRY_ADDR + now + |
| 224 find_cmdline_start((char *)config_buf, config_size); |
| 225 |
| 226 /* Copy the config. */ |
| 227 if (config_size) |
| 228 Memcpy(blob + now, config_buf, config_size); |
| 229 now += CROS_CONFIG_SIZE; |
| 230 |
| 231 /* The zeropage data is next. Overlay the linux_kernel_header onto it, and |
| 232 * tweak a few fields. */ |
| 233 params = (struct linux_kernel_params *)(blob + now); |
| 234 Memcpy(&(params->setup_sects), &(lh->setup_sects), |
| 235 sizeof(*lh) - offsetof(struct linux_kernel_header, setup_sects)); |
| 236 params->boot_flag = 0; |
| 237 params->ramdisk_image = 0; /* we don't support initrd */ |
| 238 params->ramdisk_size = 0; |
| 239 params->type_of_loader = 0xff; |
| 240 params->cmd_line_ptr = cmdline_addr; |
| 241 now += CROS_PARAMS_SIZE; |
| 242 |
| 243 /* Finally, append the bootloader. Remember where it will load in |
| 244 * memory, too. */ |
| 245 bootloader_mem_start = CROS_32BIT_ENTRY_ADDR + now; |
| 246 bootloader_mem_size = roundup(bootloader_size, CROS_ALIGN); |
| 247 if (bootloader_size) |
| 248 Memcpy(blob + now, bootloader_buf, bootloader_size); |
| 249 now += bootloader_mem_size; |
| 250 |
| 251 /* Free input buffers */ |
| 252 Free(kernel_buf); |
| 253 Free(config_buf); |
| 254 Free(bootloader_buf); |
| 255 |
| 256 /* Sign the kernel data */ |
| 257 body_sig = CalculateSignature(blob, blob_size, signing_key); |
| 258 if (!body_sig) { |
| 259 error("Error calculating body signature\n"); |
| 260 return 1; |
| 261 } |
| 262 |
| 263 /* Create preamble */ |
| 264 preamble = CreateKernelPreamble(version, |
| 265 CROS_32BIT_ENTRY_ADDR, |
| 266 bootloader_mem_start, |
| 267 bootloader_mem_size, |
| 268 body_sig, |
| 269 pad - key_block_size, |
| 270 signing_key); |
| 271 if (!preamble) { |
| 272 error("Error creating preamble.\n"); |
| 273 return 1; |
| 274 } |
| 275 |
| 276 /* Write the output file */ |
| 277 f = fopen(outfile, "wb"); |
| 278 if (!f) { |
| 279 error("Can't open output file %s\n", outfile); |
| 280 return 1; |
| 281 } |
| 282 i = ((1 != fwrite(key_block, key_block_size, 1, f)) || |
| 283 (1 != fwrite(preamble, preamble->preamble_size, 1, f)) || |
| 284 (1 != fwrite(blob, blob_size, 1, f))); |
| 285 fclose(f); |
| 286 if (i) { |
| 287 error("Can't write output file %s\n", outfile); |
| 288 unlink(outfile); |
| 289 return 1; |
| 290 } |
| 291 |
| 292 /* Success */ |
| 293 return 0; |
| 294 } |
| 295 |
| 296 |
| 297 static int Verify(const char* infile, const char* signpubkey) { |
| 298 |
| 299 VbKeyBlockHeader* key_block; |
| 300 VbKernelPreambleHeader* preamble; |
| 301 VbPublicKey* data_key; |
| 302 VbPublicKey* sign_key; |
| 303 RSAPublicKey* rsa; |
| 304 uint8_t* blob; |
| 305 uint64_t blob_size; |
| 306 uint64_t now = 0; |
| 307 |
| 308 if (!infile || !signpubkey) { |
| 309 error("Must specify filename and signpubkey\n"); |
| 310 return 1; |
| 311 } |
| 312 |
| 313 /* Read public signing key */ |
| 314 sign_key = PublicKeyRead(signpubkey); |
| 315 if (!sign_key) { |
| 316 error("Error reading signpubkey.\n"); |
| 317 return 1; |
| 318 } |
| 319 |
| 320 /* Read blob */ |
| 321 blob = ReadFile(infile, &blob_size); |
| 322 if (!blob) { |
| 323 error("Error reading input file\n"); |
| 324 return 1; |
| 325 } |
| 326 |
| 327 /* Verify key block */ |
| 328 key_block = (VbKeyBlockHeader*)blob; |
| 329 if (0 != VerifyKeyBlock(key_block, blob_size, sign_key)) { |
| 330 error("Error verifying key block.\n"); |
| 331 return 1; |
| 332 } |
| 333 Free(sign_key); |
| 334 now += key_block->key_block_size; |
| 335 |
| 336 printf("Key block:\n"); |
| 337 data_key = &key_block->data_key; |
| 338 printf(" Size: %" PRIu64 "\n", key_block->key_block_size); |
| 339 printf(" Data key algorithm: %" PRIu64 " %s\n", data_key->algorithm, |
| 340 (data_key->algorithm < kNumAlgorithms ? |
| 341 algo_strings[data_key->algorithm] : "(invalid)")); |
| 342 printf(" Data key version: %" PRIu64 "\n", data_key->key_version); |
| 343 printf(" Flags: %" PRIu64 "\n", key_block->key_block_flags); |
| 344 |
| 345 rsa = PublicKeyToRSA(&key_block->data_key); |
| 346 if (!rsa) { |
| 347 error("Error parsing data key.\n"); |
| 348 return 1; |
| 349 } |
| 350 |
| 351 /* Verify preamble */ |
| 352 preamble = (VbKernelPreambleHeader*)(blob + now); |
| 353 if (0 != VerifyKernelPreamble2(preamble, blob_size - now, rsa)) { |
| 354 error("Error verifying preamble.\n"); |
| 355 return 1; |
| 356 } |
| 357 now += preamble->preamble_size; |
| 358 |
| 359 printf("Preamble:\n"); |
| 360 printf(" Size: %" PRIu64 "\n", preamble->preamble_size); |
| 361 printf(" Header version: %" PRIu32 ".%" PRIu32"\n", |
| 362 preamble->header_version_major, preamble->header_version_minor); |
| 363 printf(" Kernel version: %" PRIu64 "\n", preamble->kernel_version); |
| 364 printf(" Body load address: %" PRIu64 "\n", preamble->body_load_address); |
| 365 printf(" Body size: %" PRIu64 "\n", |
| 366 preamble->body_signature.data_size); |
| 367 printf(" Bootloader address: %" PRIu64 "\n", preamble->bootloader_address); |
| 368 printf(" Bootloader size: %" PRIu64 "\n", preamble->bootloader_size); |
| 369 |
| 370 /* Verify body */ |
| 371 if (0 != VerifyData(blob + now, &preamble->body_signature, rsa)) { |
| 372 error("Error verifying kernel body.\n"); |
| 373 return 1; |
| 374 } |
| 375 printf("Body verification succeeded.\n"); |
| 376 return 0; |
| 377 } |
| 378 |
| 379 |
| 380 int main(int argc, char* argv[]) { |
| 381 |
| 382 char* filename = NULL; |
| 383 char* key_block_file = NULL; |
| 384 char* signpubkey = NULL; |
| 385 char* signprivate = NULL; |
| 386 uint64_t version = 0; |
| 387 char* vmlinuz = NULL; |
| 388 char* bootloader = NULL; |
| 389 char* config_file = NULL; |
| 390 uint64_t pad = 65536; |
| 391 int mode = 0; |
| 392 int parse_error = 0; |
| 393 char* e; |
| 394 int i; |
| 395 |
| 396 while ((i = getopt_long(argc, argv, "", long_opts, NULL)) != -1) { |
| 397 switch (i) { |
| 398 case '?': |
| 399 /* Unhandled option */ |
| 400 printf("Unknown option\n"); |
| 401 parse_error = 1; |
| 402 break; |
| 403 |
| 404 case OPT_MODE_PACK: |
| 405 case OPT_MODE_VERIFY: |
| 406 mode = i; |
| 407 filename = optarg; |
| 408 break; |
| 409 |
| 410 case OPT_KEYBLOCK: |
| 411 key_block_file = optarg; |
| 412 break; |
| 413 |
| 414 case OPT_SIGNPUBKEY: |
| 415 signpubkey = optarg; |
| 416 break; |
| 417 |
| 418 case OPT_SIGNPRIVATE: |
| 419 signprivate = optarg; |
| 420 break; |
| 421 |
| 422 case OPT_VMLINUZ: |
| 423 vmlinuz = optarg; |
| 424 break; |
| 425 |
| 426 case OPT_BOOTLOADER: |
| 427 bootloader = optarg; |
| 428 break; |
| 429 |
| 430 case OPT_CONFIG: |
| 431 config_file = optarg; |
| 432 break; |
| 433 |
| 434 case OPT_VERSION: |
| 435 version = strtoul(optarg, &e, 0); |
| 436 if (!*optarg || (e && *e)) { |
| 437 printf("Invalid --version\n"); |
| 438 parse_error = 1; |
| 439 } |
| 440 break; |
| 441 |
| 442 case OPT_PAD: |
| 443 pad = strtoul(optarg, &e, 0); |
| 444 if (!*optarg || (e && *e)) { |
| 445 printf("Invalid --pad\n"); |
| 446 parse_error = 1; |
| 447 } |
| 448 break; |
| 449 } |
| 450 } |
| 451 |
| 452 if (parse_error) |
| 453 return PrintHelp(); |
| 454 |
| 455 switch(mode) { |
| 456 case OPT_MODE_PACK: |
| 457 return Pack(filename, key_block_file, signprivate, version, vmlinuz, |
| 458 bootloader, config_file, pad); |
| 459 case OPT_MODE_VERIFY: |
| 460 return Verify(filename, signpubkey); |
| 461 default: |
| 462 printf("Must specify a mode.\n"); |
| 463 return PrintHelp(); |
| 464 } |
| 465 } |
OLD | NEW |