1027 lines
27 KiB
Diff
1027 lines
27 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Vladimir Serbinenko <phcoder@gmail.com>
|
|
Date: Sun, 5 Feb 2017 14:25:47 +0100
|
|
Subject: [PATCH] verifiers: Framework core
|
|
|
|
Verifiers framework provides core file verification functionality which
|
|
can be used by various security mechanisms, e.g., UEFI secure boot, TPM,
|
|
PGP signature verification, etc.
|
|
|
|
The patch contains PGP code changes and probably they should be extracted
|
|
to separate patch for the sake of clarity.
|
|
|
|
Signed-off-by: Vladimir Serbinenko <phcoder@gmail.com>
|
|
Signed-off-by: Daniel Kiper <daniel.kiper@oracle.com>
|
|
Reviewed-by: Ross Philipson <ross.philipson@oracle.com>
|
|
(cherry picked from commit 75a919e334f8514b6adbc024743cfcd9b88fa394)
|
|
Signed-off-by: Daniel Axtens <dja@axtens.net>
|
|
---
|
|
grub-core/Makefile.core.def | 5 +
|
|
grub-core/commands/verifiers.c | 197 ++++++++++++++
|
|
grub-core/commands/verify.c | 566 ++++++++++++++++++++---------------------
|
|
include/grub/file.h | 2 +-
|
|
include/grub/list.h | 1 +
|
|
include/grub/verify.h | 65 +++++
|
|
6 files changed, 539 insertions(+), 297 deletions(-)
|
|
create mode 100644 grub-core/commands/verifiers.c
|
|
create mode 100644 include/grub/verify.h
|
|
|
|
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
|
|
index c8a50b4fcfa..29c3bf6cd66 100644
|
|
--- a/grub-core/Makefile.core.def
|
|
+++ b/grub-core/Makefile.core.def
|
|
@@ -921,6 +921,11 @@ module = {
|
|
cppflags = '-I$(srcdir)/lib/posix_wrap';
|
|
};
|
|
|
|
+module = {
|
|
+ name = verifiers;
|
|
+ common = commands/verifiers.c;
|
|
+};
|
|
+
|
|
module = {
|
|
name = hdparm;
|
|
common = commands/hdparm.c;
|
|
diff --git a/grub-core/commands/verifiers.c b/grub-core/commands/verifiers.c
|
|
new file mode 100644
|
|
index 00000000000..fde88318d4c
|
|
--- /dev/null
|
|
+++ b/grub-core/commands/verifiers.c
|
|
@@ -0,0 +1,197 @@
|
|
+/*
|
|
+ * GRUB -- GRand Unified Bootloader
|
|
+ * Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
|
|
+ *
|
|
+ * Verifiers helper.
|
|
+ */
|
|
+
|
|
+#include <grub/file.h>
|
|
+#include <grub/verify.h>
|
|
+#include <grub/dl.h>
|
|
+
|
|
+GRUB_MOD_LICENSE ("GPLv3+");
|
|
+
|
|
+struct grub_file_verifier *grub_file_verifiers;
|
|
+
|
|
+struct grub_verified
|
|
+{
|
|
+ grub_file_t file;
|
|
+ void *buf;
|
|
+};
|
|
+typedef struct grub_verified *grub_verified_t;
|
|
+
|
|
+static void
|
|
+verified_free (grub_verified_t verified)
|
|
+{
|
|
+ if (verified)
|
|
+ {
|
|
+ grub_free (verified->buf);
|
|
+ grub_free (verified);
|
|
+ }
|
|
+}
|
|
+
|
|
+static grub_ssize_t
|
|
+verified_read (struct grub_file *file, char *buf, grub_size_t len)
|
|
+{
|
|
+ grub_verified_t verified = file->data;
|
|
+
|
|
+ grub_memcpy (buf, (char *) verified->buf + file->offset, len);
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static grub_err_t
|
|
+verified_close (struct grub_file *file)
|
|
+{
|
|
+ grub_verified_t verified = file->data;
|
|
+
|
|
+ grub_file_close (verified->file);
|
|
+ verified_free (verified);
|
|
+ file->data = 0;
|
|
+
|
|
+ /* Device and name are freed by parent. */
|
|
+ file->device = 0;
|
|
+ file->name = 0;
|
|
+
|
|
+ return grub_errno;
|
|
+}
|
|
+
|
|
+struct grub_fs verified_fs =
|
|
+{
|
|
+ .name = "verified_read",
|
|
+ .read = verified_read,
|
|
+ .close = verified_close
|
|
+};
|
|
+
|
|
+static grub_file_t
|
|
+grub_verifiers_open (grub_file_t io, enum grub_file_type type)
|
|
+{
|
|
+ grub_verified_t verified = NULL;
|
|
+ struct grub_file_verifier *ver;
|
|
+ void *context;
|
|
+ grub_file_t ret = 0;
|
|
+ grub_err_t err;
|
|
+
|
|
+ grub_dprintf ("verify", "file: %s type: %d\n", io->name, type);
|
|
+
|
|
+ if ((type & GRUB_FILE_TYPE_MASK) == GRUB_FILE_TYPE_SIGNATURE
|
|
+ || (type & GRUB_FILE_TYPE_MASK) == GRUB_FILE_TYPE_VERIFY_SIGNATURE
|
|
+ || (type & GRUB_FILE_TYPE_SKIP_SIGNATURE))
|
|
+ return io;
|
|
+
|
|
+ if (io->device->disk &&
|
|
+ (io->device->disk->dev->id == GRUB_DISK_DEVICE_MEMDISK_ID
|
|
+ || io->device->disk->dev->id == GRUB_DISK_DEVICE_PROCFS_ID))
|
|
+ return io;
|
|
+
|
|
+ FOR_LIST_ELEMENTS(ver, grub_file_verifiers)
|
|
+ {
|
|
+ enum grub_verify_flags flags = 0;
|
|
+ err = ver->init (io, type, &context, &flags);
|
|
+ if (err)
|
|
+ goto fail_noclose;
|
|
+ if (!(flags & GRUB_VERIFY_FLAGS_SKIP_VERIFICATION))
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!ver)
|
|
+ /* No verifiers wanted to verify. Just return underlying file. */
|
|
+ return io;
|
|
+
|
|
+ ret = grub_malloc (sizeof (*ret));
|
|
+ if (!ret)
|
|
+ {
|
|
+ goto fail;
|
|
+ }
|
|
+ *ret = *io;
|
|
+
|
|
+ ret->fs = &verified_fs;
|
|
+ ret->not_easily_seekable = 0;
|
|
+ if (ret->size >> (sizeof (grub_size_t) * GRUB_CHAR_BIT - 1))
|
|
+ {
|
|
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
|
+ N_("big file signature isn't implemented yet"));
|
|
+ goto fail;
|
|
+ }
|
|
+ verified = grub_malloc (sizeof (*verified));
|
|
+ if (!verified)
|
|
+ {
|
|
+ goto fail;
|
|
+ }
|
|
+ verified->buf = grub_malloc (ret->size);
|
|
+ if (!verified->buf)
|
|
+ {
|
|
+ goto fail;
|
|
+ }
|
|
+ if (grub_file_read (io, verified->buf, ret->size) != (grub_ssize_t) ret->size)
|
|
+ {
|
|
+ if (!grub_errno)
|
|
+ grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"),
|
|
+ io->name);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ err = ver->write (context, verified->buf, ret->size);
|
|
+ if (err)
|
|
+ goto fail;
|
|
+
|
|
+ err = ver->fini ? ver->fini (context) : GRUB_ERR_NONE;
|
|
+ if (err)
|
|
+ goto fail;
|
|
+
|
|
+ if (ver->close)
|
|
+ ver->close (context);
|
|
+
|
|
+ FOR_LIST_ELEMENTS_NEXT(ver, grub_file_verifiers)
|
|
+ {
|
|
+ enum grub_verify_flags flags = 0;
|
|
+ err = ver->init (io, type, &context, &flags);
|
|
+ if (err)
|
|
+ goto fail_noclose;
|
|
+ if (flags & GRUB_VERIFY_FLAGS_SKIP_VERIFICATION)
|
|
+ continue;
|
|
+ err = ver->write (context, verified->buf, ret->size);
|
|
+ if (err)
|
|
+ goto fail;
|
|
+
|
|
+ err = ver->fini ? ver->fini (context) : GRUB_ERR_NONE;
|
|
+ if (err)
|
|
+ goto fail;
|
|
+
|
|
+ if (ver->close)
|
|
+ ver->close (context);
|
|
+ }
|
|
+
|
|
+ verified->file = io;
|
|
+ ret->data = verified;
|
|
+ return ret;
|
|
+
|
|
+ fail:
|
|
+ ver->close (context);
|
|
+ fail_noclose:
|
|
+ verified_free (verified);
|
|
+ grub_free (ret);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+GRUB_MOD_INIT(verifiers)
|
|
+{
|
|
+ grub_file_filter_register (GRUB_FILE_FILTER_VERIFY, grub_verifiers_open);
|
|
+}
|
|
+
|
|
+GRUB_MOD_FINI(verifiers)
|
|
+{
|
|
+ grub_file_filter_unregister (GRUB_FILE_FILTER_VERIFY);
|
|
+}
|
|
diff --git a/grub-core/commands/verify.c b/grub-core/commands/verify.c
|
|
index f0dfeceebd4..29e74a64004 100644
|
|
--- a/grub-core/commands/verify.c
|
|
+++ b/grub-core/commands/verify.c
|
|
@@ -30,16 +30,10 @@
|
|
#include <grub/env.h>
|
|
#include <grub/kernel.h>
|
|
#include <grub/extcmd.h>
|
|
+#include <grub/verify.h>
|
|
|
|
GRUB_MOD_LICENSE ("GPLv3+");
|
|
|
|
-struct grub_verified
|
|
-{
|
|
- grub_file_t file;
|
|
- void *buf;
|
|
-};
|
|
-typedef struct grub_verified *grub_verified_t;
|
|
-
|
|
enum
|
|
{
|
|
OPTION_SKIP_SIG = 0
|
|
@@ -445,23 +439,27 @@ rsa_pad (gcry_mpi_t *hmpi, grub_uint8_t *hval,
|
|
return ret;
|
|
}
|
|
|
|
-static grub_err_t
|
|
-grub_verify_signature_real (char *buf, grub_size_t size,
|
|
- grub_file_t f, grub_file_t sig,
|
|
- struct grub_public_key *pkey)
|
|
+struct grub_pubkey_context
|
|
{
|
|
- grub_size_t len;
|
|
+ grub_file_t sig;
|
|
+ struct signature_v4_header v4;
|
|
grub_uint8_t v;
|
|
+ const gcry_md_spec_t *hash;
|
|
+ void *hash_context;
|
|
+};
|
|
+
|
|
+static grub_err_t
|
|
+grub_verify_signature_init (struct grub_pubkey_context *ctxt, grub_file_t sig)
|
|
+{
|
|
+ grub_size_t len;
|
|
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 = 0;
|
|
|
|
+ grub_memset (ctxt, 0, sizeof (*ctxt));
|
|
+
|
|
err = read_packet_header (sig, &type, &len);
|
|
if (err)
|
|
return err;
|
|
@@ -469,18 +467,18 @@ grub_verify_signature_real (char *buf, grub_size_t size,
|
|
if (type != 0x2)
|
|
return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature"));
|
|
|
|
- if (grub_file_read (sig, &v, sizeof (v)) != sizeof (v))
|
|
+ if (grub_file_read (sig, &ctxt->v, sizeof (ctxt->v)) != sizeof (ctxt->v))
|
|
return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature"));
|
|
|
|
- if (v != 4)
|
|
+ if (ctxt->v != 4)
|
|
return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature"));
|
|
|
|
- if (grub_file_read (sig, &v4, sizeof (v4)) != sizeof (v4))
|
|
+ if (grub_file_read (sig, &ctxt->v4, sizeof (ctxt->v4)) != sizeof (ctxt->v4))
|
|
return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature"));
|
|
|
|
- h = v4.hash;
|
|
- t = v4.type;
|
|
- pk = v4.pkeyalgo;
|
|
+ h = ctxt->v4.hash;
|
|
+ t = ctxt->v4.type;
|
|
+ pk = ctxt->v4.pkeyalgo;
|
|
|
|
if (t != 0)
|
|
return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature"));
|
|
@@ -491,183 +489,233 @@ grub_verify_signature_real (char *buf, grub_size_t size,
|
|
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)
|
|
+ ctxt->hash = grub_crypto_lookup_md_by_name (hashes[h]);
|
|
+ if (!ctxt->hash)
|
|
return grub_error (GRUB_ERR_BAD_SIGNATURE, "hash `%s' not loaded", hashes[h]);
|
|
|
|
grub_dprintf ("crypt", "alive\n");
|
|
|
|
- {
|
|
- void *context = NULL;
|
|
- 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_uint8_t *readbuf = NULL;
|
|
+ ctxt->sig = sig;
|
|
|
|
- context = grub_zalloc (hash->contextsize);
|
|
- readbuf = grub_zalloc (READBUF_SIZE);
|
|
- if (!context || !readbuf)
|
|
- goto fail;
|
|
-
|
|
- hash->init (context);
|
|
- if (buf)
|
|
- hash->write (context, buf, size);
|
|
- else
|
|
- while (1)
|
|
- {
|
|
- r = grub_file_read (f, readbuf, READBUF_SIZE);
|
|
- if (r < 0)
|
|
- goto fail;
|
|
- if (r == 0)
|
|
- break;
|
|
- hash->write (context, readbuf, r);
|
|
- }
|
|
-
|
|
- hash->write (context, &v, sizeof (v));
|
|
- hash->write (context, &v4, sizeof (v4));
|
|
- while (rem)
|
|
- {
|
|
- r = grub_file_read (sig, readbuf,
|
|
- rem < READBUF_SIZE ? rem : READBUF_SIZE);
|
|
- if (r < 0)
|
|
- goto fail;
|
|
- 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))
|
|
- goto fail;
|
|
- {
|
|
- grub_uint8_t *ptr;
|
|
- grub_uint32_t l;
|
|
- rem = grub_be_to_cpu16 (unhashed_sub);
|
|
- if (rem > READBUF_SIZE)
|
|
- goto fail;
|
|
- r = grub_file_read (sig, readbuf, rem);
|
|
- if (r != rem)
|
|
- goto fail;
|
|
- 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) << GRUB_CHAR_BIT) | 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))
|
|
- goto fail;
|
|
- if (grub_memcmp (hval, hash_start, sizeof (hash_start)) != 0)
|
|
- goto fail;
|
|
-
|
|
- 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_dprintf ("crypt", "alive\n");
|
|
- if (grub_file_read (sig, &l, sizeof (l)) != sizeof (l))
|
|
- goto fail;
|
|
- 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 > READBUF_SIZE - sizeof (grub_uint16_t))
|
|
- goto fail;
|
|
- grub_dprintf ("crypt", "alive\n");
|
|
- if (grub_file_read (sig, readbuf + sizeof (grub_uint16_t), lb) != (grub_ssize_t) lb)
|
|
- goto fail;
|
|
- grub_dprintf ("crypt", "alive\n");
|
|
- grub_memcpy (readbuf, &l, sizeof (l));
|
|
- grub_dprintf ("crypt", "alive\n");
|
|
-
|
|
- if (gcry_mpi_scan (&mpis[i], GCRYMPI_FMT_PGP,
|
|
- readbuf, lb + sizeof (grub_uint16_t), 0))
|
|
- goto fail;
|
|
- 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. */
|
|
- grub_error (GRUB_ERR_BAD_SIGNATURE, N_("public key %08x not found"),
|
|
- keyid);
|
|
- goto fail;
|
|
- }
|
|
-
|
|
- if (pkalgos[pk].pad (&hmpi, hval, hash, sk))
|
|
- goto fail;
|
|
- if (!*pkalgos[pk].algo)
|
|
- {
|
|
- grub_dl_load (pkalgos[pk].module);
|
|
- grub_errno = GRUB_ERR_NONE;
|
|
- }
|
|
-
|
|
- if (!*pkalgos[pk].algo)
|
|
- {
|
|
- grub_error (GRUB_ERR_BAD_SIGNATURE, N_("module `%s' isn't loaded"),
|
|
- pkalgos[pk].module);
|
|
- goto fail;
|
|
- }
|
|
- if ((*pkalgos[pk].algo)->verify (0, hmpi, mpis, sk->mpis, 0, 0))
|
|
- goto fail;
|
|
-
|
|
- grub_free (context);
|
|
- grub_free (readbuf);
|
|
-
|
|
- return GRUB_ERR_NONE;
|
|
-
|
|
- fail:
|
|
- grub_free (context);
|
|
- grub_free (readbuf);
|
|
- if (!grub_errno)
|
|
- return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature"));
|
|
+ ctxt->hash_context = grub_zalloc (ctxt->hash->contextsize);
|
|
+ if (!ctxt->hash_context)
|
|
return grub_errno;
|
|
+
|
|
+ ctxt->hash->init (ctxt->hash_context);
|
|
+
|
|
+ return GRUB_ERR_NONE;
|
|
+}
|
|
+
|
|
+static grub_err_t
|
|
+grub_pubkey_write (void *ctxt_, void *buf, grub_size_t size)
|
|
+{
|
|
+ struct grub_pubkey_context *ctxt = ctxt_;
|
|
+ ctxt->hash->write (ctxt->hash_context, buf, size);
|
|
+ return GRUB_ERR_NONE;
|
|
+}
|
|
+
|
|
+static grub_err_t
|
|
+grub_verify_signature_real (struct grub_pubkey_context *ctxt,
|
|
+ struct grub_public_key *pkey)
|
|
+{
|
|
+ gcry_mpi_t mpis[10];
|
|
+ grub_uint8_t pk = ctxt->v4.pkeyalgo;
|
|
+ grub_size_t i;
|
|
+ grub_uint8_t *readbuf = NULL;
|
|
+ unsigned char *hval;
|
|
+ grub_ssize_t rem = grub_be_to_cpu16 (ctxt->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;
|
|
+
|
|
+ readbuf = grub_malloc (READBUF_SIZE);
|
|
+ if (!readbuf)
|
|
+ goto fail;
|
|
+
|
|
+ ctxt->hash->write (ctxt->hash_context, &ctxt->v, sizeof (ctxt->v));
|
|
+ ctxt->hash->write (ctxt->hash_context, &ctxt->v4, sizeof (ctxt->v4));
|
|
+ while (rem)
|
|
+ {
|
|
+ r = grub_file_read (ctxt->sig, readbuf,
|
|
+ rem < READBUF_SIZE ? rem : READBUF_SIZE);
|
|
+ if (r < 0)
|
|
+ goto fail;
|
|
+ if (r == 0)
|
|
+ break;
|
|
+ ctxt->hash->write (ctxt->hash_context, readbuf, r);
|
|
+ rem -= r;
|
|
+ }
|
|
+ ctxt->hash->write (ctxt->hash_context, &ctxt->v, sizeof (ctxt->v));
|
|
+ s = 0xff;
|
|
+ ctxt->hash->write (ctxt->hash_context, &s, sizeof (s));
|
|
+ ctxt->hash->write (ctxt->hash_context, &headlen, sizeof (headlen));
|
|
+ r = grub_file_read (ctxt->sig, &unhashed_sub, sizeof (unhashed_sub));
|
|
+ if (r != sizeof (unhashed_sub))
|
|
+ goto fail;
|
|
+ {
|
|
+ grub_uint8_t *ptr;
|
|
+ grub_uint32_t l;
|
|
+ rem = grub_be_to_cpu16 (unhashed_sub);
|
|
+ if (rem > READBUF_SIZE)
|
|
+ goto fail;
|
|
+ r = grub_file_read (ctxt->sig, readbuf, rem);
|
|
+ if (r != rem)
|
|
+ goto fail;
|
|
+ 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) << GRUB_CHAR_BIT) | 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);
|
|
+ }
|
|
}
|
|
+
|
|
+ ctxt->hash->final (ctxt->hash_context);
|
|
+
|
|
+ grub_dprintf ("crypt", "alive\n");
|
|
+
|
|
+ hval = ctxt->hash->read (ctxt->hash_context);
|
|
+
|
|
+ if (grub_file_read (ctxt->sig, hash_start, sizeof (hash_start)) != sizeof (hash_start))
|
|
+ goto fail;
|
|
+ if (grub_memcmp (hval, hash_start, sizeof (hash_start)) != 0)
|
|
+ goto fail;
|
|
+
|
|
+ grub_dprintf ("crypt", "@ %x\n", (int)grub_file_tell (ctxt->sig));
|
|
+
|
|
+ for (i = 0; i < pkalgos[pk].nmpisig; i++)
|
|
+ {
|
|
+ grub_uint16_t l;
|
|
+ grub_size_t lb;
|
|
+ grub_dprintf ("crypt", "alive\n");
|
|
+ if (grub_file_read (ctxt->sig, &l, sizeof (l)) != sizeof (l))
|
|
+ goto fail;
|
|
+ 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 > READBUF_SIZE - sizeof (grub_uint16_t))
|
|
+ goto fail;
|
|
+ grub_dprintf ("crypt", "alive\n");
|
|
+ if (grub_file_read (ctxt->sig, readbuf + sizeof (grub_uint16_t), lb) != (grub_ssize_t) lb)
|
|
+ goto fail;
|
|
+ grub_dprintf ("crypt", "alive\n");
|
|
+ grub_memcpy (readbuf, &l, sizeof (l));
|
|
+ grub_dprintf ("crypt", "alive\n");
|
|
+
|
|
+ if (gcry_mpi_scan (&mpis[i], GCRYMPI_FMT_PGP,
|
|
+ readbuf, lb + sizeof (grub_uint16_t), 0))
|
|
+ goto fail;
|
|
+ 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. */
|
|
+ grub_error (GRUB_ERR_BAD_SIGNATURE, N_("public key %08x not found"),
|
|
+ keyid);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ if (pkalgos[pk].pad (&hmpi, hval, ctxt->hash, sk))
|
|
+ goto fail;
|
|
+ if (!*pkalgos[pk].algo)
|
|
+ {
|
|
+ grub_dl_load (pkalgos[pk].module);
|
|
+ grub_errno = GRUB_ERR_NONE;
|
|
+ }
|
|
+
|
|
+ if (!*pkalgos[pk].algo)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_SIGNATURE, N_("module `%s' isn't loaded"),
|
|
+ pkalgos[pk].module);
|
|
+ goto fail;
|
|
+ }
|
|
+ if ((*pkalgos[pk].algo)->verify (0, hmpi, mpis, sk->mpis, 0, 0))
|
|
+ goto fail;
|
|
+
|
|
+ grub_free (readbuf);
|
|
+
|
|
+ return GRUB_ERR_NONE;
|
|
+
|
|
+ fail:
|
|
+ grub_free (readbuf);
|
|
+ if (!grub_errno)
|
|
+ return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("bad signature"));
|
|
+ return grub_errno;
|
|
+}
|
|
+
|
|
+static void
|
|
+grub_pubkey_close_real (struct grub_pubkey_context *ctxt)
|
|
+{
|
|
+ if (ctxt->sig)
|
|
+ grub_file_close (ctxt->sig);
|
|
+ if (ctxt->hash_context)
|
|
+ grub_free (ctxt->hash_context);
|
|
+}
|
|
+
|
|
+static void
|
|
+grub_pubkey_close (void *ctxt)
|
|
+{
|
|
+ grub_pubkey_close_real (ctxt);
|
|
+ grub_free (ctxt);
|
|
}
|
|
|
|
grub_err_t
|
|
grub_verify_signature (grub_file_t f, grub_file_t sig,
|
|
struct grub_public_key *pkey)
|
|
{
|
|
- return grub_verify_signature_real (0, 0, f, sig, pkey);
|
|
+ grub_err_t err;
|
|
+ struct grub_pubkey_context ctxt;
|
|
+ grub_uint8_t *readbuf = NULL;
|
|
+
|
|
+ err = grub_verify_signature_init (&ctxt, sig);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ readbuf = grub_zalloc (READBUF_SIZE);
|
|
+ if (!readbuf)
|
|
+ goto fail;
|
|
+
|
|
+ while (1)
|
|
+ {
|
|
+ grub_ssize_t r;
|
|
+ r = grub_file_read (f, readbuf, READBUF_SIZE);
|
|
+ if (r < 0)
|
|
+ goto fail;
|
|
+ if (r == 0)
|
|
+ break;
|
|
+ err = grub_pubkey_write (&ctxt, readbuf, r);
|
|
+ if (err)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ grub_verify_signature_real (&ctxt, pkey);
|
|
+ fail:
|
|
+ grub_pubkey_close_real (&ctxt);
|
|
+ return grub_errno;
|
|
}
|
|
|
|
static grub_err_t
|
|
@@ -819,134 +867,52 @@ grub_cmd_verify_signature (grub_extcmd_context_t ctxt,
|
|
|
|
static int sec = 0;
|
|
|
|
-static void
|
|
-verified_free (grub_verified_t verified)
|
|
-{
|
|
- if (verified)
|
|
- {
|
|
- grub_free (verified->buf);
|
|
- grub_free (verified);
|
|
- }
|
|
-}
|
|
-
|
|
-static grub_ssize_t
|
|
-verified_read (struct grub_file *file, char *buf, grub_size_t len)
|
|
-{
|
|
- grub_verified_t verified = file->data;
|
|
-
|
|
- grub_memcpy (buf, (char *) verified->buf + file->offset, len);
|
|
- return len;
|
|
-}
|
|
-
|
|
static grub_err_t
|
|
-verified_close (struct grub_file *file)
|
|
-{
|
|
- grub_verified_t verified = file->data;
|
|
-
|
|
- grub_file_close (verified->file);
|
|
- verified_free (verified);
|
|
- file->data = 0;
|
|
-
|
|
- /* device and name are freed by parent */
|
|
- file->device = 0;
|
|
- file->name = 0;
|
|
-
|
|
- return grub_errno;
|
|
-}
|
|
-
|
|
-struct grub_fs verified_fs =
|
|
-{
|
|
- .name = "verified_read",
|
|
- .read = verified_read,
|
|
- .close = verified_close
|
|
-};
|
|
-
|
|
-static grub_file_t
|
|
-grub_pubkey_open (grub_file_t io, enum grub_file_type type)
|
|
+grub_pubkey_init (grub_file_t io, enum grub_file_type type __attribute__ ((unused)),
|
|
+ void **context, enum grub_verify_flags *flags)
|
|
{
|
|
grub_file_t sig;
|
|
char *fsuf, *ptr;
|
|
grub_err_t err;
|
|
- grub_file_t ret;
|
|
- grub_verified_t verified;
|
|
-
|
|
- if ((type & GRUB_FILE_TYPE_MASK) == GRUB_FILE_TYPE_SIGNATURE
|
|
- || (type & GRUB_FILE_TYPE_MASK) == GRUB_FILE_TYPE_VERIFY_SIGNATURE
|
|
- || (type & GRUB_FILE_TYPE_SKIP_SIGNATURE))
|
|
- return io;
|
|
+ struct grub_pubkey_context *ctxt;
|
|
|
|
if (!sec)
|
|
- return io;
|
|
- if (io->device->disk &&
|
|
- (io->device->disk->dev->id == GRUB_DISK_DEVICE_MEMDISK_ID
|
|
- || io->device->disk->dev->id == GRUB_DISK_DEVICE_PROCFS_ID))
|
|
- return io;
|
|
+ {
|
|
+ *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION;
|
|
+ return GRUB_ERR_NONE;
|
|
+ }
|
|
+
|
|
fsuf = grub_malloc (grub_strlen (io->name) + sizeof (".sig"));
|
|
if (!fsuf)
|
|
- return NULL;
|
|
+ return grub_errno;
|
|
ptr = grub_stpcpy (fsuf, io->name);
|
|
grub_memcpy (ptr, ".sig", sizeof (".sig"));
|
|
|
|
sig = grub_file_open (fsuf, GRUB_FILE_TYPE_SIGNATURE);
|
|
grub_free (fsuf);
|
|
if (!sig)
|
|
- return NULL;
|
|
+ return grub_errno;
|
|
|
|
- ret = grub_malloc (sizeof (*ret));
|
|
- if (!ret)
|
|
+ ctxt = grub_malloc (sizeof (*ctxt));
|
|
+ if (!ctxt)
|
|
{
|
|
grub_file_close (sig);
|
|
- return NULL;
|
|
+ return grub_errno;
|
|
}
|
|
- *ret = *io;
|
|
-
|
|
- ret->fs = &verified_fs;
|
|
- ret->not_easily_seekable = 0;
|
|
- if (ret->size >> (sizeof (grub_size_t) * GRUB_CHAR_BIT - 1))
|
|
- {
|
|
- grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
|
- "big file signature isn't implemented yet");
|
|
- grub_file_close (sig);
|
|
- grub_free (ret);
|
|
- return NULL;
|
|
- }
|
|
- verified = grub_malloc (sizeof (*verified));
|
|
- if (!verified)
|
|
- {
|
|
- grub_file_close (sig);
|
|
- grub_free (ret);
|
|
- return NULL;
|
|
- }
|
|
- verified->buf = grub_malloc (ret->size);
|
|
- if (!verified->buf)
|
|
- {
|
|
- grub_file_close (sig);
|
|
- verified_free (verified);
|
|
- grub_free (ret);
|
|
- return NULL;
|
|
- }
|
|
- if (grub_file_read (io, verified->buf, ret->size) != (grub_ssize_t) ret->size)
|
|
- {
|
|
- if (!grub_errno)
|
|
- grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"),
|
|
- io->name);
|
|
- grub_file_close (sig);
|
|
- verified_free (verified);
|
|
- grub_free (ret);
|
|
- return NULL;
|
|
- }
|
|
-
|
|
- err = grub_verify_signature_real (verified->buf, ret->size, 0, sig, NULL);
|
|
- grub_file_close (sig);
|
|
+ err = grub_verify_signature_init (ctxt, sig);
|
|
if (err)
|
|
{
|
|
- verified_free (verified);
|
|
- grub_free (ret);
|
|
- return NULL;
|
|
+ grub_pubkey_close (ctxt);
|
|
+ return err;
|
|
}
|
|
- verified->file = io;
|
|
- ret->data = verified;
|
|
- return ret;
|
|
+ *context = ctxt;
|
|
+ return GRUB_ERR_NONE;
|
|
+}
|
|
+
|
|
+static grub_err_t
|
|
+grub_pubkey_fini (void *ctxt)
|
|
+{
|
|
+ return grub_verify_signature_real (ctxt, NULL);
|
|
}
|
|
|
|
static char *
|
|
@@ -970,8 +936,16 @@ struct grub_fs pseudo_fs =
|
|
{
|
|
.name = "pseudo",
|
|
.read = pseudo_read
|
|
-};
|
|
+ };
|
|
|
|
+struct grub_file_verifier grub_pubkey_verifier =
|
|
+ {
|
|
+ .name = "pgp",
|
|
+ .init = grub_pubkey_init,
|
|
+ .fini = grub_pubkey_fini,
|
|
+ .write = grub_pubkey_write,
|
|
+ .close = grub_pubkey_close,
|
|
+ };
|
|
|
|
static grub_extcmd_t cmd, cmd_trust;
|
|
static grub_command_t cmd_distrust, cmd_list;
|
|
@@ -986,8 +960,6 @@ GRUB_MOD_INIT(verify)
|
|
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");
|
|
@@ -1033,11 +1005,13 @@ GRUB_MOD_INIT(verify)
|
|
cmd_distrust = grub_register_command ("distrust", grub_cmd_distrust,
|
|
N_("PUBKEY_ID"),
|
|
N_("Remove PUBKEY_ID from trusted keys."));
|
|
+
|
|
+ grub_verifier_register (&grub_pubkey_verifier);
|
|
}
|
|
|
|
GRUB_MOD_FINI(verify)
|
|
{
|
|
- grub_file_filter_unregister (GRUB_FILE_FILTER_PUBKEY);
|
|
+ grub_verifier_unregister (&grub_pubkey_verifier);
|
|
grub_unregister_extcmd (cmd);
|
|
grub_unregister_extcmd (cmd_trust);
|
|
grub_unregister_command (cmd_list);
|
|
diff --git a/include/grub/file.h b/include/grub/file.h
|
|
index 5b47c5f91f9..19dda67f68b 100644
|
|
--- a/include/grub/file.h
|
|
+++ b/include/grub/file.h
|
|
@@ -171,7 +171,7 @@ extern grub_disk_read_hook_t EXPORT_VAR(grub_file_progress_hook);
|
|
/* Filters with lower ID are executed first. */
|
|
typedef enum grub_file_filter_id
|
|
{
|
|
- GRUB_FILE_FILTER_PUBKEY,
|
|
+ GRUB_FILE_FILTER_VERIFY,
|
|
GRUB_FILE_FILTER_GZIO,
|
|
GRUB_FILE_FILTER_XZIO,
|
|
GRUB_FILE_FILTER_LZOPIO,
|
|
diff --git a/include/grub/list.h b/include/grub/list.h
|
|
index d170ff6da02..b13acb96243 100644
|
|
--- a/include/grub/list.h
|
|
+++ b/include/grub/list.h
|
|
@@ -35,6 +35,7 @@ void EXPORT_FUNC(grub_list_push) (grub_list_t *head, grub_list_t item);
|
|
void EXPORT_FUNC(grub_list_remove) (grub_list_t item);
|
|
|
|
#define FOR_LIST_ELEMENTS(var, list) for ((var) = (list); (var); (var) = (var)->next)
|
|
+#define FOR_LIST_ELEMENTS_NEXT(var, list) for ((var) = (var)->next; (var); (var) = (var)->next)
|
|
#define FOR_LIST_ELEMENTS_SAFE(var, nxt, list) for ((var) = (list), (nxt) = ((var) ? (var)->next : 0); (var); (var) = (nxt), ((nxt) = (var) ? (var)->next : 0))
|
|
|
|
static inline void *
|
|
diff --git a/include/grub/verify.h b/include/grub/verify.h
|
|
new file mode 100644
|
|
index 00000000000..298120f5776
|
|
--- /dev/null
|
|
+++ b/include/grub/verify.h
|
|
@@ -0,0 +1,65 @@
|
|
+/*
|
|
+ * GRUB -- GRand Unified Bootloader
|
|
+ * Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
|
|
+ */
|
|
+
|
|
+#include <grub/file.h>
|
|
+#include <grub/list.h>
|
|
+
|
|
+enum grub_verify_flags
|
|
+ {
|
|
+ GRUB_VERIFY_FLAGS_SKIP_VERIFICATION = 1,
|
|
+ GRUB_VERIFY_FLAGS_SINGLE_CHUNK = 2
|
|
+ };
|
|
+
|
|
+struct grub_file_verifier
|
|
+{
|
|
+ struct grub_file_verifier *next;
|
|
+ struct grub_file_verifier **prev;
|
|
+
|
|
+ const char *name;
|
|
+
|
|
+ /*
|
|
+ * Check if file needs to be verified and set up context.
|
|
+ * init/read/fini is structured in the same way as hash interface.
|
|
+ */
|
|
+ grub_err_t (*init) (grub_file_t io, enum grub_file_type type,
|
|
+ void **context, enum grub_verify_flags *flags);
|
|
+
|
|
+ /*
|
|
+ * Right now we pass the whole file in one call but it may
|
|
+ * change in the future. If you insist on single buffer you
|
|
+ * need to set GRUB_VERIFY_FLAGS_SINGLE_CHUNK in verify_flags.
|
|
+ */
|
|
+ grub_err_t (*write) (void *context, void *buf, grub_size_t size);
|
|
+
|
|
+ grub_err_t (*fini) (void *context);
|
|
+ void (*close) (void *context);
|
|
+};
|
|
+
|
|
+extern struct grub_file_verifier *grub_file_verifiers;
|
|
+
|
|
+static inline void
|
|
+grub_verifier_register (struct grub_file_verifier *ver)
|
|
+{
|
|
+ grub_list_push (GRUB_AS_LIST_P (&grub_file_verifiers), GRUB_AS_LIST (ver));
|
|
+}
|
|
+
|
|
+static inline void
|
|
+grub_verifier_unregister (struct grub_file_verifier *ver)
|
|
+{
|
|
+ grub_list_remove (GRUB_AS_LIST (ver));
|
|
+}
|