/* * GRUB -- GRand Unified Bootloader * Copyright (C) 2011 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); static grub_err_t read_packet_header (grub_file_t sig, grub_uint8_t *out_type, grub_size_t *len) { grub_uint8_t type; grub_uint8_t l; grub_uint16_t l16; grub_uint32_t l32; /* New format. */ switch (grub_file_read (sig, &type, sizeof (type))) { case 1: break; case 0: { *out_type = 0xff; return 0; } default: if (grub_errno) return grub_errno; /* TRANSLATORS: it's about GNUPG signatures. */ return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); } if (type == 0) { *out_type = 0xfe; return 0; } if (!(type & 0x80)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); if (type & 0x40) { *out_type = (type & 0x3f); if (grub_file_read (sig, &l, sizeof (l)) != 1) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); if (l < 192) { *len = l; return 0; } if (l < 224) { *len = (l - 192) << 8; if (grub_file_read (sig, &l, sizeof (l)) != 1) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); *len |= l; return 0; } if (l == 255) { if (grub_file_read (sig, &l32, sizeof (l32)) != sizeof (l32)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); *len = grub_be_to_cpu32 (l32); return 0; } return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); } *out_type = ((type >> 2) & 0xf); switch (type & 0x3) { case 0: if (grub_file_read (sig, &l, sizeof (l)) != sizeof (l)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); *len = l; return 0; case 1: if (grub_file_read (sig, &l16, sizeof (l16)) != sizeof (l16)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); *len = grub_be_to_cpu16 (l16); return 0; case 2: if (grub_file_read (sig, &l32, sizeof (l32)) != sizeof (l32)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); *len = grub_be_to_cpu32 (l32); return 0; } return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); } struct signature_v4_header { grub_uint8_t type; grub_uint8_t pkeyalgo; grub_uint8_t hash; grub_uint16_t hashed_sub; } __attribute__ ((packed)); const char *hashes[] = { "md5", "sha1", "ripemd160", [0x08] = "sha256", [0x09] = "sha384", [0x0a] = "sha512", [0x0b] = "sha224" }; struct { const char *name; grub_size_t nmpisig; grub_size_t nmpipub; } pkalgos[] = { [1] = { "rsa", 1, 2 }, [3] = { "rsa", 1, 2 }, [17] = { "dsa", 2, 4 }, }; struct grub_public_key { struct grub_public_key *next; struct grub_public_subkey *subkeys; }; struct grub_public_subkey { struct grub_public_subkey *next; grub_uint8_t type; grub_uint32_t fingerprint[5]; gcry_mpi_t mpis[10]; }; static void free_pk (struct grub_public_key *pk) { struct grub_public_subkey *nsk, *sk; for (sk = pk->subkeys; sk; sk = nsk) { nsk = sk->next; grub_free (sk); } grub_free (pk); } struct grub_public_key * grub_load_public_key (grub_file_t f) { grub_err_t err; struct grub_public_key *ret; struct grub_public_subkey **last = 0; ret = grub_zalloc (sizeof (*ret)); if (!ret) return NULL; last = &ret->subkeys; while (1) { grub_uint8_t type; grub_size_t len; grub_uint8_t v, pk; grub_uint32_t creation_time; grub_off_t pend; struct grub_public_subkey *sk; grub_size_t i; grub_uint16_t len_be; GRUB_PROPERLY_ALIGNED_ARRAY (fingerprint_context, GRUB_MD_SHA1->contextsize); err = read_packet_header (f, &type, &len); if (err) goto fail; if (type == 0xfe) continue; if (type == 0xff) return ret; grub_dprintf ("crypt", "len = %x\n", (int) len); pend = grub_file_tell (f) + len; if (type != 6 && type != 14 && type != 5 && type != 7) { grub_file_seek (f, pend); continue; } if (grub_file_read (f, &v, sizeof (v)) != sizeof (v)) { grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); goto fail; } grub_dprintf ("crypt", "v = %x\n", v); if (v != 4) { grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); goto fail; } if (grub_file_read (f, &creation_time, sizeof (creation_time)) != sizeof (creation_time)) { grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); goto fail; } grub_dprintf ("crypt", "time = %x\n", creation_time); if (grub_file_read (f, &pk, sizeof (pk)) != sizeof (pk)) { grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); goto fail; } grub_dprintf ("crypt", "pk = %x\n", pk); if (pk >= ARRAY_SIZE (pkalgos) || pkalgos[pk].name == NULL) { grub_file_seek (f, pend); continue; } sk = grub_zalloc (sizeof (struct grub_public_subkey)); if (!sk) goto fail; grub_memset (fingerprint_context, 0, sizeof (fingerprint_context)); GRUB_MD_SHA1->init (fingerprint_context); GRUB_MD_SHA1->write (fingerprint_context, "\x99", 1); len_be = grub_cpu_to_be16 (len); GRUB_MD_SHA1->write (fingerprint_context, &len_be, sizeof (len_be)); GRUB_MD_SHA1->write (fingerprint_context, &v, sizeof (v)); GRUB_MD_SHA1->write (fingerprint_context, &creation_time, sizeof (creation_time)); GRUB_MD_SHA1->write (fingerprint_context, &pk, sizeof (pk)); for (i = 0; i < pkalgos[pk].nmpipub; i++) { grub_uint16_t l; grub_size_t lb; grub_uint8_t buffer[4096]; if (grub_file_read (f, &l, sizeof (l)) != sizeof (l)) { grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); goto fail; } lb = (grub_be_to_cpu16 (l) + 7) / 8; if (lb > sizeof (buffer) - sizeof (grub_uint16_t)) { grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); goto fail; } if (grub_file_read (f, buffer + sizeof (grub_uint16_t), lb) != (grub_ssize_t) lb) { grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); goto fail; } grub_memcpy (buffer, &l, sizeof (l)); GRUB_MD_SHA1->write (fingerprint_context, buffer, lb + sizeof (grub_uint16_t)); if (gcry_mpi_scan (&sk->mpis[i], GCRYMPI_FMT_PGP, buffer, lb + sizeof (grub_uint16_t), 0)) { grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); goto fail; } } GRUB_MD_SHA1->final (fingerprint_context); grub_memcpy (sk->fingerprint, GRUB_MD_SHA1->read (fingerprint_context), 20); *last = sk; last = &sk->next; grub_dprintf ("crypt", "actual pos: %x, expected: %x\n", (int)grub_file_tell (f), (int)pend); grub_file_seek (f, pend); } fail: free_pk (ret); return NULL; } struct grub_public_key *grub_pk_trusted; struct grub_public_subkey * grub_crypto_pk_locate_subkey (grub_uint64_t keyid, struct grub_public_key *pkey) { struct grub_public_subkey *sk; for (sk = pkey->subkeys; sk; sk = sk->next) if (grub_memcmp (sk->fingerprint + 3, &keyid, 8) == 0) return sk; return 0; } struct grub_public_subkey * grub_crypto_pk_locate_subkey_in_trustdb (grub_uint64_t keyid) { struct grub_public_key *pkey; struct grub_public_subkey *sk; for (pkey = grub_pk_trusted; pkey; pkey = pkey->next) { sk = grub_crypto_pk_locate_subkey (keyid, pkey); if (sk) return sk; } return 0; } grub_err_t grub_verify_signature (grub_file_t f, grub_file_t sig, struct grub_public_key *pkey) { grub_size_t len; grub_uint8_t v; grub_uint8_t h; grub_uint8_t t; grub_uint8_t pk; const gcry_md_spec_t *hash; struct signature_v4_header v4; grub_err_t err; grub_size_t i; gcry_mpi_t mpis[10]; grub_uint8_t type; err = read_packet_header (sig, &type, &len); if (err) return err; if (type != 0x2) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); if (grub_file_read (sig, &v, sizeof (v)) != sizeof (v)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); if (v != 4) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); if (grub_file_read (sig, &v4, sizeof (v4)) != sizeof (v4)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); h = v4.hash; t = v4.type; pk = v4.pkeyalgo; if (t != 0) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); if (h >= ARRAY_SIZE (hashes) || hashes[h] == NULL) return grub_error (GRUB_ERR_BAD_SIGNATURE, "unknown hash"); if (pk >= ARRAY_SIZE (pkalgos) || pkalgos[pk].name == NULL) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); hash = grub_crypto_lookup_md_by_name (hashes[h]); if (!hash) return grub_error (GRUB_ERR_BAD_SIGNATURE, "hash `%s' not loaded", hashes[h]); grub_dprintf ("crypt", "alive\n"); { GRUB_PROPERLY_ALIGNED_ARRAY (context, hash->contextsize); unsigned char *hval; grub_ssize_t rem = grub_be_to_cpu16 (v4.hashed_sub); grub_uint32_t headlen = grub_cpu_to_be32 (rem + 6); grub_uint8_t s; grub_uint16_t unhashed_sub; grub_ssize_t r; grub_uint8_t hash_start[2]; gcry_mpi_t hmpi; grub_uint64_t keyid = 0; struct grub_public_subkey *sk; grub_memset (context, 0, sizeof (context)); hash->init (context); while (1) { grub_uint8_t readbuf[4096]; r = grub_file_read (f, readbuf, sizeof (readbuf)); if (r < 0) return grub_errno; if (r == 0) break; hash->write (context, readbuf, r); } hash->write (context, &v, sizeof (v)); hash->write (context, &v4, sizeof (v4)); while (rem) { grub_uint8_t readbuf[4096]; r = grub_file_read (sig, readbuf, rem < (grub_ssize_t) sizeof (readbuf) ? rem : (grub_ssize_t) sizeof (readbuf)); if (r < 0) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); if (r == 0) break; hash->write (context, readbuf, r); rem -= r; } hash->write (context, &v, sizeof (v)); s = 0xff; hash->write (context, &s, sizeof (s)); hash->write (context, &headlen, sizeof (headlen)); r = grub_file_read (sig, &unhashed_sub, sizeof (unhashed_sub)); if (r != sizeof (unhashed_sub)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); { grub_uint8_t readbuf[4096]; grub_uint8_t *ptr; grub_uint32_t l; rem = grub_be_to_cpu16 (unhashed_sub); if (rem > (int) sizeof (readbuf)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); r = grub_file_read (sig, readbuf, rem); if (r != rem) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); for (ptr = readbuf; ptr < readbuf + rem; ptr += l) { if (*ptr < 192) l = *ptr++; else if (*ptr < 255) { if (ptr + 1 >= readbuf + rem) break; l = (((ptr[0] & ~192) << 8) | ptr[1]) + 192; ptr += 2; } else { if (ptr + 5 >= readbuf + rem) break; l = grub_be_to_cpu32 (grub_get_unaligned32 (ptr + 1)); ptr += 5; } if (*ptr == 0x10 && l >= 8) keyid = grub_get_unaligned64 (ptr + 1); } } hash->final (context); grub_dprintf ("crypt", "alive\n"); hval = hash->read (context); if (grub_file_read (sig, hash_start, sizeof (hash_start)) != sizeof (hash_start)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); if (grub_memcmp (hval, hash_start, sizeof (hash_start)) != 0) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); grub_dprintf ("crypt", "@ %x\n", (int)grub_file_tell (sig)); for (i = 0; i < pkalgos[pk].nmpisig; i++) { grub_uint16_t l; grub_size_t lb; grub_uint8_t buffer[4096]; grub_dprintf ("crypt", "alive\n"); if (grub_file_read (sig, &l, sizeof (l)) != sizeof (l)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); grub_dprintf ("crypt", "alive\n"); lb = (grub_be_to_cpu16 (l) + 7) / 8; grub_dprintf ("crypt", "l = 0x%04x\n", grub_be_to_cpu16 (l)); if (lb > sizeof (buffer) - sizeof (grub_uint16_t)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); grub_dprintf ("crypt", "alive\n"); if (grub_file_read (sig, buffer + sizeof (grub_uint16_t), lb) != (grub_ssize_t) lb) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); grub_dprintf ("crypt", "alive\n"); grub_memcpy (buffer, &l, sizeof (l)); grub_dprintf ("crypt", "alive\n"); if (gcry_mpi_scan (&mpis[i], GCRYMPI_FMT_PGP, buffer, lb + sizeof (grub_uint16_t), 0)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); grub_dprintf ("crypt", "alive\n"); } if (pkey) sk = grub_crypto_pk_locate_subkey (keyid, pkey); else sk = grub_crypto_pk_locate_subkey_in_trustdb (keyid); if (!sk) /* TRANSLATORS: %08x is 32-bit key id. */ return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("public key %08x not found"), keyid); unsigned nbits = gcry_mpi_get_nbits (sk->mpis[1]); grub_dprintf ("crypt", "must be %u bits got %d bits\n", nbits, (int)(8 * hash->mdlen)); if (gcry_mpi_scan (&hmpi, GCRYMPI_FMT_USG, hval, nbits / 8 < (unsigned) hash->mdlen ? nbits / 8 : (unsigned) hash->mdlen, 0)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); if (!grub_crypto_pk_dsa) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("module `%s' isn't loaded"), "gcry_dsa"); if (grub_crypto_pk_dsa->verify (0, hmpi, mpis, sk->mpis, 0, 0)) return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature")); } return GRUB_ERR_NONE; } static grub_err_t grub_cmd_trust (grub_command_t cmd __attribute__ ((unused)), int argc, char **args) { grub_file_t pkf; struct grub_public_key *pk = NULL; if (argc < 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); grub_file_filter_disable_all (); pkf = grub_file_open (args[0]); if (!pkf) return grub_errno; pk = grub_load_public_key (pkf); if (!pk) { grub_file_close (pkf); return grub_errno; } grub_file_close (pkf); pk->next = grub_pk_trusted; grub_pk_trusted = pk; return GRUB_ERR_NONE; } static grub_err_t grub_cmd_list (grub_command_t cmd __attribute__ ((unused)), int argc __attribute__ ((unused)), char **args __attribute__ ((unused))) { struct grub_public_key *pk = NULL; struct grub_public_subkey *sk = NULL; for (pk = grub_pk_trusted; pk; pk = pk->next) for (sk = pk->subkeys; sk; sk = sk->next) { unsigned i; for (i = 0; i < 20; i += 2) grub_printf ("%02x%02x ", ((grub_uint8_t *) sk->fingerprint)[i], ((grub_uint8_t *) sk->fingerprint)[i + 1]); grub_printf ("\n"); } return GRUB_ERR_NONE; } static grub_err_t grub_cmd_distrust (grub_command_t cmd __attribute__ ((unused)), int argc, char **args) { grub_uint32_t keyid, keyid_be; struct grub_public_key **pkey; struct grub_public_subkey *sk; if (argc < 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); keyid = grub_strtoull (args[0], 0, 16); if (grub_errno) return grub_errno; keyid_be = grub_cpu_to_be32 (keyid); for (pkey = &grub_pk_trusted; *pkey; pkey = &((*pkey)->next)) { struct grub_public_key *next; for (sk = (*pkey)->subkeys; sk; sk = sk->next) if (grub_memcmp (sk->fingerprint + 4, &keyid_be, 4) == 0) break; if (!sk) continue; next = (*pkey)->next; free_pk (*pkey); *pkey = next; return GRUB_ERR_NONE; } /* TRANSLATORS: %08x is 32-bit key id. */ return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("public key %08x not found"), keyid); } static grub_err_t grub_cmd_verify_signature (grub_command_t cmd __attribute__ ((unused)), int argc, char **args) { grub_file_t f, sig; grub_err_t err; struct grub_public_key *pk = NULL; grub_dprintf ("crypt", "alive\n"); if (argc < 2) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("two arguments expected")); grub_dprintf ("crypt", "alive\n"); if (argc > 2) { grub_file_t pkf; grub_file_filter_disable_all (); pkf = grub_file_open (args[2]); if (!pkf) return grub_errno; pk = grub_load_public_key (pkf); if (!pk) { grub_file_close (pkf); return grub_errno; } grub_file_close (pkf); } grub_file_filter_disable_all (); f = grub_file_open (args[0]); if (!f) return grub_errno; grub_file_filter_disable_all (); sig = grub_file_open (args[1]); if (!sig) { grub_file_close (f); return grub_errno; } err = grub_verify_signature (f, sig, pk); grub_file_close (f); grub_file_close (sig); return err; } static int sec = 0; static grub_file_t grub_pubkey_open (grub_file_t io, const char *filename) { grub_file_t sig; char *fsuf, *ptr; grub_err_t err; grub_file_filter_t curfilt[GRUB_FILE_FILTER_MAX]; if (!sec) return io; fsuf = grub_malloc (grub_strlen (filename) + sizeof (".sig")); if (!fsuf) return NULL; ptr = grub_stpcpy (fsuf, filename); grub_memcpy (ptr, ".sig", sizeof (".sig")); grub_memcpy (curfilt, grub_file_filters_enabled, sizeof (curfilt)); grub_file_filter_disable_all (); sig = grub_file_open (fsuf); grub_memcpy (grub_file_filters_enabled, curfilt, sizeof (curfilt)); grub_free (fsuf); if (!sig) return NULL; err = grub_verify_signature (io, sig, NULL); grub_file_close (sig); if (err) return NULL; grub_file_seek (io, 0); return io; } static char * grub_env_write_sec (struct grub_env_var *var __attribute__ ((unused)), const char *val) { sec = (*val == '1') || (*val == 'e'); return grub_strdup (sec ? "enforce" : "no"); } static grub_ssize_t pseudo_read (struct grub_file *file, char *buf, grub_size_t len) { grub_memcpy (buf, (grub_uint8_t *) file->data + file->offset, len); return len; } /* Filesystem descriptor. */ struct grub_fs pseudo_fs = { .name = "pseudo", .read = pseudo_read }; struct gcry_pk_spec *grub_crypto_pk_dsa; struct gcry_pk_spec *grub_crypto_pk_ecdsa; struct gcry_pk_spec *grub_crypto_pk_rsa; static grub_command_t cmd, cmd_trust, cmd_distrust, cmd_list; GRUB_MOD_INIT(verify) { const char *val; struct grub_module_header *header; val = grub_env_get ("check_signatures"); if (val && (val[0] == '1' || val[0] == 'e')) sec = 1; else sec = 0; grub_file_filter_register (GRUB_FILE_FILTER_PUBKEY, grub_pubkey_open); grub_register_variable_hook ("check_signatures", 0, grub_env_write_sec); grub_env_export ("check_signatures"); grub_pk_trusted = 0; FOR_MODULES (header) { struct grub_file pseudo_file; struct grub_public_key *pk = NULL; grub_memset (&pseudo_file, 0, sizeof (pseudo_file)); /* Not an ELF module, skip. */ if (header->type != OBJ_TYPE_PUBKEY) continue; pseudo_file.fs = &pseudo_fs; pseudo_file.size = (header->size - sizeof (struct grub_module_header)); pseudo_file.data = (char *) header + sizeof (struct grub_module_header); pk = grub_load_public_key (&pseudo_file); if (!pk) grub_fatal ("error loading initial key: %s\n", grub_errmsg); pk->next = grub_pk_trusted; grub_pk_trusted = pk; } if (!val) grub_env_set ("check_signatures", grub_pk_trusted ? "enforce" : "no"); cmd = grub_register_command ("verify_detached", grub_cmd_verify_signature, N_("FILE SIGNATURE_FILE [PUBKEY_FILE]"), N_("Verify detached signature.")); cmd_trust = grub_register_command ("trust", grub_cmd_trust, N_("PUBKEY_FILE"), N_("Add PKFILE to trusted keys.")); cmd_list = grub_register_command ("list_trusted", grub_cmd_list, 0, N_("List trusted keys.")); cmd_distrust = grub_register_command ("distrust", grub_cmd_distrust, N_("PUBKEY_ID"), N_("Remove KEYID from trusted keys.")); } GRUB_MOD_FINI(verify) { grub_file_filter_unregister (GRUB_FILE_FILTER_PUBKEY); grub_unregister_command (cmd); grub_unregister_command (cmd_trust); grub_unregister_command (cmd_list); grub_unregister_command (cmd_distrust); }