#!/bin/sh # Exit immediately if a command exits with a non-zero status set -e # Summary of the script's functionality summary="Decrypts a JWE using a TPM2.0 chip." # TPM2.0 owner hierarchy to be used by the Operating System auth="o" # Display summary if requested if [ "$1" = "--summary" ]; then echo "$summary" exit 0 fi # Display usage information if input is from a terminal if [ -t 0 ]; then exec >&2 echo echo "Usage: \"zlevis-decrypt < file.jwe\"" echo "Usage ZFS: \"zfs list -Ho tpm:jwe | zlevis-decrypt\"" echo echo "$summary" exit 2 fi # Function to clean up temporary files on exit on_exit() { if [ ! -d "$tmp" ] || ! rm -rf "$tmp"; then echo "Delete temporary files failed" >&2 echo "You need to clean up: $tmp" >&2 exit 1 fi } # Get the version of tpm2-tools tpm2tools_version=$(tpm2_createprimary -v | awk -F'version="' '{print $2}' | awk -F'.' '{print $1}') # Check if the tpm2-tools version is supported if [ -z "$tpm2tools_version" ] || [ "$tpm2tools_version" -lt 4 ] || [ "$tpm2tools_version" -gt 5 ]; then echo "The tpm2 pin requires a tpm2-tools version between 4 and 5" exit 1 fi # Create a temporary directory for TPM files if ! tmp="$(mktemp -d)"; then echo "Creating a temporary dir for TPM files failed" >&2 exit 1 fi # Set up cleanup on exit trap 'on_exit' EXIT # Read the JWE protected header read -r -d . hdr echo "$hdr" > "$tmp"/hdr # Decode the JWE protected header if ! jhd="$(jose b64 dec -i- < "$tmp"/hdr)"; then echo "Error decoding JWE protected header" >&2 exit 1 fi echo "$jhd" > "$tmp"/jhd # Validate the JWE pin type if [ "$(jose fmt -j- -Og clevis -g pin -u- < "$tmp"/jhd)" != "tpm2" ]; then echo "JWE pin mismatch" >&2 exit 1 fi # Extract required parameters from the JWE header if ! hash="$(jose fmt -j- -Og clevis -g tpm2 -g hash -Su- < "$tmp"/jhd)"; then echo "JWE missing required 'hash' header parameter!" >&2 exit 1 fi if ! key="$(jose fmt -j- -Og clevis -g tpm2 -g key -Su- < "$tmp"/jhd)"; then echo "JWE missing required 'key' header parameter!" >&2 exit 1 fi if ! jwk_pub="$(jose fmt -j- -Og clevis -g tpm2 -g jwk_pub -Su- < "$tmp"/jhd)"; then echo "JWE missing required 'jwk_pub' header parameter!" >&2 exit 1 fi echo "$jwk_pub" > "$tmp"/jwk_pub if ! jwk_priv="$(jose fmt -j- -Og clevis -g tpm2 -g jwk_priv -Su- < "$tmp"/jhd)"; then echo "JWE missing required 'jwk_priv' header parameter!" >&2 exit 1 fi echo "$jwk_priv" > "$tmp"/jwk_priv # Handle optional PCR parameters pcr_ids="$(jose fmt -j- -Og clevis -g tpm2 -g pcr_ids -Su- < "$tmp"/jhd)" || true pcr_spec="" if [ -n "$pcr_ids" ]; then pcr_bank="$(jose fmt -j- -Og clevis -g tpm2 -g pcr_bank -Su- < "$tmp"/jhd)" pcr_spec="$pcr_bank:$pcr_ids" fi # Decode the public and private keys from Base64 if ! jose b64 dec -i- -O "$tmp"/jwk.pub < "$tmp"/jwk_pub; then echo "Decoding jwk.pub from Base64 failed" >&2 exit 1 fi if ! jose b64 dec -i- -O "$tmp"/jwk.priv < "$tmp"/jwk_priv; then echo "Decoding jwk.priv from Base64 failed" >&2 exit 1 fi # Create the primary key in the TPM case "$tpm2tools_version" in 4|5) tpm2_createprimary -Q -C "$auth" -g "$hash" -G "$key" -c "$tmp"/primary.context || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "Creating TPM2 primary key failed" >&2 exit 1 fi tpm2_flushcontext -t # Load the JWK into the TPM case "$tpm2tools_version" in 4|5) tpm2_load -Q -C "$tmp"/primary.context -u "$tmp"/jwk.pub -r "$tmp"/jwk.priv -c "$tmp"/load.context || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "Loading jwk to TPM2 failed" >&2 exit 1 fi tpm2_flushcontext -t # Unseal the JWK from the TPM case "$tpm2tools_version" in 4|5) jwk="$(tpm2_unseal -c "$tmp/load.context" ${pcr_spec:+-p pcr:$pcr_spec})" || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "Unsealing jwk from TPM failed" >&2 exit 1 fi tpm2_flushcontext -t # Output the decrypted JWK along with the original header (echo "$jwk$hdr."; /bin/cat) | jose jwe dec -k- -i- # Exit with the status of the last command exit $?