From: Hernan Gatta <hegatta@linux.microsoft.com>
The TPM2 key protector is a module that enables the automatic retrieval
of a fully-encrypted disk's unlocking key from a TPM 2.0.
The theory of operation is such that the module accepts various
arguments, most of which are optional and therefore possess reasonable
defaults. One of these arguments is the keyfile/tpm2key parameter, which
is mandatory. There are two supported key formats:
1. Raw Sealed Key (--keyfile)
When sealing a key with TPM2_Create, the public portion of the sealed
key is stored in TPM2B_PUBLIC, and the private portion is in
TPM2B_PRIVATE. The raw sealed key glues the fully marshalled
TPM2B_PUBLIC and TPM2B_PRIVATE into one file.
2. TPM 2.0 Key (--tpm2key)
The following is the ASN.1 definition of TPM 2.0 Key File:
TPMPolicy ::= SEQUENCE {
CommandCode [0] EXPLICIT INTEGER
CommandPolicy [1] EXPLICIT OCTET STRING
}
TPMAuthPolicy ::= SEQUENCE {
Name [0] EXPLICIT UTF8STRING OPTIONAL
Policy [1] EXPLICIT SEQUENCE OF TPMPolicy
}
TPMKey ::= SEQUENCE {
type OBJECT IDENTIFIER
emptyAuth [0] EXPLICIT BOOLEAN OPTIONAL
policy [1] EXPLICIT SEQUENCE OF TPMPolicy OPTIONAL
secret [2] EXPLICIT OCTET STRING OPTIONAL
authPolicy [3] EXPLICIT SEQUENCE OF TPMAuthPolicy OPTIONAL
description [4] EXPLICIT UTF8String OPTIONAL,
rsaParent [5] EXPLICIT BOOLEAN OPTIONAL,
parent INTEGER
pubkey OCTET STRING
privkey OCTET STRING
}
The TPM2 key protector only expects a "sealed" key in DER encoding,
so 'type' is always 2.23.133.10.1.5, 'emptyAuth' is 'TRUE', and
'secret' is empty. 'policy' and 'authPolicy' are the possible policy
command sequences to construst the policy digest to unseal the key.
Similar to the raw sealed key, the public portion (TPM2B_PUBLIC) of
the sealed key is stored in 'pubkey', and the private portion
(TPM2B_PRIVATE) is in 'privkey'.
For more details:
https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.html
This sealed key file is created via the grub-protect tool. The tool
utilizes the TPM's sealing functionality to seal (i.e., encrypt) an
unlocking key using a Storage Root Key (SRK) to the values of various
Platform Configuration Registers (PCRs). These PCRs reflect the state
of the system as it boots. If the values are as expected, the system
may be considered trustworthy, at which point the TPM allows for a
caller to utilize the private component of the SRK to unseal (i.e.,
decrypt) the sealed key file. The caller, in this case, is this key
protector.
The TPM2 key protector registers two commands:
- tpm2_key_protector_init: Initializes the state of the TPM2 key
protector for later usage, clearing any
previous state, too, if any.
- tpm2_key_protector_clear: Clears any state set by tpm2_key_protector_init.
The way this is expected to be used requires the user to, either
interactively or, normally, via a boot script, initialize/configure
the key protector and then specify that it be used by the 'cryptomount'
command (modifications to this command are in a different patch).
For instance, to unseal the raw sealed key file:
tpm2_key_protector_init --keyfile=(hd0,gpt1)/efi/grub/sealed-1.key
cryptomount -u <PART1_UUID> -P tpm2
tpm2_key_protector_init --keyfile=(hd0,gpt1)/efi/grub/sealed-2.key --pcrs=7,11
cryptomount -u <PART2_UUID> -P tpm2
Or, to unseal the TPM 2.0 Key file:
tpm2_key_protector_init --tpm2key=(hd0,gpt1)/efi/grub/sealed-1.tpm
cryptomount -u <PART1_UUID> -P tpm2
tpm2_key_protector_init --tpm2key=(hd0,gpt1)/efi/grub/sealed-2.tpm --pcrs=7,11
cryptomount -u <PART2_UUID> -P tpm2
If a user does not initialize the key protector and attempts to use it
anyway, the protector returns an error.
Before unsealing the key, the TPM2 key protector follows the "TPMPolicy"
sequences to enforce the TPM policy commands to construct a valid policy
digest to unseal the key.
For the TPM 2.0 Key files, 'authPolicy' may contain multiple "TPMPolicy"
sequences, the TPM2 key protector iterates 'authPolicy' to find a valid
sequence to unseal key. If 'authPolicy' is empty or all sequences in
'authPolicy' fail, the protector tries the one from 'policy'. In case
'policy' is also empty, the protector creates a "TPMPolicy" sequence
based on the given PCR selection.
For the raw sealed key, the TPM2 key protector treats the key file as a
TPM 2.0 Key file without 'authPolicy' and 'policy', so the "TPMPolicy"
sequence is always based on the PCR selection from the command
parameters.
This commit only supports one policy command: TPM2_PolicyPCR. The
command set will be extended to support advanced features, such as
authorized policy, in the later commits.
Cc: Stefan Berger <stefanb@linux.ibm.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Signed-off-by: Hernan Gatta <hegatta@linux.microsoft.com>
Signed-off-by: Gary Lin <glin@suse.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
grub-core/Makefile.core.def | 11 +
grub-core/commands/tpm2_key_protector/args.c | 127 ++
.../commands/tpm2_key_protector/module.c | 1153 +++++++++++++++++
grub-core/commands/tpm2_key_protector/tpm2.h | 36 +
.../commands/tpm2_key_protector/tpm2_args.h | 49 +
.../commands/tpm2_key_protector/tpm2key.asn | 49 +
.../commands/tpm2_key_protector/tpm2key.c | 499 +++++++
.../commands/tpm2_key_protector/tpm2key.h | 87 ++
.../tpm2_key_protector/tpm2key_asn1_tab.c | 63 +
9 files changed, 2074 insertions(+)
create mode 100644 grub-core/commands/tpm2_key_protector/args.c
create mode 100644 grub-core/commands/tpm2_key_protector/module.c
create mode 100644 grub-core/commands/tpm2_key_protector/tpm2.h
create mode 100644 grub-core/commands/tpm2_key_protector/tpm2_args.h
create mode 100644 grub-core/commands/tpm2_key_protector/tpm2key.asn
create mode 100644 grub-core/commands/tpm2_key_protector/tpm2key.c
create mode 100644 grub-core/commands/tpm2_key_protector/tpm2key.h
create mode 100644 grub-core/commands/tpm2_key_protector/tpm2key_asn1_tab.c
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 45b705a34..97ae4e49b 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -2578,6 +2578,17 @@ module = {
cppflags = '-I$(srcdir)/lib/tss2';
};
+module = {
+ name = tpm2_key_protector;
+ common = commands/tpm2_key_protector/args.c;
+ common = commands/tpm2_key_protector/module.c;
+ common = commands/tpm2_key_protector/tpm2key.c;
+ common = commands/tpm2_key_protector/tpm2key_asn1_tab.c;
+ /* The plaform support of tpm2_key_protector depends on the tcg2
implementation in tss2. */
+ enable = efi;
+ cppflags = '-I$(srcdir)/lib/tss2 -I$(srcdir)/lib/libtasn1-grub';
+};
+
module = {
name = tr;
common = commands/tr.c;
diff --git a/grub-core/commands/tpm2_key_protector/args.c
b/grub-core/commands/tpm2_key_protector/args.c
new file mode 100644
index 000000000..0e8bb6419
--- /dev/null
+++ b/grub-core/commands/tpm2_key_protector/args.c
@@ -0,0 +1,127 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2022 Microsoft Corporation
+ * Copyright (C) 2024 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/err.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+
+#include "tpm2_args.h"
+
+grub_err_t
+grub_tpm2_protector_parse_pcrs (char *value, grub_uint8_t *pcrs,
+ grub_uint8_t *pcr_count)
+{
+ char *current_pcr = value;
+ char *next_pcr;
+ const char *pcr_end;
+ grub_uint64_t pcr;
+ grub_uint8_t i;
+
+ if (grub_strlen (value) == 0)
+ return GRUB_ERR_BAD_ARGUMENT;
+
+ *pcr_count = 0;
+ for (i = 0; i < TPM_MAX_PCRS; i++)
+ {
+ next_pcr = grub_strchr (current_pcr, ',');
+ if (next_pcr == current_pcr)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("empty entry in PCR
list"));
+ if (next_pcr != NULL)
+ *next_pcr = '\0';
+
+ pcr = grub_strtoul (current_pcr, &pcr_end, 10);
+ if (*current_pcr == '\0' || *pcr_end != '\0')
+ return grub_error (GRUB_ERR_BAD_NUMBER, N_("entry '%s' in PCR list is not a
number"), current_pcr);
+
+ if (pcr > TPM_MAX_PCRS)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("entry %llu in PCR list is too
large to be a PCR number, PCR numbers range from 0 to %u"), (unsigned long long)pcr,
TPM_MAX_PCRS);