mirror of
https://git.sr.ht/~nabijaczleweli/tzpfms
synced 2025-04-11 09:30:02 +03:00
Add PCR binding. password => passphrase in manuals
All logically distinct modes are now: TPM1.X: passphraseless, PCRs, passphrase, passphrase & PCRs TPM2: passphraseless, PCRs, passphrase, passphrase | PCRs TPM2 sees a backward-incompatible ";pcr list" addition to its handle Cf. https://twitter.com/nabijaczleweli/status/1463707170793562117
This commit is contained in:
parent
de9b591546
commit
49f0a05c33
@ -8,6 +8,7 @@ packages:
|
||||
- pkg-config
|
||||
- libtss2-dev
|
||||
- libtspi-dev
|
||||
- libssl-dev
|
||||
- mandoc
|
||||
- shellcheck
|
||||
- curl
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -14,7 +14,7 @@
|
||||
!src/**
|
||||
!man
|
||||
!man/**
|
||||
!ext
|
||||
!ext/**
|
||||
!contrib
|
||||
!contrib/**
|
||||
!initrd
|
||||
!initrd/**
|
||||
|
6
Makefile
6
Makefile
@ -23,7 +23,7 @@
|
||||
include configMakefile
|
||||
|
||||
|
||||
LDDLLS := rt tspi $(OS_LD_LIBS)
|
||||
LDDLLS := rt tspi crypto $(OS_LD_LIBS)
|
||||
PKGS := libzfs libzfs_core tss2-esys tss2-rc
|
||||
LDAR := $(LNCXXAR) $(foreach l,,-L$(BLDDIR)$(l)) $(foreach dll,$(LDDLLS),-l$(dll)) $(shell pkg-config --libs $(PKGS))
|
||||
INCAR := $(foreach l,$(foreach l,,$(l)/include),-isystemext/$(l)) $(foreach l,,-isystem$(BLDDIR)$(l)/include) $(shell pkg-config --cflags $(PKGS))
|
||||
@ -99,10 +99,6 @@ $(OBJDIR)%$(OBJ) : $(SRCDIR)%.cpp
|
||||
@mkdir -p $(dir $@)
|
||||
$(CXX) $(CXXAR) $(INCAR) $(VERAR) $(DEF_TPH) -c -o$@ $^
|
||||
|
||||
$(BLDDIR)test/%$(OBJ) : $(TSTDIR)%.cpp
|
||||
@mkdir -p $(dir $@)
|
||||
$(CXX) $(CXXAR) $(INCAR) -I$(SRCDIR) $(VERAR) -c -o$@ $^
|
||||
|
||||
$(OUTDIR)dracut/usr/lib/dracut/modules.d/91tzpfms/% : $(INITRDDIR)dracut/% $(INITRD_HEADERS)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AWK) -f pp.awk $< > $@
|
||||
|
11
README.md
11
README.md
@ -18,13 +18,16 @@ Essentially BitLocker, but for ZFS –
|
||||
a random raw key is generated and sealed to the TPM (both 2 and 1.x supported) with an additional optional password in front of it,
|
||||
tying the dataset to the platform and an additional optional secret (or to the posession of the back-up).
|
||||
|
||||
Additionally, 1.x TPMs support PCR binding with and without passwords.
|
||||
2 TPMs support PCR binding without a password and PCR binding *OR* a password – both may be set, and any can be used to unseal (exclusive by default to prevent foot-guns).
|
||||
|
||||
Both dracut (with/without Plymouth) (with/without hostonly) (only on systemd systems, I don't have a test-bed for the non-systemd path)
|
||||
and initramfs-tools (with/without Plymouth) are supported for [ZFS-on-root](https://nabijaczleweli.xyz/content/blogn_t/005-low-curse-zfs-on-root.html) set-ups.
|
||||
|
||||
### Building
|
||||
|
||||
You'll need `pkg-config`, `shellcheck`, `libzfslinux-dev` (0.8.x and 2.[01].x work), `libtss2-dev`, `libtspi-dev`, and `make` should hopefully Just Work™ if you have a C++17-capable compiler.
|
||||
The output binaries are trimmed of extraneous dependencies, so they're all just libc + libzfs and friends + the chosen TPM back-end, if any.
|
||||
You'll need `pkg-config`, `shellcheck`, `libzfslinux-dev` (0.8.x and 2.[01].x work), `libtss2-dev`, `libtspi-dev`, `libssl-dev`, and `make` should hopefully Just Work™ if you have a C++17-capable compiler.
|
||||
The output binaries are trimmed of extraneous dependencies, so they're all just libc + libzfs and friends + the chosen TPM back-end, if any + libcrypto for TPM2 PCR handling.
|
||||
|
||||
`mandoc` is required for HTML manuals. Set `MANDOC=true` to forgo this.
|
||||
|
||||
@ -69,7 +72,7 @@ See the [repository README](//debian.nabijaczleweli.xyz/README) for more informa
|
||||
|
||||
Build [`swtpm`](//github.com/stefanberger/swtpm), then prepare and run it:
|
||||
```sh
|
||||
swtpm_setup --tpmstate tpm2-state --tpm2 --createek --display --logfile /dev/stdout --overwrite
|
||||
swtpm_setup --tpmstate tpm2-state --tpm2 --createek --display --logfile /dev/tty --overwrite
|
||||
swtpm socket --server type=tcp,port=2321 --ctrl type=tcp,port=2322 --tpm2 --tpmstate dir=tpm2-state --flags not-need-init --log level=10
|
||||
```
|
||||
|
||||
@ -82,7 +85,7 @@ ln -s /usr/lib/i386-linux-gnu/libtss2-tcti-{swtpm,default}.so
|
||||
Build [`swtpm`](//github.com/stefanberger/swtpm), then prepare and run it and
|
||||
([hopefully](//github.com/stefanberger/swtpm/issues/5#issuecomment-210607890)) [TrouSerS](//sourceforge.net/projects/trousers), as `root`/`tpm`:
|
||||
```sh
|
||||
swtpm_setup --tpmstate tpm1x-state --createek --display --logfile /dev/stdout --overwrite
|
||||
swtpm_setup --tpmstate tpm1x-state --createek --display --logfile /dev/tty --overwrite
|
||||
swtpm cuse -n tpm --tpmstate dir=tpm1x-state --seccomp action=none --log level=10,file=/dev/fd/4 4>&1
|
||||
swtpm_ioctl -i /dev/tpm
|
||||
TPM_DEVICE=/dev/tpm swtpm_bios
|
||||
|
@ -27,7 +27,7 @@ OS_LD_LIBS :=
|
||||
CXXVER := $(shell $(CXX) --version)
|
||||
ifneq "$(findstring clang,$(CXXVER))" ""
|
||||
# GCC doesn't have this granularity
|
||||
CXXSPECIFIC := -flto=full -pedantic -Wno-gnu-statement-expression -Wno-gnu-include-next -Wno-gnu-conditional-omitted-operand
|
||||
CXXSPECIFIC := -flto=full -pedantic -Wno-gnu-statement-expression -Wno-gnu-include-next -Wno-gnu-conditional-omitted-operand -Wno-c++20-designator
|
||||
else
|
||||
CXXSPECIFIC := -flto
|
||||
endif
|
||||
@ -62,6 +62,5 @@ OUTDIR := out/
|
||||
BLDDIR := out/build/
|
||||
OBJDIR := $(BLDDIR)obj/
|
||||
SRCDIR := src/
|
||||
TSTDIR := test/
|
||||
MANDIR := man/
|
||||
INITRDDIR := initrd/
|
||||
|
2
contrib/README
Normal file
2
contrib/README
Normal file
@ -0,0 +1,2 @@
|
||||
These are development aids, not for distribution.
|
||||
Link them to src/bin/ to build.
|
60
contrib/zfs-tpm1x-muddle-pcrs.cpp
Normal file
60
contrib/zfs-tpm1x-muddle-pcrs.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/random.h>
|
||||
|
||||
#include "../main.hpp"
|
||||
#include "../tpm1x.hpp"
|
||||
|
||||
|
||||
#define THIS_BACKEND "TPM1.X"
|
||||
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
uint32_t * pcrs{};
|
||||
size_t pcrs_len{};
|
||||
bool just_read{};
|
||||
return do_main(
|
||||
argc, argv, "RP:", "[-R] [-P PCR[,PCR]...]",
|
||||
[&](auto o) {
|
||||
switch(o) {
|
||||
case 'R':
|
||||
return just_read = true, 0;
|
||||
case 'P':
|
||||
return tpm1x_parse_pcrs(optarg, pcrs, pcrs_len);
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
},
|
||||
[&](auto) {
|
||||
return with_tpm1x_session([&](auto ctx, auto, auto) {
|
||||
TSS_HTPM tpm_h{};
|
||||
TRY_TPM1X("extract TPM from context", Tspi_Context_GetTpmObject(ctx, &tpm_h));
|
||||
|
||||
|
||||
for(size_t i = 0; i < pcrs_len; i++) {
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof(buf), "muddle PCR %" PRIu32 "", pcrs[i]);
|
||||
|
||||
BYTE * val{};
|
||||
uint32_t val_len{};
|
||||
|
||||
if(just_read)
|
||||
TRY_TPM1X(buf, Tspi_TPM_PcrRead(tpm_h, pcrs[i], &val_len, &val));
|
||||
else {
|
||||
BYTE data[TPM_SHA1_160_HASH_LEN];
|
||||
getrandom(data, sizeof(data), 0);
|
||||
TRY_TPM1X(buf, Tspi_TPM_PcrExtend(tpm_h, pcrs[i], sizeof(data), data, nullptr, &val_len, &val));
|
||||
}
|
||||
|
||||
|
||||
printf("PCR%u: ", pcrs[i]);
|
||||
for(auto i = 0u; i < val_len; ++i)
|
||||
printf("%02hhX", ((uint8_t *)val)[i]);
|
||||
printf("\n");
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
}
|
@ -31,5 +31,6 @@ and the documentation at
|
||||
.Lk https:/\&/tpm2-tss.readthedocs.io .
|
||||
.Pp
|
||||
The TPM 2.0 specifications, mainly at
|
||||
.Lk https:/\&/trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf
|
||||
.Lk https:/\&/trustedcomputinggroup.org/resource/tpm-library-specification/ ,
|
||||
.Lk https:/\&/trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf ,
|
||||
and related pages.
|
||||
|
@ -10,6 +10,7 @@
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl b Ar backup-file
|
||||
.Op Fl P Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns …
|
||||
.Ar dataset
|
||||
.
|
||||
.Sh DESCRIPTION
|
||||
@ -62,7 +63,7 @@ tools
|
||||
.Li tzpfms.key
|
||||
is a colon-separated pair of hexadecimal-string (i.e. "4F7730" for "Ow0") blobs;
|
||||
the first one represents the RSA key protecting the blob,
|
||||
and it is protected with either the password, if provided, or the SHA1 constant
|
||||
and it is protected with either the passphrase, if provided, or the SHA1 constant
|
||||
.Li CE4CF677875B5EB8993591D5A9AF1ED24A3A8736 ;
|
||||
the second represents the sealed object containing the wrapping key,
|
||||
and is protected with the SHA1 constant
|
||||
@ -79,13 +80,13 @@ or to issue a note for manual intervention into the standard error stream.
|
||||
A final verification should be made by running
|
||||
.Nm zfs-tpm1x-load-key Fl n Ar dataset .
|
||||
If that command succeeds, all is well,
|
||||
but otherwise the dataset can be manually rolled back to a password with
|
||||
but otherwise the dataset can be manually rolled back to a passphrase with
|
||||
.Nm zfs-tpm1x-clear-key Ar dataset
|
||||
.Pq or, if that fails to work, Nm zfs Cm change-key Fl o Li keyformat=passphrase Ar dataset ,
|
||||
and you are hereby asked to report a bug, please.
|
||||
.Pp
|
||||
.Nm zfs-tpm1x-clear-key Ar dataset
|
||||
can be used to clear the properties and go back to using a password.
|
||||
can be used to clear the properties and go back to using a passphrase.
|
||||
.
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -compact -width "-b backup-file"
|
||||
@ -98,6 +99,15 @@ This back-up
|
||||
be stored securely, off-site.
|
||||
In case of a catastrophic event, the key can be loaded by running
|
||||
.Dl Nm zfs Cm load-key Ar dataset Li < Ar backup-file
|
||||
.Pp
|
||||
.
|
||||
.It Fl P Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns …
|
||||
Bind the key to space- or comma-separated
|
||||
.Ar PCR Ns s
|
||||
\(em if they change, the wrapping key will not be able to be unsealed.
|
||||
The minimum amount of PCRs for a PC TPM is
|
||||
.Sy 24 Pq numbered Sy 0 Ns .. Ns Sy 23 .
|
||||
For most, this is also the maximum.
|
||||
.El
|
||||
.
|
||||
#include "passphrase.h"
|
||||
@ -105,3 +115,11 @@ In case of a catastrophic event, the key can be loaded by running
|
||||
#include "backend-tpm1x.h"
|
||||
.
|
||||
#include "common.h"
|
||||
.
|
||||
.Sh SEE ALSO
|
||||
.\" Match this to zfs-tpm2-change-key.8:
|
||||
PCR allocations:
|
||||
.Lk https:/\&/wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers
|
||||
and
|
||||
.Lk https:/\&/trustedcomputinggroup.org/wp-content/uploads/PC-ClientSpecific_Platform_Profile_for_TPM_2p0_Systems_v51.pdf ,
|
||||
Section 2.3.4 "PCR Usage", Table 1.
|
||||
|
@ -10,6 +10,10 @@
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl b Ar backup-file
|
||||
.Oo
|
||||
.Fl P Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns … Ns Oo Cm + Ns Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns … Oc Ns …
|
||||
.Op Fl A
|
||||
.Oc
|
||||
.Ar dataset
|
||||
.
|
||||
.Sh DESCRIPTION
|
||||
@ -49,7 +53,7 @@ The following properties are set on
|
||||
.It
|
||||
.Li xyz.nabijaczleweli:tzpfms.backend Ns = Ns Sy TPM2
|
||||
.It
|
||||
.Li xyz.nabijaczleweli:tzpfms.key Ns = Ns Ar ID of persistent object
|
||||
.Li xyz.nabijaczleweli:tzpfms.key Ns = Ns Ar persistent-object-ID Ns Op Cm ;\& Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns … Ns Oo Cm + Ns Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns … Oc Ns …
|
||||
.El
|
||||
.Pp
|
||||
.Li tzpfms.backend
|
||||
@ -60,10 +64,17 @@ tools
|
||||
.Pq namely Xr zfs-tpm2-change-key 8 , Xr zfs-tpm2-load-key 8 , and Xr zfs-tpm2-clear-key 8 .
|
||||
.Pp
|
||||
.Li tzpfms.key
|
||||
is an integer representing the sealed object;
|
||||
is an integer representing the sealed object, optionally followed by a semicolon and PCR list as specified with
|
||||
.Fl P ,
|
||||
normalised to be
|
||||
.Nm tpm-tools Ns -toolchain-compatible ;
|
||||
if needed, it can be passed to
|
||||
.Nm tpm2_unseal Fl c Ev ${tzpfms.key} Op Fl p Ev ${password}
|
||||
or equivalent for back-up
|
||||
.Nm tpm2_unseal Fl c Ev ${tzpfms.key Ns Cm %% Ns Li ;* Ns Ev }\&
|
||||
with
|
||||
.Fl p Qq Li str:\& Ns Ev ${passphrase}
|
||||
or
|
||||
.Fl p Qq Li pcr:\& Ns Ev ${tzpfms.key Ns Cm # Ns Li *; Ns Ev }\& ,
|
||||
as the case may be, or equivalent, for back-up
|
||||
.Pq see Sx OPTIONS .
|
||||
If you have a sealed key you can access with that or equivalent tool and set both of these properties, it will funxion seamlessly.
|
||||
.Pp
|
||||
@ -76,13 +87,13 @@ or to issue a note for manual intervention into the standard error stream.
|
||||
A final verification should be made by running
|
||||
.Nm zfs-tpm2-load-key Fl n Ar dataset .
|
||||
If that command succeeds, all is well,
|
||||
but otherwise the dataset can be manually rolled back to a password with
|
||||
but otherwise the dataset can be manually rolled back to a passphrase with
|
||||
.Nm zfs-tpm2-clear-key Ar dataset
|
||||
.Pq or, if that fails to work, Nm zfs Cm change-key Fl o Li keyformat=passphrase Ar dataset ,
|
||||
and you are hereby asked to report a bug, please.
|
||||
.Pp
|
||||
.Nm zfs-tpm2-clear-key Ar dataset
|
||||
can be used to free the TPM persistent object and go back to using a password.
|
||||
can be used to free the TPM persistent object and go back to using a passphrase.
|
||||
.
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -compact -width "-b backup-file"
|
||||
@ -95,6 +106,48 @@ This back-up
|
||||
be stored securely, off-site.
|
||||
In case of a catastrophic event, the key can be loaded by running
|
||||
.Dl Nm zfs Cm load-key Ar dataset Li < Ar backup-file
|
||||
.Pp
|
||||
.
|
||||
.It Fl P Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns … Ns Oo Cm + Ns Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns … Oc Ns …
|
||||
Bind the key to space- or comma-separated
|
||||
.Ar PCR Ns s
|
||||
within their corresponding hashing
|
||||
.Ar algorithm
|
||||
\(em if they change, the wrapping key will not be able to be unsealed.
|
||||
There are
|
||||
.Sy 24
|
||||
PCRs, numbered
|
||||
.Sy 0 Ns .. Ns Sy 23 .
|
||||
.Pp
|
||||
.Ar algorithm
|
||||
may be any of case-insensitive
|
||||
.Qq Sy sha1 ,
|
||||
.Qq Sy sha256 ,
|
||||
.Qq Sy sha384 ,
|
||||
.Qq Sy sha512 ,
|
||||
.Qq Sy sm3_256 ,
|
||||
.Qq Sy sm3-256 ,
|
||||
.Qq Sy sha3_256 ,
|
||||
.Qq Sy sha3-256 ,
|
||||
.Qq Sy sha3_384 ,
|
||||
.Qq Sy sha3-384 ,
|
||||
.Qq Sy sha3_512 ,
|
||||
or
|
||||
.Qq Sy sha3-512 ,
|
||||
and must be supported by the TPM.
|
||||
.Pp
|
||||
.
|
||||
.It Fl A
|
||||
With
|
||||
.Fl P ,
|
||||
also prompt for a passphrase.
|
||||
This is skipped by default because the passphrase is
|
||||
.Em OR Ns ed
|
||||
with the PCR policy \(em the wrapping key can be unsealed
|
||||
.Em either
|
||||
passphraseless with the right PCRs
|
||||
.Em or
|
||||
with the passphrase, and this is usually not the intent.
|
||||
.El
|
||||
.
|
||||
#include "passphrase.h"
|
||||
@ -105,3 +158,10 @@ In case of a catastrophic event, the key can be loaded by running
|
||||
.
|
||||
.Sh SEE ALSO
|
||||
.Xr tpm2_unseal 1
|
||||
.Pp
|
||||
.\" Match this to zfs-tpm1x-change-key.8:
|
||||
PCR allocations:
|
||||
.Lk https:/\&/wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers
|
||||
and
|
||||
.Lk https:/\&/trustedcomputinggroup.org/wp-content/uploads/PC-ClientSpecific_Platform_Profile_for_TPM_2p0_Systems_v51.pdf ,
|
||||
Section 2.3.4 "PCR Usage", Table 1.
|
||||
|
@ -5,6 +5,7 @@
|
||||
// #include <sys/zio_crypt.h>
|
||||
#define WRAPPING_KEY_LEN 32
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "../fd.hpp"
|
||||
@ -23,8 +24,20 @@
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
const char * backup{};
|
||||
uint32_t * pcrs{};
|
||||
size_t pcrs_len{};
|
||||
return do_main(
|
||||
argc, argv, "b:", "[-b backup-file]", [&](auto) { backup = optarg; },
|
||||
argc, argv, "b:P:", "[-b backup-file] [-P PCR[,PCR]…]",
|
||||
[&](auto o) {
|
||||
switch(o) {
|
||||
case 'b':
|
||||
return backup = optarg, 0;
|
||||
case 'P':
|
||||
return tpm1x_parse_pcrs(optarg, pcrs, pcrs_len);
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
},
|
||||
[&](auto dataset) {
|
||||
REQUIRE_KEY_LOADED(dataset);
|
||||
|
||||
@ -37,6 +50,36 @@ int main(int argc, char ** argv) {
|
||||
TRY_TPM1X("extract TPM from context", Tspi_Context_GetTpmObject(ctx, &tpm_h));
|
||||
|
||||
|
||||
/// Do it early because it's a cmdline argument and to not ask for password if it fails
|
||||
TSS_HOBJECT bound_pcrs{};
|
||||
quickscope_wrapper bound_pcrs_deleter{[&] {
|
||||
if(bound_pcrs)
|
||||
Tspi_Context_CloseObject(ctx, bound_pcrs);
|
||||
}};
|
||||
if(pcrs_len) {
|
||||
auto has_big = std::find_if(pcrs, pcrs + pcrs_len, [](auto p) { return p > 15; }) != pcrs + pcrs_len;
|
||||
|
||||
TRY_TPM1X("create PCR list",
|
||||
Tspi_Context_CreateObject(ctx, TSS_OBJECT_TYPE_PCRS, has_big ? TSS_PCRS_STRUCT_INFO_LONG : TSS_PCRS_STRUCT_DEFAULT, &bound_pcrs));
|
||||
|
||||
for(size_t i = 0; i < pcrs_len; i++) {
|
||||
char buf[15 + 10 + 1]; // 4294967296
|
||||
snprintf(buf, sizeof(buf), "read PCR %" PRIu32 "", pcrs[i]);
|
||||
|
||||
BYTE * val{};
|
||||
uint32_t val_len{};
|
||||
TRY_TPM1X(buf, Tspi_TPM_PcrRead(tpm_h, pcrs[i], &val_len, &val));
|
||||
quickscope_wrapper bound_pcrs_deleter{[&] { Tspi_Context_FreeMemory(ctx, val); }};
|
||||
|
||||
snprintf(buf, sizeof(buf), "save PCR %" PRIu32 " value", pcrs[i]);
|
||||
TRY_TPM1X(buf, Tspi_PcrComposite_SetPcrValue(bound_pcrs, pcrs[i], val_len, val));
|
||||
}
|
||||
|
||||
if(has_big)
|
||||
TRY_TPM1X("set PCR locality", Tspi_PcrComposite_SetPcrLocality(bound_pcrs, TSS_LOCALITY_ALL));
|
||||
}
|
||||
|
||||
|
||||
uint8_t * wrap_key{};
|
||||
TRY_TPM1X("get random data from TPM", Tspi_TPM_GetRandom(tpm_h, WRAPPING_KEY_LEN, &wrap_key));
|
||||
if(backup)
|
||||
@ -91,12 +134,8 @@ int main(int argc, char ** argv) {
|
||||
Tspi_Context_CloseObject(ctx, sealed_object);
|
||||
}};
|
||||
|
||||
// This would need to replace the 0 below to handle PCRs
|
||||
// TSS_HOBJECT bound_pcrs{}; // See tpm_sealdata.c from src:tpm-tools for more on flags here
|
||||
// TRY_TPM1X("create PCR list", Tspi_Context_CreateObject(ctx, TSS_OBJECT_TYPE_PCRS, 0, &bound_pcrs));
|
||||
// quickscope_wrapper bound_pcrs_deleter{[&] { Tspi_Context_CloseObject(ctx, bound_pcrs); }};
|
||||
|
||||
TRY_TPM1X("seal wrapping key data", Tspi_Data_Seal(sealed_object, parent_key, WRAPPING_KEY_LEN, wrap_key, 0));
|
||||
TRY_TPM1X("seal wrapping key data", Tspi_Data_Seal(sealed_object, parent_key, WRAPPING_KEY_LEN, wrap_key, bound_pcrs));
|
||||
|
||||
|
||||
uint8_t * parent_key_blob{};
|
||||
@ -118,10 +157,10 @@ int main(int argc, char ** argv) {
|
||||
{
|
||||
auto cur = handle;
|
||||
for(auto i = 0u; i < parent_key_blob_len; ++i, cur += 2)
|
||||
sprintf(cur, "%02X", parent_key_blob[i]);
|
||||
sprintf(cur, "%02hhX", parent_key_blob[i]);
|
||||
*cur++ = ':';
|
||||
for(auto i = 0u; i < sealed_object_blob_len; ++i, cur += 2)
|
||||
sprintf(cur, "%02X", sealed_object_blob[i]);
|
||||
sprintf(cur, "%02hhX", sealed_object_blob[i]);
|
||||
*cur++ = '\0';
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ int main(int argc, char ** argv) {
|
||||
if(loaded_wrap_key_len != sizeof(wrap_key)) {
|
||||
fprintf(stderr, "Wrong sealed data length (%" PRIu32 " != %zu): ", loaded_wrap_key_len, sizeof(wrap_key));
|
||||
for(auto i = 0u; i < loaded_wrap_key_len; ++i)
|
||||
fprintf(stderr, "%02X", loaded_wrap_key[i]);
|
||||
fprintf(stderr, "%02hhX", loaded_wrap_key[i]);
|
||||
fprintf(stderr, "\n");
|
||||
return __LINE__;
|
||||
}
|
||||
|
@ -20,13 +20,28 @@
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
const char * backup{};
|
||||
TPML_PCR_SELECTION pcrs{};
|
||||
bool allow_PCR_or_pass{};
|
||||
return do_main(
|
||||
argc, argv, "b:", "[-b backup-file]", [&](auto) { backup = optarg; },
|
||||
argc, argv, "b:P:A", "[-b backup-file] [-P algorithm:PCR[,PCR]…[+algorithm:PCR[,PCR]…]… [-A]]",
|
||||
[&](auto o) {
|
||||
switch(o) {
|
||||
case 'b':
|
||||
return backup = optarg, 0;
|
||||
case 'P':
|
||||
return tpm2_parse_pcrs(optarg, pcrs);
|
||||
case 'A':
|
||||
return allow_PCR_or_pass = true, 0;
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
},
|
||||
[&](auto dataset) {
|
||||
REQUIRE_KEY_LOADED(dataset);
|
||||
|
||||
|
||||
// https://software.intel.com/content/www/us/en/develop/articles/code-sample-protecting-secret-data-and-keys-using-intel-platform-trust-technology.html
|
||||
// https://tpm2-software.github.io/2020/04/13/Disk-Encryption.html#pcr-policy-authentication---access-control-of-sealed-pass-phrase-on-tpm2-with-pcr-sealing
|
||||
// tssstartup
|
||||
// tpm2_createprimary -Q --hierarchy=o --key-context=prim.ctx
|
||||
// cat /tmp/sk | tpm2_create --hash-algorithm=sha256 --public=seal.pub --private=seal.priv --sealing-input=- --parent-context=prim.ctx
|
||||
@ -36,14 +51,26 @@ int main(int argc, char ** argv) {
|
||||
// persistent-handle: 0x81000001
|
||||
//
|
||||
// tpm2_unseal -Q --object-context=0x81000000
|
||||
//
|
||||
// For PCRs:
|
||||
// tpm2_startauthsession --session=session.ctx
|
||||
// tpm2_policypcr -S session.ctx -l 'sha512:7+sha256:10' -L 5-10.policy3
|
||||
// tpm2_flushcontext session.ctx; rm session.ctx
|
||||
// + tpm2_create{,primary} gain -l 'sha512:7+sha256:10', tpm2_create gains -L 5-10.policy3
|
||||
//
|
||||
// tpm2_unseal -p pcr:'sha512:7+sha256:10' --object-context=0x81000000
|
||||
// or, longhand:
|
||||
// tpm2_startauthsession --policy-session --session=session3.ctx
|
||||
// tpm2_policypcr --session=session3.ctx --pcr-list='sha512:7+sha256:10'
|
||||
// tpm2_unseal -p session:session3.ctx --object-context=0x81000000
|
||||
// tpm2_flushcontext session3.ctx; rm session3.ctx
|
||||
|
||||
return with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) {
|
||||
TRY_MAIN(verify_backend(dataset, THIS_BACKEND, [&](auto previous_handle_s) {
|
||||
TPMI_DH_PERSISTENT previous_handle{};
|
||||
if(!parse_uint(previous_handle_s, previous_handle))
|
||||
fprintf(stderr,
|
||||
"Couldn't parse previous persistent handle for dataset %s: %s. You might need to run \"tpm2_evictcontrol -c %s\" or equivalent!\n",
|
||||
zfs_get_name(dataset), strerror(errno), previous_handle_s);
|
||||
if(tpm2_parse_prop(zfs_get_name(dataset), previous_handle_s, previous_handle, nullptr))
|
||||
fprintf(stderr, "Couldn't parse previous persistent handle for dataset %s. You might need to run \"tpm2_evictcontrol -c %s\" or equivalent!\n",
|
||||
zfs_get_name(dataset), previous_handle_s);
|
||||
else {
|
||||
if(tpm2_free_persistent(tpm2_ctx, tpm2_session, previous_handle))
|
||||
fprintf(stderr,
|
||||
@ -60,8 +87,8 @@ int main(int argc, char ** argv) {
|
||||
if(backup)
|
||||
TRY_MAIN(write_exact(backup, wrap_key, sizeof(wrap_key), 0400));
|
||||
|
||||
TRY_MAIN(tpm2_seal(zfs_get_name(dataset), tpm2_ctx, tpm2_session, persistent_handle, tpm2_creation_metadata(zfs_get_name(dataset)), wrap_key,
|
||||
sizeof(wrap_key)));
|
||||
TRY_MAIN(tpm2_seal(zfs_get_name(dataset), tpm2_ctx, tpm2_session, persistent_handle, tpm2_creation_metadata(zfs_get_name(dataset)), pcrs,
|
||||
allow_PCR_or_pass, wrap_key, sizeof(wrap_key)));
|
||||
bool ok = false; // Try to free the persistent handle if we're unsuccessful in actually using it later on
|
||||
quickscope_wrapper persistent_clearer{[&] {
|
||||
if(!ok && tpm2_free_persistent(tpm2_ctx, tpm2_session, persistent_handle))
|
||||
@ -72,12 +99,10 @@ int main(int argc, char ** argv) {
|
||||
}};
|
||||
|
||||
{
|
||||
char persistent_handle_s[2 + sizeof(persistent_handle) * 2 + 1];
|
||||
if(auto written = snprintf(persistent_handle_s, sizeof(persistent_handle_s), "0x%" PRIX32, persistent_handle);
|
||||
written < 0 || written >= static_cast<int>(sizeof(persistent_handle_s))) {
|
||||
return fprintf(stderr, "Truncated persistent_handle name? %d/%zu\n", written, sizeof(persistent_handle_s)), __LINE__;
|
||||
}
|
||||
TRY_MAIN(set_key_props(dataset, THIS_BACKEND, persistent_handle_s));
|
||||
char * prop{};
|
||||
TRY_MAIN(tpm2_unparse_prop(persistent_handle, pcrs, &prop));
|
||||
quickscope_wrapper prop_deleter{[&] { free(prop); }};
|
||||
TRY_MAIN(set_key_props(dataset, THIS_BACKEND, prop));
|
||||
}
|
||||
|
||||
TRY_MAIN(change_key(dataset, wrap_key));
|
||||
@ -85,5 +110,10 @@ int main(int argc, char ** argv) {
|
||||
ok = true;
|
||||
return 0;
|
||||
});
|
||||
},
|
||||
[&]() {
|
||||
if(allow_PCR_or_pass && !pcrs.count)
|
||||
return __LINE__;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
@ -12,6 +12,6 @@ int main(int argc, char ** argv) {
|
||||
TPMI_DH_PERSISTENT persistent_handle{};
|
||||
return do_clear_main(
|
||||
argc, argv, THIS_BACKEND,
|
||||
[&](auto dataset, auto persistent_handle_s) { return tpm2_parse_handle(zfs_get_name(dataset), persistent_handle_s, persistent_handle); },
|
||||
[&](auto dataset, auto persistent_handle_s) { return tpm2_parse_prop(zfs_get_name(dataset), persistent_handle_s, persistent_handle, nullptr); },
|
||||
[&] { return with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) { return tpm2_free_persistent(tpm2_ctx, tpm2_session, persistent_handle); }); });
|
||||
}
|
||||
|
@ -25,12 +25,13 @@ int main(int argc, char ** argv) {
|
||||
TRY_MAIN(parse_key_props(dataset, THIS_BACKEND, handle_s));
|
||||
|
||||
TPMI_DH_PERSISTENT handle{};
|
||||
TRY_MAIN(tpm2_parse_handle(zfs_get_name(dataset), handle_s, handle));
|
||||
TPML_PCR_SELECTION pcrs{};
|
||||
TRY_MAIN(tpm2_parse_prop(zfs_get_name(dataset), handle_s, handle, &pcrs));
|
||||
|
||||
|
||||
uint8_t wrap_key[WRAPPING_KEY_LEN];
|
||||
TRY_MAIN(with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) {
|
||||
TRY_MAIN(tpm2_unseal(zfs_get_name(dataset), tpm2_ctx, tpm2_session, handle, wrap_key, sizeof(wrap_key)));
|
||||
TRY_MAIN(tpm2_unseal(zfs_get_name(dataset), tpm2_ctx, tpm2_session, handle, pcrs, wrap_key, sizeof(wrap_key)));
|
||||
return 0;
|
||||
}));
|
||||
|
||||
|
66
src/main.hpp
66
src/main.hpp
@ -19,8 +19,10 @@
|
||||
} while(0)
|
||||
|
||||
|
||||
template <class G, class M>
|
||||
int do_bare_main(int argc, char ** argv, const char * getoptions, const char * usage, const char * dataset_usage, G && getoptfn, M && main) {
|
||||
template <class G, class M, class V = int (*)()>
|
||||
static int do_bare_main(
|
||||
int argc, char ** argv, const char * getoptions, const char * usage, const char * dataset_usage, G && getoptfn, M && main,
|
||||
V && validate = []() { return 0; }) {
|
||||
const auto libz = TRY_PTR("initialise libzfs", libzfs_init());
|
||||
quickscope_wrapper libz_deleter{[=] { libzfs_fini(libz); }};
|
||||
|
||||
@ -39,47 +41,55 @@ int do_bare_main(int argc, char ** argv, const char * getoptions, const char * u
|
||||
fprintf(opt == 'h' ? stdout : stderr, "Usage: %s [-hV] %s%s%s\n", argv[0], usage, strlen(usage) ? " " : "", dataset_usage);
|
||||
return opt == 'h' ? 0 : __LINE__;
|
||||
case 'V':
|
||||
printf("tzpfms version %s\n", TZPFMS_VERSION);
|
||||
puts("tzpfms version " TZPFMS_VERSION);
|
||||
return 0;
|
||||
default:
|
||||
if constexpr(std::is_same_v<std::invoke_result_t<G, decltype(opt)>, void>)
|
||||
getoptfn(opt);
|
||||
else if constexpr(std::is_arithmetic_v<std::invoke_result_t<G, decltype(opt)>>)
|
||||
TRY_MAIN(getoptfn(opt));
|
||||
else {
|
||||
if(auto err = getoptfn(opt))
|
||||
return fprintf(stderr, "Usage: %s [-hV] %s%s%s\n", argv[0], usage, strlen(usage) ? " " : "", dataset_usage), err;
|
||||
}
|
||||
}
|
||||
|
||||
if(auto err = validate())
|
||||
return fprintf(stderr, "Usage: %s [-hV] %s%s%s\n", argv[0], usage, strlen(usage) ? " " : "", dataset_usage), err;
|
||||
return main(libz);
|
||||
}
|
||||
|
||||
template <class G, class M>
|
||||
int do_main(int argc, char ** argv, const char * getoptions, const char * usage, G && getoptfn, M && main) {
|
||||
return do_bare_main(argc, argv, getoptions, usage, "<dataset>", getoptfn, [&](auto libz) {
|
||||
if(optind >= argc)
|
||||
return fprintf(stderr,
|
||||
"No dataset to act on?\n"
|
||||
"Usage: %s [-hV] %s%s<dataset>\n",
|
||||
argv[0], usage, strlen(usage) ? " " : ""),
|
||||
__LINE__;
|
||||
auto dataset = TRY_PTR(nullptr, zfs_open(libz, argv[optind], ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME));
|
||||
quickscope_wrapper dataset_deleter{[&] { zfs_close(dataset); }};
|
||||
template <class G, class M, class V = int (*)()>
|
||||
static int do_main(
|
||||
int argc, char ** argv, const char * getoptions, const char * usage, G && getoptfn, M && main, V && validate = []() { return 0; }) {
|
||||
return do_bare_main(
|
||||
argc, argv, getoptions, usage, "dataset", getoptfn,
|
||||
[&](auto libz) {
|
||||
if(optind >= argc)
|
||||
return fprintf(stderr,
|
||||
"No dataset to act on?\n"
|
||||
"Usage: %s [-hV] %s%sdataset\n",
|
||||
argv[0], usage, strlen(usage) ? " " : ""),
|
||||
__LINE__;
|
||||
auto dataset = TRY_PTR(nullptr, zfs_open(libz, argv[optind], ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME));
|
||||
quickscope_wrapper dataset_deleter{[&] { zfs_close(dataset); }};
|
||||
|
||||
{
|
||||
char encryption_root[MAXNAMELEN];
|
||||
boolean_t dataset_is_root;
|
||||
TRY("get encryption root", zfs_crypto_get_encryption_root(dataset, &dataset_is_root, encryption_root));
|
||||
{
|
||||
char encryption_root[MAXNAMELEN];
|
||||
boolean_t dataset_is_root;
|
||||
TRY("get encryption root", zfs_crypto_get_encryption_root(dataset, &dataset_is_root, encryption_root));
|
||||
|
||||
if(!dataset_is_root && !strlen(encryption_root))
|
||||
return fprintf(stderr, "Dataset %s not encrypted?\n", zfs_get_name(dataset)), __LINE__;
|
||||
else if(!dataset_is_root) {
|
||||
fprintf(stderr, "Using dataset %s's encryption root %s instead.\n", zfs_get_name(dataset), encryption_root);
|
||||
zfs_close(dataset);
|
||||
dataset = TRY_PTR(nullptr, zfs_open(libz, encryption_root, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME));
|
||||
}
|
||||
}
|
||||
if(!dataset_is_root && !strlen(encryption_root))
|
||||
return fprintf(stderr, "Dataset %s not encrypted?\n", zfs_get_name(dataset)), __LINE__;
|
||||
else if(!dataset_is_root) {
|
||||
fprintf(stderr, "Using dataset %s's encryption root %s instead.\n", zfs_get_name(dataset), encryption_root);
|
||||
zfs_close(dataset);
|
||||
dataset = TRY_PTR(nullptr, zfs_open(libz, encryption_root, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return main(dataset);
|
||||
});
|
||||
return main(dataset);
|
||||
},
|
||||
validate);
|
||||
}
|
||||
|
@ -4,7 +4,9 @@
|
||||
#include "tpm1x.hpp"
|
||||
|
||||
#include "main.hpp"
|
||||
#include "parse.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
@ -80,6 +82,39 @@ int tpm1x_prep_sealed_object(TSS_HCONTEXT ctx, TSS_HOBJECT & sealed_object, TSS_
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-1-Design-Principles_v1.2_rev116_01032011.pdf sections 4.4.7, .8 (L1228-1236):
|
||||
/// > 7. A TPM implementation MUST provide 16 or more independent PCRs. These PCRs areidentified by index and MUST be numbered from 0 (that is, PCR0 through
|
||||
/// > PCR15 are required for TCG compliance). Vendors MAY implement more registers for general-purpose use. Extra registers MUST be numbered contiguously
|
||||
/// > from16 up to max – 1,where max is the maximum offered by the TPM
|
||||
/// > 8. The TCG-protected capabilities that expose and modify the PCRs use a 32-bit index,indicating the maximum usable PCR index. However, TCG reserves
|
||||
/// > register indices 230and higher for later versions of the specification. A TPM implementation MUST NOTprovide registers with indices greater than or
|
||||
/// > equal to 230.
|
||||
int tpm1x_parse_pcrs(char * arg, uint32_t *& pcrs, size_t & pcrs_len) {
|
||||
size_t out_cap = 16;
|
||||
pcrs = reinterpret_cast<uint32_t *>(TRY_PTR("allocate PCR list", calloc(out_cap, sizeof(uint32_t))));
|
||||
|
||||
char * sv{};
|
||||
for(arg = strtok_r(arg, ", ", &sv); arg; arg = strtok_r(nullptr, ", ", &sv)) {
|
||||
uint32_t pcr;
|
||||
if(!parse_uint(arg, pcr))
|
||||
return fprintf(stderr, "PCR %s: %s\n", arg, strerror(errno)), __LINE__;
|
||||
if(pcr >= 230)
|
||||
return fprintf(stderr, "PCR %s: too large (max 229).\n", arg), __LINE__;
|
||||
|
||||
auto idx = std::upper_bound(pcrs, pcrs + pcrs_len, pcr) - pcrs;
|
||||
if(!idx || pcrs[idx - 1] != pcr) {
|
||||
if(pcrs_len >= out_cap)
|
||||
pcrs = reinterpret_cast<uint32_t *>(TRY_PTR("allocate PCR list", reallocarray(pcrs, out_cap *= 2, sizeof(uint32_t))));
|
||||
|
||||
memmove(pcrs + idx + 1, pcrs + idx, (pcrs_len - idx) * sizeof(uint32_t));
|
||||
pcrs[idx] = pcr;
|
||||
++pcrs_len;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// This feels suboptimal somehow, and yet
|
||||
static int fromxchar(uint8_t & out, char c) {
|
||||
|
@ -102,3 +102,6 @@ extern int tpm1x_parse_handle(const char * dataset_name, char * handle_s, tpm1x_
|
||||
|
||||
/// Create sealed object, assign a policy and a known secret to it.
|
||||
extern int tpm1x_prep_sealed_object(TSS_HCONTEXT ctx, TSS_HOBJECT & sealed_object, TSS_HPOLICY & sealed_object_policy);
|
||||
|
||||
/// Parse a comma- or space-separated number list.
|
||||
extern int tpm1x_parse_pcrs(char * arg, uint32_t *& pcrs, size_t & pcrs_len);
|
||||
|
301
src/tpm2.cpp
301
src/tpm2.cpp
@ -8,6 +8,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <inttypes.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <optional>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
@ -39,11 +41,12 @@ static int try_or_passphrase(const char * what, const char * what_for, ESYS_CONT
|
||||
|
||||
|
||||
TPM2B_DATA tpm2_creation_metadata(const char * dataset_name) {
|
||||
TPM2B_DATA metadata{}; // 64 bytesish
|
||||
TPM2B_DATA metadata{}; // 64 bytesish
|
||||
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
metadata.size = snprintf((char *)metadata.buffer, sizeof(metadata.buffer), "%" PRIu64 ".%09ld %s %s", ts.tv_sec, ts.tv_nsec, dataset_name, TZPFMS_VERSION) + 1;
|
||||
metadata.size =
|
||||
snprintf((char *)metadata.buffer, sizeof(metadata.buffer), "%" PRIu64 ".%09ld %s %s", ts.tv_sec, ts.tv_nsec, dataset_name, TZPFMS_VERSION) + 1;
|
||||
metadata.size = metadata.size > sizeof(metadata.buffer) ? sizeof(metadata.buffer) : metadata.size;
|
||||
|
||||
// fprintf(stderr, "%" PRIu16 "/%zu: \"%s\"\n", metadata.size, sizeof(metadata.buffer), metadata.buffer);
|
||||
@ -51,10 +54,159 @@ TPM2B_DATA tpm2_creation_metadata(const char * dataset_name) {
|
||||
}
|
||||
|
||||
|
||||
int tpm2_parse_handle(const char * dataset_name, const char * handle_s, TPMI_DH_PERSISTENT & handle) {
|
||||
if(!parse_uint(handle_s, handle))
|
||||
int tpm2_parse_prop(const char * dataset_name, char * handle_s, TPMI_DH_PERSISTENT & handle, TPML_PCR_SELECTION * pcrs) {
|
||||
char * sv{};
|
||||
if(!parse_uint(handle_s = strtok_r(handle_s, ";", &sv), handle))
|
||||
return fprintf(stderr, "Dataset %s's handle %s: %s.\n", dataset_name, handle_s, strerror(errno)), __LINE__;
|
||||
|
||||
if(auto p = strtok_r(nullptr, ";", &sv); p && pcrs)
|
||||
TRY_MAIN(tpm2_parse_pcrs(p, *pcrs));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// Extension of the table used by tpm2-tools (tpm2_create et al.), which only has "s{m,ha}3_XXX", not "s{m,ha}3-XXX", and does case-sentitive comparisons
|
||||
#define TPM2_HASH_ALGS_MAX_NAME_LEN 8 // sha3_512
|
||||
static const constexpr struct tpm2_hash_algs_t {
|
||||
TPM2_ALG_ID alg;
|
||||
const char * names[2];
|
||||
} tpm2_hash_algs[] = {{TPM2_ALG_SHA1, {"sha1"}},
|
||||
{TPM2_ALG_SHA256, {"sha256"}},
|
||||
{TPM2_ALG_SHA384, {"sha384"}},
|
||||
{TPM2_ALG_SHA512, {"sha512"}},
|
||||
{TPM2_ALG_SM3_256, {"sm3_256", "sm3-256"}},
|
||||
{TPM2_ALG_SHA3_256, {"sha3_256", "sha3-256"}},
|
||||
{TPM2_ALG_SHA3_384, {"sha3_384", "sha3-384"}},
|
||||
{TPM2_ALG_SHA3_512, {"sha3_512", "sha3-512"}}};
|
||||
|
||||
static constexpr bool is_tpm2_hash_algs_sorted() {
|
||||
for(auto itr = std::begin(tpm2_hash_algs); itr != std::end(tpm2_hash_algs) - 1; ++itr)
|
||||
if((itr + 1)->alg < itr->alg)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
static_assert(is_tpm2_hash_algs_sorted()); // for the binary_search() below
|
||||
|
||||
/// Assuming always != end: we always parse first
|
||||
static const char * tpm2_hash_alg_name(TPM2_ALG_ID id) {
|
||||
return std::lower_bound(std::begin(tpm2_hash_algs), std::end(tpm2_hash_algs), tpm2_hash_algs_t{id, {}},
|
||||
[&](auto && lhs, auto && rhs) { return lhs.alg < rhs.alg; })
|
||||
->names[0];
|
||||
}
|
||||
|
||||
|
||||
/// Nominally:
|
||||
/// #define TPM2_MAX_PCRS 32
|
||||
/// #define TPM2_PCR_SELECT_MAX ((TPM2_MAX_PCRS + 7) / 8)
|
||||
/// and
|
||||
/// struct TPMS_PCR_SELECT {
|
||||
/// UINT8 sizeofSelect; /* the size in octets of the pcrSelect array */
|
||||
/// BYTE pcrSelect[TPM2_PCR_SELECT_MAX]; /* the bit map of selected PCR */
|
||||
/// };
|
||||
///
|
||||
/// This works out to TPM2_PCR_SELECT_MAX=4, but most (all?) TPM2s have only 24 PCRs, meaning *any* request with sizeofSelect=sizeof(pcrSelect)=4 fails with
|
||||
/// WARNING:esys:src/tss2-esys/api/Esys_CreatePrimary.c:393:Esys_CreatePrimary_Finish() Received TPM Error
|
||||
/// ERROR:esys:src/tss2-esys/api/Esys_CreatePrimary.c:135:Esys_CreatePrimary() Esys Finish ErrorCode (0x000004c4)
|
||||
/// Couldn't create primary encryption key: tpm:parameter(4):value is out of range or is not correct for the context
|
||||
///
|
||||
/// Follow tpm2-tools and pretend TPM2_MAX_PCRS=24 => TPM2_PCR_SELECT_MAX=3 => sizeofSelect=3.
|
||||
#define TPM2_MAX_PCRS_BUT_STRONGER 24
|
||||
#define TPM2_PCR_SELECT_MAX_BUT_STRONGER ((TPM2_MAX_PCRS_BUT_STRONGER + 7) / 8)
|
||||
static_assert(TPM2_PCR_SELECT_MAX_BUT_STRONGER <= sizeof(TPMS_PCR_SELECT::pcrSelect));
|
||||
|
||||
int tpm2_parse_pcrs(char * arg, TPML_PCR_SELECTION & pcrs) {
|
||||
TPMS_PCR_SELECTION * bank = pcrs.pcrSelections;
|
||||
|
||||
char * ph_sv{};
|
||||
for(auto per_hash = strtok_r(arg, "+", &ph_sv); per_hash; per_hash = strtok_r(nullptr, "+", &ph_sv), ++bank) {
|
||||
while(*per_hash == ' ')
|
||||
++per_hash;
|
||||
|
||||
if(bank == pcrs.pcrSelections + (sizeof(pcrs.pcrSelections) / sizeof(*pcrs.pcrSelections))) // == TPM2_NUM_PCR_BANKS
|
||||
return fprintf(stderr, "Too many PCR banks specified! Can only have up to %zu\n", sizeof(pcrs.pcrSelections) / sizeof(*pcrs.pcrSelections)), __LINE__;
|
||||
|
||||
if(auto sep = strchr(per_hash, ':')) {
|
||||
*sep = '\0';
|
||||
auto values = sep + 1;
|
||||
|
||||
if(auto alg = std::find_if(
|
||||
std::begin(tpm2_hash_algs), std::end(tpm2_hash_algs),
|
||||
[&](auto && alg) { return std::any_of(std::begin(alg.names), std::end(alg.names), [&](auto && nm) { return nm && !strcasecmp(per_hash, nm); }); });
|
||||
alg != std::end(tpm2_hash_algs))
|
||||
bank->hash = alg->alg;
|
||||
else {
|
||||
if(!parse_uint(per_hash, bank->hash) || !std::binary_search(std::begin(tpm2_hash_algs), std::end(tpm2_hash_algs), tpm2_hash_algs_t{bank->hash, {}},
|
||||
[&](auto && lhs, auto && rhs) { return lhs.alg < rhs.alg; })) {
|
||||
fprintf(stderr,
|
||||
"Unknown hash algorithm %s.\n"
|
||||
"Can be any of case-insensitive ",
|
||||
per_hash);
|
||||
auto first = true;
|
||||
for(auto && alg : tpm2_hash_algs)
|
||||
for(auto && nm : alg.names)
|
||||
if(nm)
|
||||
fprintf(stderr, "%s%s", first ? "" : ", ", nm), first = false;
|
||||
return fputs(".\n", stderr), __LINE__;
|
||||
}
|
||||
}
|
||||
|
||||
bank->sizeofSelect = TPM2_PCR_SELECT_MAX_BUT_STRONGER;
|
||||
if(!strcasecmp(values, "all"))
|
||||
memset(bank->pcrSelect, 0xFF, bank->sizeofSelect);
|
||||
else if(!strcasecmp(values, "none"))
|
||||
; // already 0
|
||||
else {
|
||||
char * sv{};
|
||||
for(values = strtok_r(values, ", ", &sv); values; values = strtok_r(nullptr, ", ", &sv)) {
|
||||
uint8_t pcr;
|
||||
if(!parse_uint(values, pcr))
|
||||
return fprintf(stderr, "PCR %s: %s\n", values, strerror(errno)), __LINE__;
|
||||
if(pcr > TPM2_MAX_PCRS_BUT_STRONGER - 1)
|
||||
return fprintf(stderr, "PCR %s: %s, max %u\n", values, strerror(ERANGE), TPM2_MAX_PCRS_BUT_STRONGER - 1), __LINE__;
|
||||
|
||||
bank->pcrSelect[pcr / 8] |= 1 << (pcr % 8);
|
||||
}
|
||||
}
|
||||
} else
|
||||
return fprintf(stderr, "PCR bank \"%s\": no algorithm; need alg:PCR[,PCR]...\n", per_hash), __LINE__;
|
||||
}
|
||||
|
||||
pcrs.count = bank - pcrs.pcrSelections;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tpm2_unparse_prop(TPMI_DH_PERSISTENT persistent_handle, const TPML_PCR_SELECTION & pcrs, char ** prop) {
|
||||
// 0xFFFFFFFF;sha3_512:00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22+sha3_...
|
||||
*prop = TRY_PTR("allocate property value",
|
||||
reinterpret_cast<char *>(malloc(2 + 8 + pcrs.count * (1 + TPM2_HASH_ALGS_MAX_NAME_LEN + (TPM2_MAX_PCRS_BUT_STRONGER - 1) * 3) + 1)));
|
||||
|
||||
auto cur = *prop;
|
||||
cur += sprintf(cur, "0x%" PRIX32 "", persistent_handle);
|
||||
|
||||
auto pre = ';';
|
||||
for(size_t i = 0; i < pcrs.count; ++i) {
|
||||
auto && sel = pcrs.pcrSelections[i];
|
||||
*cur++ = std::exchange(pre, '+');
|
||||
|
||||
auto nm = tpm2_hash_alg_name(sel.hash);
|
||||
auto nm_len = strlen(nm);
|
||||
memcpy(cur, nm, nm_len), cur += nm_len;
|
||||
|
||||
if(std::all_of(sel.pcrSelect, sel.pcrSelect + sel.sizeofSelect, [](auto b) { return b == 0x00; }))
|
||||
memcpy(cur, ":none", strlen(":none")), cur += strlen(":none");
|
||||
else if(std::all_of(sel.pcrSelect, sel.pcrSelect + sel.sizeofSelect, [](auto b) { return b == 0xFF; }))
|
||||
memcpy(cur, ":all", strlen(":all")), cur += strlen(":all");
|
||||
else {
|
||||
bool first = true;
|
||||
for(size_t j = 0; j < sel.sizeofSelect; ++j)
|
||||
for(uint8_t b = 0; b < 8; ++b)
|
||||
if(sel.pcrSelect[j] & (1 << b))
|
||||
cur += sprintf(cur, "%c%zu", std::exchange(first, false) ? ':' : ',', j * 8 + b);
|
||||
}
|
||||
}
|
||||
|
||||
*cur = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -62,7 +214,7 @@ int tpm2_parse_handle(const char * dataset_name, const char * handle_s, TPMI_DH_
|
||||
int tpm2_generate_rand(ESYS_CONTEXT * tpm2_ctx, void * into, size_t length) {
|
||||
TPM2B_DIGEST * rand{};
|
||||
TRY_TPM2("get random data from TPM", Esys_GetRandom(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, length, &rand));
|
||||
quickscope_wrapper rand_deleter{[=] { Esys_Free(rand); }};
|
||||
quickscope_wrapper rand_deleter{[&] { Esys_Free(rand); }};
|
||||
|
||||
if(rand->size != length)
|
||||
return fprintf(stderr, "Wrong random size: wanted %zu, got %" PRIu16 " bytes.\n", length, rand->size), __LINE__;
|
||||
@ -73,10 +225,10 @@ int tpm2_generate_rand(ESYS_CONTEXT * tpm2_ctx, void * into, size_t length) {
|
||||
|
||||
|
||||
static int tpm2_find_unused_persistent_non_platform(ESYS_CONTEXT * tpm2_ctx, TPMI_DH_PERSISTENT & persistent_handle) {
|
||||
TPMS_CAPABILITY_DATA * cap; // TODO: check for more data?
|
||||
TPMS_CAPABILITY_DATA * cap{}; // TODO: check for more data?
|
||||
TRY_TPM2("Read used persistent TPM handles", Esys_GetCapability(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_HANDLES, TPM2_PERSISTENT_FIRST,
|
||||
TPM2_MAX_CAP_HANDLES, nullptr, &cap));
|
||||
quickscope_wrapper cap_deleter{[=] { Esys_Free(cap); }};
|
||||
quickscope_wrapper cap_deleter{[&] { Esys_Free(cap); }};
|
||||
|
||||
persistent_handle = 0;
|
||||
switch(cap->data.handles.count) {
|
||||
@ -98,8 +250,69 @@ static int tpm2_find_unused_persistent_non_platform(ESYS_CONTEXT * tpm2_ctx, TPM
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
static int tpm2_police_pcrs(ESYS_CONTEXT * tpm2_ctx, const TPML_PCR_SELECTION & pcrs, TPM2_SE session_type, F && with_session) {
|
||||
if(!pcrs.count)
|
||||
return with_session(ESYS_TR_NONE);
|
||||
|
||||
TPM2B_DIGEST digested_pcrs{};
|
||||
digested_pcrs.size = SHA256_DIGEST_LENGTH;
|
||||
static_assert(sizeof(TPM2B_DIGEST::buffer) >= SHA256_DIGEST_LENGTH);
|
||||
|
||||
{
|
||||
SHA256_CTX ctx;
|
||||
new_pcrs:
|
||||
std::optional<uint32_t> update_count;
|
||||
SHA256_Init(&ctx);
|
||||
auto pcrs_left = pcrs;
|
||||
while(std::any_of(pcrs_left.pcrSelections, pcrs_left.pcrSelections + pcrs_left.count,
|
||||
[](auto && sel) { return std::any_of(sel.pcrSelect, sel.pcrSelect + sel.sizeofSelect, [](auto b) { return b; }); })) {
|
||||
uint32_t out_upcnt{};
|
||||
TPML_PCR_SELECTION * out_sel{};
|
||||
TPML_DIGEST * out_val{};
|
||||
TRY_TPM2("read PCRs", Esys_PCR_Read(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pcrs_left, &out_upcnt, &out_sel, &out_val));
|
||||
quickscope_wrapper out_deleter{[&] { Esys_Free(out_val), Esys_Free(out_sel); }};
|
||||
|
||||
if(update_count && update_count != out_upcnt)
|
||||
goto new_pcrs;
|
||||
update_count = out_upcnt;
|
||||
|
||||
if(!out_val->count) { // this can happen with SHA1 disabled, for example
|
||||
auto first = true;
|
||||
fputs("No PCRs when asking for ", stderr);
|
||||
for(size_t i = 0; i < pcrs_left.count; ++i)
|
||||
if(std::any_of(pcrs_left.pcrSelections[i].pcrSelect, pcrs_left.pcrSelections[i].pcrSelect + pcrs_left.pcrSelections[i].sizeofSelect,
|
||||
[](auto b) { return b; }))
|
||||
fprintf(stderr, "%s%s", std::exchange(first, false) ? "" : ", ", tpm2_hash_alg_name(pcrs_left.pcrSelections[i].hash));
|
||||
return fputs(": does the TPM support the algorithm?\n", stderr), __LINE__;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < out_val->count; ++i)
|
||||
SHA256_Update(&ctx, out_val->digests[i].buffer, out_val->digests[i].size);
|
||||
|
||||
for(size_t i = 0; i < out_sel->count; ++i)
|
||||
for(size_t j = 0u; j < out_sel->pcrSelections[i].sizeofSelect; ++j)
|
||||
pcrs_left.pcrSelections[i].pcrSelect[j] &= ~out_sel->pcrSelections[i].pcrSelect[j];
|
||||
}
|
||||
|
||||
SHA256_Final(digested_pcrs.buffer, &ctx);
|
||||
}
|
||||
|
||||
|
||||
ESYS_TR pcr_session = ESYS_TR_NONE;
|
||||
quickscope_wrapper tpm2_session_deleter{[&] { Esys_FlushContext(tpm2_ctx, pcr_session); }};
|
||||
|
||||
TRY_TPM2("start PCR session", Esys_StartAuthSession(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, nullptr, session_type,
|
||||
&tpm2_session_key, TPM2_ALG_SHA256, &pcr_session));
|
||||
|
||||
|
||||
TRY_TPM2("create PCR policy", Esys_PolicyPCR(tpm2_ctx, pcr_session, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &digested_pcrs, &pcrs));
|
||||
|
||||
return with_session(pcr_session);
|
||||
}
|
||||
|
||||
int tpm2_seal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT & persistent_handle, const TPM2B_DATA & metadata,
|
||||
void * data, size_t data_len) {
|
||||
const TPML_PCR_SELECTION & pcrs, bool allow_PCR_or_pass, void * data, size_t data_len) {
|
||||
ESYS_TR primary_handle = ESYS_TR_NONE;
|
||||
quickscope_wrapper primary_handle_deleter{[&] { Esys_FlushContext(tpm2_ctx, primary_handle); }};
|
||||
|
||||
@ -118,21 +331,10 @@ int tpm2_seal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_sessio
|
||||
pub.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG_NULL;
|
||||
pub.publicArea.parameters.rsaDetail.keyBits = 2048;
|
||||
pub.publicArea.parameters.rsaDetail.exponent = 0;
|
||||
|
||||
const TPML_PCR_SELECTION pcrs{};
|
||||
|
||||
TPM2B_PUBLIC * public_ret{};
|
||||
TPM2B_CREATION_DATA * creation_data{};
|
||||
TPM2B_DIGEST * creation_hash{};
|
||||
TPMT_TK_CREATION * creation_ticket{};
|
||||
TRY_MAIN(try_or_passphrase("create primary encryption key", "TPM2 owner hierarchy", tpm2_ctx, TPM2_RC_BAD_AUTH, ESYS_TR_RH_OWNER, [&] {
|
||||
return Esys_CreatePrimary(tpm2_ctx, ESYS_TR_RH_OWNER, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &primary_sens, &pub, &metadata, &pcrs, &primary_handle,
|
||||
&public_ret, &creation_data, &creation_hash, &creation_ticket);
|
||||
nullptr, nullptr, nullptr, nullptr);
|
||||
}));
|
||||
quickscope_wrapper creation_ticket_deleter{[=] { Esys_Free(creation_ticket); }};
|
||||
quickscope_wrapper creation_hash_deleter{[=] { Esys_Free(creation_hash); }};
|
||||
quickscope_wrapper creation_data_deleter{[=] { Esys_Free(creation_data); }};
|
||||
quickscope_wrapper public_ret_deleter{[=] { Esys_Free(public_ret); }};
|
||||
|
||||
// TSS2_RC Esys_CertifyCreation ( ESYS_CONTEXT * esysContext,
|
||||
// ESYS_TR signHandle,
|
||||
@ -149,11 +351,19 @@ int tpm2_seal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_sessio
|
||||
// )
|
||||
}
|
||||
|
||||
TPM2B_DIGEST policy_digest{};
|
||||
if(pcrs.count)
|
||||
TRY_MAIN(tpm2_police_pcrs(tpm2_ctx, pcrs, TPM2_SE_TRIAL, [&](auto pcr_session) {
|
||||
TPM2B_DIGEST * dgst{};
|
||||
TRY_TPM2("get PCR policy digest", Esys_PolicyGetDigest(tpm2_ctx, pcr_session, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &dgst));
|
||||
quickscope_wrapper dgst_deleter{[&] { Esys_Free(dgst); }};
|
||||
policy_digest = *dgst;
|
||||
return 0;
|
||||
}));
|
||||
|
||||
TPM2B_PRIVATE * sealant_private{};
|
||||
TPM2B_PUBLIC * sealant_public{};
|
||||
quickscope_wrapper sealant_public_deleter{[=] { Esys_Free(sealant_public); }};
|
||||
quickscope_wrapper sealant_private_deleter{[=] { Esys_Free(sealant_private); }};
|
||||
quickscope_wrapper sealant_deleter{[&] { Esys_Free(sealant_public), Esys_Free(sealant_private); }};
|
||||
|
||||
/// This is the object with the actual sealed data in it
|
||||
{
|
||||
@ -161,39 +371,34 @@ int tpm2_seal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_sessio
|
||||
secret_sens.sensitive.data.size = data_len;
|
||||
memcpy(secret_sens.sensitive.data.buffer, data, secret_sens.sensitive.data.size);
|
||||
|
||||
{
|
||||
if(!pcrs.count || allow_PCR_or_pass) {
|
||||
char what_for[ZFS_MAX_DATASET_NAME_LEN + 38 + 1];
|
||||
snprintf(what_for, sizeof(what_for), "%s TPM2 wrapping key (or empty for none)", dataset);
|
||||
|
||||
uint8_t * passphrase{};
|
||||
size_t passphrase_len{};
|
||||
TRY_MAIN(read_new_passphrase("%s TPM2 wrapping key (or empty for none)", passphrase, passphrase_len,
|
||||
sizeof(TPM2B_SENSITIVE_CREATE::sensitive.userAuth.buffer)));
|
||||
TRY_MAIN(read_new_passphrase(what_for, passphrase, passphrase_len, sizeof(TPM2B_SENSITIVE_CREATE::sensitive.userAuth.buffer)));
|
||||
quickscope_wrapper passphrase_deleter{[&] { free(passphrase); }};
|
||||
|
||||
secret_sens.sensitive.userAuth.size = passphrase_len;
|
||||
memcpy(secret_sens.sensitive.userAuth.buffer, passphrase, secret_sens.sensitive.userAuth.size);
|
||||
}
|
||||
|
||||
|
||||
// Same args as tpm2-tools' tpm2_create(1)
|
||||
TPM2B_PUBLIC pub{};
|
||||
pub.publicArea.type = TPM2_ALG_KEYEDHASH;
|
||||
pub.publicArea.nameAlg = TPM2_ALG_SHA256;
|
||||
pub.publicArea.objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT | TPMA_OBJECT_USERWITHAUTH;
|
||||
pub.publicArea.type = TPM2_ALG_KEYEDHASH;
|
||||
pub.publicArea.nameAlg = TPM2_ALG_SHA256;
|
||||
pub.publicArea.objectAttributes =
|
||||
TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT | ((pcrs.count && !secret_sens.sensitive.userAuth.size) ? 0 : TPMA_OBJECT_USERWITHAUTH);
|
||||
pub.publicArea.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL;
|
||||
pub.publicArea.authPolicy = policy_digest;
|
||||
|
||||
const TPML_PCR_SELECTION pcrs{};
|
||||
|
||||
TPM2B_CREATION_DATA * creation_data{};
|
||||
TPM2B_DIGEST * creation_hash{};
|
||||
TPMT_TK_CREATION * creation_ticket{};
|
||||
TRY_TPM2("create key seal", Esys_Create(tpm2_ctx, primary_handle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &secret_sens, &pub, &metadata, &pcrs,
|
||||
&sealant_private, &sealant_public, &creation_data, &creation_hash, &creation_ticket));
|
||||
quickscope_wrapper creation_ticket_deleter{[=] { Esys_Free(creation_ticket); }};
|
||||
quickscope_wrapper creation_hash_deleter{[=] { Esys_Free(creation_hash); }};
|
||||
quickscope_wrapper creation_data_deleter{[=] { Esys_Free(creation_data); }};
|
||||
&sealant_private, &sealant_public, nullptr, nullptr, nullptr));
|
||||
}
|
||||
|
||||
|
||||
ESYS_TR sealed_handle = ESYS_TR_NONE;
|
||||
quickscope_wrapper sealed_handle_deleter{[&] { Esys_FlushContext(tpm2_ctx, sealed_handle); }};
|
||||
|
||||
@ -214,7 +419,9 @@ int tpm2_seal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_sessio
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tpm2_unseal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle, void * data, size_t data_len) {
|
||||
int tpm2_unseal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle, const TPML_PCR_SELECTION & pcrs,
|
||||
void * data, size_t data_len) {
|
||||
// Esys_FlushContext(tpm2_ctx, tpm2_session);
|
||||
char what_for[ZFS_MAX_DATASET_NAME_LEN + 18 + 1];
|
||||
snprintf(what_for, sizeof(what_for), "%s TPM2 wrapping key", dataset);
|
||||
|
||||
@ -222,10 +429,22 @@ int tpm2_unseal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_sess
|
||||
ESYS_TR pandle;
|
||||
TRY_TPM2("convert persistent handle to object", Esys_TR_FromTPMPublic(tpm2_ctx, persistent_handle, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pandle));
|
||||
|
||||
|
||||
TPM2B_SENSITIVE_DATA * unsealed{};
|
||||
quickscope_wrapper unsealed_deleter{[=] { Esys_Free(unsealed); }};
|
||||
TRY_MAIN(try_or_passphrase("unseal wrapping key", what_for, tpm2_ctx, TPM2_RC_AUTH_FAIL, pandle,
|
||||
[&] { return Esys_Unseal(tpm2_ctx, pandle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &unsealed); }));
|
||||
quickscope_wrapper unsealed_deleter{[&] { Esys_Free(unsealed); }};
|
||||
auto unseal = [&](auto sess) { return Esys_Unseal(tpm2_ctx, pandle, sess, ESYS_TR_NONE, ESYS_TR_NONE, &unsealed); };
|
||||
TRY_MAIN(tpm2_police_pcrs(tpm2_ctx, pcrs, TPM2_SE_POLICY, [&](auto pcr_session) {
|
||||
// In case there's (PCR policy || passphrase): try PCR once; if it fails, fall back to passphrase
|
||||
if(pcr_session != ESYS_TR_NONE) {
|
||||
if(auto err = unseal(pcr_session); err != TPM2_RC_SUCCESS)
|
||||
fprintf(stderr, "Couldn't %s with PCR policy: %s\n", "unseal wrapping key", Tss2_RC_Decode(err));
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
return try_or_passphrase("unseal wrapping key", what_for, tpm2_ctx, TPM2_RC_AUTH_FAIL, pandle, [&] { return unseal(tpm2_session); });
|
||||
}));
|
||||
|
||||
|
||||
if(unsealed->size != data_len)
|
||||
return fprintf(stderr, "Unsealed data has wrong length %" PRIu16 ", expected %zu!\n", unsealed->size, data_len), __LINE__;
|
||||
|
26
src/tpm2.hpp
26
src/tpm2.hpp
@ -14,6 +14,10 @@
|
||||
#define TRY_TPM2(what, ...) TRY_GENERIC(what, , != TPM2_RC_SUCCESS, _try_ret, __LINE__, Tss2_RC_Decode, __VA_ARGS__)
|
||||
|
||||
|
||||
// https://github.com/tpm2-software/tpm2-tss/blob/49146d926ccb0fd3c3ee064455eb02356e0cdf90/test/integration/esys-create-session-auth.int.c#L218
|
||||
static const constexpr TPMT_SYM_DEF tpm2_session_key{.algorithm = TPM2_ALG_AES, .keyBits = {.aes = 128}, .mode = {.aes = TPM2_ALG_CFB}};
|
||||
|
||||
|
||||
template <class F>
|
||||
int with_tpm2_session(F && func) {
|
||||
// https://trustedcomputinggroup.org/wp-content/uploads/TSS_ESAPI_v1p00_r05_pubrev.pdf
|
||||
@ -29,15 +33,8 @@ int with_tpm2_session(F && func) {
|
||||
ESYS_TR tpm2_session = ESYS_TR_NONE;
|
||||
quickscope_wrapper tpm2_session_deleter{[&] { Esys_FlushContext(tpm2_ctx, tpm2_session); }};
|
||||
|
||||
{
|
||||
// https://github.com/tpm2-software/tpm2-tss/blob/master/test/integration/esys-create-session-auth.int.c#L218
|
||||
TPMT_SYM_DEF session_key{};
|
||||
session_key.algorithm = TPM2_ALG_AES;
|
||||
session_key.keyBits.aes = 128;
|
||||
session_key.mode.aes = TPM2_ALG_CFB;
|
||||
TRY_TPM2("authenticate with TPM", Esys_StartAuthSession(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, nullptr,
|
||||
TPM2_SE_HMAC, &session_key, TPM2_ALG_SHA256, &tpm2_session));
|
||||
}
|
||||
TRY_TPM2("authenticate with TPM", Esys_StartAuthSession(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, nullptr, TPM2_SE_HMAC,
|
||||
&tpm2_session_key, TPM2_ALG_SHA256, &tpm2_session));
|
||||
|
||||
return func(tpm2_ctx, tpm2_session);
|
||||
}
|
||||
@ -45,10 +42,15 @@ int with_tpm2_session(F && func) {
|
||||
extern TPM2B_DATA tpm2_creation_metadata(const char * dataset_name);
|
||||
|
||||
/// Parse a persistent handle name as stored in a ZFS property
|
||||
extern int tpm2_parse_handle(const char * dataset_name, const char * handle_s, TPMI_DH_PERSISTENT & handle);
|
||||
extern int tpm2_parse_prop(const char * dataset_name, char * handle_s, TPMI_DH_PERSISTENT & handle, TPML_PCR_SELECTION * pcrs);
|
||||
extern int tpm2_unparse_prop(TPMI_DH_PERSISTENT persistent_handle, const TPML_PCR_SELECTION & pcrs, char ** prop);
|
||||
|
||||
/// `alg:PCR[,PCR]...[+alg:PCR[,PCR]...]...`; all separators can have spaces
|
||||
extern int tpm2_parse_pcrs(char * arg, TPML_PCR_SELECTION & pcrs);
|
||||
|
||||
extern int tpm2_generate_rand(ESYS_CONTEXT * tpm2_ctx, void * into, size_t length);
|
||||
extern int tpm2_seal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT & persistent_handle, const TPM2B_DATA & metadata,
|
||||
void * data, size_t data_len);
|
||||
extern int tpm2_unseal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle, void * data, size_t data_len);
|
||||
const TPML_PCR_SELECTION & pcrs, bool allow_PCR_or_pass, void * data, size_t data_len);
|
||||
extern int tpm2_unseal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle,
|
||||
const TPML_PCR_SELECTION & pcrs, void * data, size_t data_len);
|
||||
extern int tpm2_free_persistent(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle);
|
||||
|
@ -25,6 +25,11 @@
|
||||
"name": "Source",
|
||||
"path": "src"
|
||||
},
|
||||
{
|
||||
"follow_symlinks": true,
|
||||
"name": "Misc source",
|
||||
"path": "contrib"
|
||||
},
|
||||
{
|
||||
"follow_symlinks": true,
|
||||
"name": "Initrd plug-ins",
|
||||
|
Loading…
x
Reference in New Issue
Block a user