diff --git a/README.md b/README.md index 8c7f31e..80dddc0 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,22 @@ -# zlevis +# Zlevis A minimal fork of [Clevis](https://github.com/latchset/clevis), rewritten in POSIX shell to accommodate automatic decryption of a ZFS root pool with TPM2. ## Installation -`zlevis` can be installed with `meson`, after cloning the repository, setup the build directory: +### Alpine Linux + +Work in progress. + +### Manual + +Zlevis can be manually installed with `meson`, after cloning the repository, setup the build directory ``` $ meson setup builddir ``` -> Using the `--prefix=/usr` flag will install `zlevis` in `/usr/bin` instead of `/usr/local/bin`. - -> Using the `--reconfigure` flag will reconfigure the build directory. - -Installation of the `zlevis` scripts is now performed with: +Installation of the zlevis scripts is now performed with ``` # meson install -C builddir diff --git a/meson.build b/meson.build index e8969cc..2c824ab 100644 --- a/meson.build +++ b/meson.build @@ -1,11 +1,11 @@ # Project definition -project('zlevis', license: 'GPL3') +project('zlevis', license: 'GPL3', version: '1') # Define bindir -bindir = join_paths(get_option('prefix'), get_option('bindir')) +bindir=join_paths(get_option('prefix'), get_option('bindir')) # Define bins list -bins = [] +bins=[] # Define subdir with bins subdir('src') diff --git a/src/meson.build b/src/meson.build index 09c3d2c..145e509 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,14 +1,4 @@ -# Find scripts -main = find_program('zlevis') -encrypt = find_program('zlevis-encrypt') -decrypt = find_program('zlevis-decrypt') - -# Test the scripts -test('zlevis', main, args: '--summary') -test('zlevis-encrypt', encrypt, args: '--summary') -test('zlevis-decrypt', decrypt, args: '--summary') - # Add paths of scripts to bins -bins += join_paths(meson.current_source_dir(), 'zlevis') bins += join_paths(meson.current_source_dir(), 'zlevis-encrypt') -bins += join_paths(meson.current_source_dir(), 'zlevis-decrypt') \ No newline at end of file +bins += join_paths(meson.current_source_dir(), 'zlevis-decrypt') +bins += join_paths(meson.current_source_dir(), 'zlevis-fetch') diff --git a/src/zlevis b/src/zlevis deleted file mode 100755 index 34acddc..0000000 --- a/src/zlevis +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh - -# Exit immediately if a command exits with a non-zero status -set -e - -# Summary of the script's functionality -summary="A tool that enables automatic decryption of ZFS rpools with TPM2" - -# Display summary if requested -if [ "$1" = "--summary" ]; then - echo "$summary" - exit 0 -fi - -# Function to display usage information of zlevis -info() { - exec >&2 - echo "Usage: \"zlevis {decrypt|encrypt} \"" - exit 2 -} - -# Function to display usage information of zlevis encrypt pool -encrypt_pool_info() { - exec >&2 - echo "Usage: \"zlevis encrypt '{\"property\":\"value\"}' < file.key\"" - echo - echo "This command uses the following configuration properties:" - echo " hash: -> Hash algorithm used in the computation of the object name (default: sha256)." - echo " key: -> Algorithm type for the generated key (default: ecc)." - echo " pcr_bank: -> PCR algorithm bank to use for policy (default: first supported by TPM)." - echo " pcr_ids: -> PCR list used for policy. If not present, no policy is used." - echo " pcr_digest: -> Binary PCR hashes encoded in base64. If not present, the hash values are looked up." - exit 2 -} - -# Determine the argument path and execute the relevant script or function -if [ -t 0 ]; then - case "$1" in - "decrypt") zfs list -Ho tpm:jwe "$2" | zlevis-decrypt;; - "encrypt") encrypt_pool_info;; - *) info;; - esac -else - case "$1" in - "encrypt") read -r -d . key || zfs set tpm:jwe=$(printf "%s" "$key" | zlevis-encrypt "$3") "$2";; - *) info;; - esac -fi - -# Exit with the status of the last command -exit $? \ No newline at end of file diff --git a/src/zlevis-decrypt b/src/zlevis-decrypt index 7cac845..17672bc 100755 --- a/src/zlevis-decrypt +++ b/src/zlevis-decrypt @@ -4,7 +4,7 @@ set -e # Summary of the script's functionality -summary="Decrypts a JWE using a TPM2.0 chip" +summary="Decrypts a JWE using a TPM2.0 chip." # TPM2.0 owner hierarchy to be used by the Operating System auth="o" @@ -18,82 +18,98 @@ fi # Display usage information if input is from a terminal if [ -t 0 ]; then exec >&2 + echo "$summary" + echo echo "Usage: \"zlevis-decrypt < file.jwe\"" + echo "Usage ZFS: \"zfs list -Ho tpm:jwe | zlevis-decrypt\"" 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" >&2 + 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="$(printf "%s" "$hdr" | jose b64 dec -i-)"; then +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 [ "$(printf "%s" "$jhd" | jose fmt -j- -Og zlevis -g pin -u-)" != "tpm2" ]; then +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="$(printf "%s" "$jhd" | jose fmt -j- -Og zlevis -g tpm2 -g hash -Su-)"; then - echo "JWE missing required 'hash' header parameter" >&2 +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="$(printf "%s" "$jhd" | jose fmt -j- -Og zlevis -g tpm2 -g key -Su-)"; then - echo "JWE missing required 'key' header parameter" >&2 +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="$(printf "%s" "$jhd" | jose fmt -j- -Og zlevis -g tpm2 -g jwk_pub -Su-)"; then - echo "JWE missing required 'jwk_pub' header parameter" >&2 +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 -if ! jwk_priv="$(printf "%s" "$jhd" | jose fmt -j- -Og zlevis -g tpm2 -g jwk_priv -Su-)"; then - echo "JWE missing required 'jwk_priv' header parameter" >&2 +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="$(printf "%s" "$jhd" | jose fmt -j- -Og zlevis -g tpm2 -g pcr_ids -Su-)" || true +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="$(printf "%s" "$jhd" | jose fmt -j- -Og zlevis -g tpm2 -g pcr_bank -Su-)" + pcr_bank="$(jose fmt -j- -Og clevis -g tpm2 -g pcr_bank -Su- < "$tmp"/jhd)" pcr_spec="$pcr_bank:$pcr_ids" fi -# Define and trap tmp jwk_pub and jwk_priv -tmp_jwk_pub="/tmp/jwk_pub.$$" -tmp_jwk_priv="/tmp/jwk_priv.$$" -trap 'rm -f "$tmp_jwk_pub" "$tmp_jwk_priv"' EXIT - # Decode the public and private keys from Base64 -if ! printf "%s" "$jwk_pub" | jose b64 dec -i- -O "$tmp_jwk_pub"; then +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 ! printf "%s" "$jwk_priv" | jose b64 dec -i- -O "$tmp_jwk_priv"; then +if ! jose b64 dec -i- -O "$tmp"/jwk.priv < "$tmp"/jwk_priv; then echo "Decoding jwk.priv from Base64 failed" >&2 exit 1 fi -# Define and trap primary_context -tmp_primary_context="/tmp/primary_context.$$" -trap 'rm -f "$tmp_jwk_pub" "$tmp_jwk_priv" "$tmp_primary_context"' EXIT - # 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=$?;; + 4|5) tpm2_createprimary -Q -C "$auth" -g "$hash" -G "$key" -c "$tmp"/primary.context || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then @@ -102,13 +118,9 @@ if [ -n "$fail" ]; then fi tpm2_flushcontext -t -# Define and trap load_context -tmp_load_context="/tmp/load_context.$$" -trap 'rm -f "$tmp_jwk_pub" "$tmp_jwk_priv" "$tmp_primary_context" "$tmp_load_context"' EXIT - # 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=$?;; + 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 @@ -117,12 +129,9 @@ if [ -n "$fail" ]; then fi tpm2_flushcontext -t -# Remove tmp_jwk_pub, tmp_jwk_priv and tmp_primary_context -rm -f "$tmp_jwk_pub" "$tmp_jwk_priv" "$tmp_primary_context" - # 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} 2>/dev/null)" || fail=$?;; + 4|5) jwk="$(tpm2_unseal -c "$tmp/load.context" ${pcr_spec:+-p pcr:$pcr_spec})" || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then @@ -131,11 +140,8 @@ if [ -n "$fail" ]; then fi tpm2_flushcontext -t -# Remove tmp_load_context -rm -f "$tmp_load_context" - # 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 $? +exit $? \ No newline at end of file diff --git a/src/zlevis-encrypt b/src/zlevis-encrypt index 6700bbe..1e3b049 100755 --- a/src/zlevis-encrypt +++ b/src/zlevis-encrypt @@ -4,7 +4,7 @@ set -e # Summary of the script's functionality -summary="Encrypts using a TPM2.0 chip binding policy" +summary="Encrypts using a TPM2.0 chip binding policy." # TPM2.0 owner hierarchy to be used by the Operating System auth="o" @@ -21,7 +21,7 @@ fi # Display usage information if input is from a terminal if [ -t 0 ]; then exec >&2 - echo "Usage: \"zlevis-encrypt '{\"property\":\"value\"}' < file.key > file.jwe\"" + echo "$summary" echo echo "This command uses the following configuration properties:" echo " hash: -> Hash algorithm used in the computation of the object name (default: sha256)." @@ -29,6 +29,9 @@ if [ -t 0 ]; then echo " pcr_bank: -> PCR algorithm bank to use for policy (default: first supported by TPM)." echo " pcr_ids: -> PCR list used for policy. If not present, no policy is used." echo " pcr_digest: -> Binary PCR hashes encoded in base64. If not present, the hash values are looked up." + echo + echo "Usage: \"zlevis-encrypt '{\"property\":\"value\"}' < file.key > file.jwe\"" + echo "Usage ZFS: \"zfs set tpm:jwe=\$(zlevis-encrypt '{\"property\":\"value\"}' < tank.key) \"" exit 2 fi @@ -55,27 +58,48 @@ validate_pcrs() { return 0 } +# 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" >&2 + 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 + # Validate the configuration input if ! cfg="$(jose fmt -j "$1" -Oo- 2>/dev/null)"; then - echo "Configuration '{\"property\":\"value\"}' is not present or malformed" >&2 + echo "Configuration is malformed" >&2 exit 1 fi +# Store the configuration in a temporary file +echo "$cfg" > "$tmp"/cfg + # Extract hash and key from the configuration, defaulting if not present -hash="$(printf "%s" "$cfg" | jose fmt -j- -Og hash -u-)" || hash="sha256" -key="$(printf "%s" "$cfg" | jose fmt -j- -Og key -u-)" || key="ecc" +hash="$(jose fmt -j- -Og hash -u- < "$tmp"/cfg)" || hash="sha256" +key="$(jose fmt -j- -Og key -u- < "$tmp"/cfg)" || key="ecc" # Determine the PCR bank to use for policy -pcr_bank="$(printf "%s" "$cfg" | jose fmt -j- -Og pcr_bank -u-)" || { +pcr_bank="$(jose fmt -j- -Og pcr_bank -u- < "$tmp"/cfg)" || { # If not specified, find a non-empty PCR algorithm bank if ! pcr_bank=$(tpm2_getcap pcrs | awk '/^[[:space:]]*-[[:space:]]*([^:]+):[[:space:]]*\[[[:space:]]*[^][:space:]]/ {found=1; split($0, m, /[-:[:space:]]+/); print m[2]; exit} END {exit !found}'); then echo "Unable to find non-empty PCR algorithm bank, please check output of tpm2_getcap pcrs" >&2 @@ -84,14 +108,15 @@ pcr_bank="$(printf "%s" "$cfg" | jose fmt -j- -Og pcr_bank -u-)" || { } # Trim spaces from the configuration for parsing PCR IDs -pcr_cfg=$(printf "%s" "$cfg" | tr -d '[:space:]') +pcr_cfg=$(tr -d '[:space:]' < "$tmp"/cfg) +echo "$pcr_cfg" > "$tmp"/pcr_cfg # Handle both string and JSON array formats for pcr_ids -if printf "%s" "$pcr_cfg" | jose fmt -j- -Og pcr_ids 2>/dev/null && ! pcr_ids="$(printf "%s" "$pcr_cfg" | jose fmt -j- -Og pcr_ids -u- 2>/dev/null)"; then +if jose fmt -j- -Og pcr_ids 2>/dev/null < "$tmp"/pcr_cfg && ! pcr_ids="$(jose fmt -j- -Og pcr_ids -u- 2>/dev/null < "$tmp"/pcr_cfg)"; then # Attempt to parse as a JSON array if string parsing fails - if printf "%s" "$pcr_cfg" | jose fmt -j- -Og pcr_ids -A 2>/dev/null; then + if jose fmt -j- -Og pcr_ids -A 2>/dev/null < "$tmp"/pcr_cfg; then # Construct a comma-separated string from the array - for pcr in $(printf "%s" "$pcr_cfg" | jose fmt -j- -Og pcr_ids -Af- | tr -d '"'); do + for pcr in $(jose fmt -j- -Og pcr_ids -Af- < "$tmp"/pcr_cfg | tr -d '"'); do pcr_ids=$(printf '%s,%s' "${pcr_ids}" "${pcr}") done # Remove leading comma @@ -109,21 +134,19 @@ if ! validate_pcrs "${tpm2tools_version}" "${pcr_bank}" "${pcr_ids}"; then fi # Get the PCR digest from the configuration or read it if not provided -pcr_digest="$(printf "%s" "$cfg" | jose fmt -j- -Og pcr_digest -u-)" || true +pcr_digest="$(jose fmt -j- -Og pcr_digest -u- < "$tmp"/cfg)" || true +echo "$pcr_digest" > "$tmp"/pcr_digest # Generate a JSON Web Key (JWK) if ! jwk="$(jose jwk gen -i '{"alg":"A256GCM"}')"; then echo "Generating a jwk failed" >&2 exit 1 fi - -# Define and trap primary_context -tmp_primary_context="/tmp/primary_context.$$" -trap 'rm -f "$tmp_primary_context"' EXIT +echo "$jwk" > "$tmp"/jwk # 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=$?;; + 4|5) tpm2_createprimary -Q -C "$auth" -g "$hash" -G "$key" -c "$tmp"/primary.context || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then @@ -132,17 +155,12 @@ if [ -n "$fail" ]; then fi tpm2_flushcontext -t -# Define and trap pcr_digest and pcr_policy -tmp_pcr_digest="/tmp/pcr_digest.$$" -tmp_pcr_policy="/tmp/pcr_policy.$$" -trap 'rm -f "$tmp_primary_context" "$tmp_pcr_digest" "$tmp_pcr_policy"' EXIT - # Handle PCRs and policy creation if PCR IDs are provided policy_options="" if [ -n "$pcr_ids" ]; then if [ -z "$pcr_digest" ]; then case "$tpm2tools_version" in - 4|5) tpm2_pcrread -Q "$pcr_bank":"$pcr_ids" -o "$tmp_pcr_digest" || fail=$?;; + 4|5) tpm2_pcrread -Q "$pcr_bank":"$pcr_ids" -o "$tmp"/pcr.digest || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then @@ -151,7 +169,7 @@ if [ -n "$pcr_ids" ]; then fi tpm2_flushcontext -t else - if ! printf "%s" "$pcr_digest" | jose b64 dec -i- -O "$tmp_pcr_digest"; then + if ! jose b64 dec -i- -O "$tmp"/pcr.digest < "$tmp"/pcr_digest; then echo "Error decoding PCR digest" >&2 exit 1 fi @@ -159,7 +177,7 @@ if [ -n "$pcr_ids" ]; then # Create the policy based on PCRs case "$tpm2tools_version" in - 4|5) tpm2_createpolicy -Q -g "$hash" --policy-pcr -l "$pcr_bank":"$pcr_ids" -f "$tmp_pcr_digest" -L "$tmp_pcr_policy" || fail=$?;; + 4|5) tpm2_createpolicy -Q -g "$hash" --policy-pcr -l "$pcr_bank":"$pcr_ids" -f "$tmp"/pcr.digest -L "$tmp"/pcr.policy || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then @@ -170,23 +188,15 @@ if [ -n "$pcr_ids" ]; then tpm2_flushcontext -l # Set the policy options to the created policy file - policy_options="$tmp_pcr_policy" + policy_options="$tmp/pcr.policy" else # If no PCR IDs are provided, add user authentication to the object attributes obj_attr="$obj_attr|userwithauth" fi -# Remove tmp_pcr_digest -rm -f "$tmp_pcr_digest" - -# Define and trap tmp jwk_pub and jwk_priv -tmp_jwk_pub="/tmp/jwk_pub.$$" -tmp_jwk_priv="/tmp/jwk_priv.$$" -trap 'rm -f "$tmp_primary_context" "$tmp_pcr_policy" "$tmp_jwk_pub" "$tmp_jwk_priv"' EXIT - # Create the TPM2 object for the JWK case "$tpm2tools_version" in - 4|5) printf "%s" "$jwk" | tpm2_create -Q -g "$hash" -C "$tmp_primary_context" -u "$tmp_jwk_pub" -r "$tmp_jwk_priv" -a "$obj_attr" -L "$policy_options" -i- || fail=$?;; + 4|5) tpm2_create -Q -g "$hash" -C "$tmp"/primary.context -u "$tmp"/jwk.pub -r "$tmp"/jwk.priv -a "$obj_attr" -L "$policy_options" -i- < "$tmp"/jwk || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then @@ -195,39 +205,33 @@ if [ -n "$fail" ]; then fi tpm2_flushcontext -t -# Remove tmp_primary_context and tmp_pcr_policy -rm -f "$tmp_primary_context" "$tmp_pcr_policy" - # Encode the JWK public and private keys in Base64 -if ! jwk_pub="$(jose b64 enc -I "$tmp_jwk_pub")"; then +if ! jwk_pub="$(jose b64 enc -I "$tmp"/jwk.pub)"; then echo "Encoding jwk.pub in Base64 failed" >&2 exit 1 fi -if ! jwk_priv="$(jose b64 enc -I "$tmp_jwk_priv")"; then +if ! jwk_priv="$(jose b64 enc -I "$tmp"/jwk.priv)"; then echo "Encoding jwk.priv in Base64 failed" >&2 exit 1 fi -# Remove tmp_jwk_pub and tmp_jwk_priv -rm -f "$tmp_jwk_pub" "$tmp_jwk_priv" - # Construct the JWE (JSON Web Encryption) structure -jwe='{"protected":{"zlevis":{"pin":"tpm2","tpm2":{}}}}' -jwe="$(jose fmt -j "$jwe" -g protected -g zlevis -g tpm2 -q "$hash" -s hash -UUUUo-)" -jwe="$(jose fmt -j "$jwe" -g protected -g zlevis -g tpm2 -q "$key" -s key -UUUUo-)" +jwe='{"protected":{"clevis":{"pin":"tpm2","tpm2":{}}}}' +jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$hash" -s hash -UUUUo-)" +jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$key" -s key -UUUUo-)" # Include PCR bank and IDs in the JWE if they are provided if [ -n "$pcr_ids" ]; then - jwe="$(jose fmt -j "$jwe" -g protected -g zlevis -g tpm2 -q "$pcr_bank" -s pcr_bank -UUUUo-)" - jwe="$(jose fmt -j "$jwe" -g protected -g zlevis -g tpm2 -q "$pcr_ids" -s pcr_ids -UUUUo-)" + jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$pcr_bank" -s pcr_bank -UUUUo-)" + jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$pcr_ids" -s pcr_ids -UUUUo-)" fi # Add the Base64 encoded JWK public and private keys to the JWE -jwe="$(jose fmt -j "$jwe" -g protected -g zlevis -g tpm2 -q "$jwk_pub" -s jwk_pub -UUUUo-)" -jwe="$(jose fmt -j "$jwe" -g protected -g zlevis -g tpm2 -q "$jwk_priv" -s jwk_priv -UUUUo-)" +jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$jwk_pub" -s jwk_pub -UUUUo-)" +jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$jwk_priv" -s jwk_priv -UUUUo-)" # Output the final JWE -(echo "$jwe$jwk$(/bin/cat)") | jose jwe enc -i- -k- -I- -c +(echo "$jwe$jwk"; /bin/cat) | jose jwe enc -i- -k- -I- -c # Exit with the status of the last command -exit $? +exit $? \ No newline at end of file diff --git a/src/zlevis-fetch b/src/zlevis-fetch new file mode 100755 index 0000000..4faffb2 --- /dev/null +++ b/src/zlevis-fetch @@ -0,0 +1,25 @@ +#!/bin/sh + +# Exit immediately if a command exits with a non-zero status +set -e + +# Check if zlevis-decrypt is present +command -v zlevis-decrypt > /dev/null || exit 1 + +# Read ZFS dataset information. +zfs list -Ho name,encryption,keystatus,encryptionroot,tpm:jwe | while IFS=$'\t' read -r ds enc keystatus encroot jwe; do + # Check if the dataset is the encryption root. + if [ "$ds" = "$encroot" ] && [ "$enc" != "off" ] && [ "$jwe" != "-" ]; then + if [ "$keystatus" = "available" ]; then + echo "Pool $ds already unlocked" + else + echo "Loading key for $ds" + if echo -n "$jwe" | zlevis-decrypt | zfs load-key -L prompt "$ds"; then + echo "Unlocked $ds" + else + echo "Failed to unlock $ds" >&2 + exit 1 + fi + fi + fi +done \ No newline at end of file