#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program 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. # # This program 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 this program. If not, see . # error() { echo "${1}" >&2 exit 1 } skip_test() { echo "${1}" >&2 exit 77 } # We require cryptsetup >= 2.0.4 to fully support LUKSv2. # Support is determined at build time. luks2_supported() { return @OLD_CRYPTSETUP@ } # Creates a tang adv to be used in the test. create_tang_adv() { local adv="${1}" local SIG="${TMP}/sig.jwk" jose jwk gen -i '{"alg":"ES512"}' > "${SIG}" local EXC="${TMP}/exc.jwk" jose jwk gen -i '{"alg":"ECMR"}' > "${EXC}" local TEMPLATE='{"protected":{"cty":"jwk-set+json"}}' jose jwk pub -s -i "${SIG}" -i "${EXC}" \ | jose jws sig -I- -s "${TEMPLATE}" -k "${SIG}" -o "${adv}" } # Creates a new LUKS1 or LUKS2 device to be used. new_device() { local LUKS="${1}" local DEV="${2}" local PASS="${3}" # Some builders fail if the cryptsetup steps are not ran as root, so let's # skip the test now if not running as root. if [ "$(id -u)" != 0 ]; then skip_test "WARNING: You must be root to run this test; test skipped." fi # Using a default password, if none has been provided. if [ -z "${PASS}" ]; then PASS="${DEFAULT_PASS}" fi local DEV_CACHED="${TMP}/${LUKS}.cached" # Let's reuse an existing device, if there is one. if [ -f "${DEV_CACHED}" ]; then echo "Reusing cached ${LUKS} device..." cp -f "${DEV_CACHED}" "${DEV}" return 0 fi fallocate -l64M "${DEV}" cryptsetup luksFormat --type "${LUKS}" --pbkdf pbkdf2 \ --pbkdf-force-iterations 1000 --batch-mode \ --force-password "${DEV}" <<< "${PASS}" # Caching the just-formatted device for possible reuse. cp -f "${DEV}" "${DEV_CACHED}" } # Creates a new LUKS1 or LUKS2 device to be used, using a keyfile. new_device_keyfile() { local LUKS="${1}" local DEV="${2}" local KEYFILE="${3}" # Some builders fail if the cryptsetup steps are not ran as root, so let's # skip the test now if not running as root. if [ "$(id -u)" != 0 ]; then skip_test "WARNING: You must be root to run this test; test skipped." fi if [[ -z "${KEYFILE}" ]] || [[ ! -f "${KEYFILE}" ]]; then error "Invalid keyfile (${KEYFILE})." fi fallocate -l64M "${DEV}" cryptsetup luksFormat --type "${LUKS}" --pbkdf pbkdf2 \ --pbkdf-force-iterations 1000 --batch-mode \ "${DEV}" "${KEYFILE}" } pin_cfg_equal() { # Let's remove the single quotes from the pin configuration. local cfg1="${1//\'/}" local cfg2="${2//\'/}" # Now we sort and present them in compact form. local sorted_cfg1 sorted_cfg2 sorted_cfg1="$(jq --compact-output --sort-keys . < <(echo -n "${cfg1}"))" sorted_cfg2="$(jq --compact-output --sort-keys . < <(echo -n "${cfg2}"))" # And we finally compare. if [ "${sorted_cfg1}" = "${sorted_cfg2}" ]; then return 0 fi return 1 } compare_luks_header() { DEV1="${1}" DEV2="${2}" TMP="${3}" cryptsetup luksHeaderBackup "${DEV1}" \ --header-backup-file "${TMP}"/check-header1 cryptsetup luksHeaderBackup "${DEV2}" \ --header-backup-file "${TMP}"/check-header2 local cs1 cs2 cs1=$(cksum "${TMP}"/check-header1 | cut -d' ' -f 1) cs2=$(cksum "${TMP}"/check-header2 | cut -d' ' -f 1) rm -f "${TMP}"/check-header{1,2} if [ "${cs1}" == "${cs2}" ]; then return 0 fi return 1 } used_luks1_metadata_slots() { DEV="${1}" if ! luksmeta test -d "${DEV}"; then echo "" return 0 fi local clevis_uuid="cb6e8904-81ff-40da-a84a-07ab9ab5715e" luksmeta show -d "${DEV}" \ | sed -rn "s|^([0-9]+)\s+active\s+${clevis_uuid}$|\1|p" \ | tr '\n' ' ' | sed 's/ $//' } used_luks2_metadata_slots() { DEV="${1}" cryptsetup luksDump "${DEV}" \ | grep -E -A1 "^\s+[0-9]+:\s+clevis$" \ | sed -rn 's|^\s+Keyslot:\s+([0-9]+)$|\1|p' | sort -n \ | tr '\n' ' ' | sed 's/ $//' } used_luks2_metadata_tokens() { DEV="${1}" cryptsetup luksDump "${DEV}" \ | grep -E -B1 "^\s+Keyslot:\s+[0-9]+$" \ | sed -rn 's|^\s+([0-9]+): clevis|\1|p' \ | tr '\n' ' ' | sed 's/ $//' } compare_luks1_metadata() { DEV1="${1}" DEV2="${2}" # If both are non-initialized, metadata is the same. ! luksmeta test -d "${DEV1}" && ! luksmeta test -d "${DEV2}" && return 0 # Otherwise, metadata differ. ! luksmeta test -d "${DEV1}" && return 1 ! luksmeta test -d "${DEV2}" && return 1 local slt1 slt2 slt1=$(used_luks1_metadata_slots "${DEV1}") slt2=$(used_luks1_metadata_slots "${DEV2}") if [ "${slt1}" != "${slt2}" ]; then echo "used slots did not match ($slt1) ($slt2)" >&2 return 1 fi local slt md1 md2 for slt in ${slt1}; do md1="$(luksmeta load -d "${DEV}" -s "${slt}")" md2="$(luksmeta load -d "${DEV2}" -s "${slt}")" if [ "${md1}" != "${md2}" ]; then echo "metadata in slot ${slt} did not match" >&2 return 1 fi done return 0 } compare_luks2_metadata() { DEV1="${1}" DEV2="${2}" local slt1 slt2 slt1=$(used_luks2_metadata_slots "${DEV1}") slt2=$(used_luks2_metadata_slots "${DEV2}") if [ "${slt1}" != "${slt2}" ]; then echo "used slots did not match ($slt1) ($slt2)" >&2 return 1 fi local tkn1 tkn2 tkn1=$(used_luks2_metadata_tokens "${DEV1}") tkn2=$(used_luks2_metadata_tokens "${DEV2}") if [ "${tkn1}" != "${tkn2}" ]; then echo "used tokens did not match ($tkn1) ($tkn2)" >&2 return 1 fi local tkn md1 md2 for tkn in ${tkn1}; do md1="$(cryptsetup token export --token-id "${tkn}" "${DEV1}")" md2="$(cryptsetup token export --token-id "${tkn}" "${DEV2}")" if [ "${md1}" != "${md2}" ]; then echo "metadata in token ${tkn} did not match" >&2 return 1 fi done return 0 } # Get a random port to be used with a test tang server. get_random_port() { shuf -i 1024-65535 -n 1 } # Removes tang rotated keys from the test server. tang_remove_rotated_keys() { local basedir="${1}" if [ -z "${basedir}" ]; then echo "Please pass a valid base directory for tang" return 1 fi [ -z "${TANGD_UPDATE}" ] && skip_test "WARNING: TANGD_UPDATE is not defined." local db="${basedir}/db" local cache="${basedir}/cache" mkdir -p "${db}" mkdir -p "${cache}" pushd "${db}" find . -name ".*.jwk" -exec rm -f {} \; popd "${TANGD_UPDATE}" "${db}" "${cache}" return 0 } # Creates new keys for the test tang server. tang_new_keys() { local basedir="${1}" local rotate="${2}" if [ -z "${basedir}" ]; then echo "Please pass a valid base directory for tang" return 1 fi [ -z "${TANGD_KEYGEN}" ] && skip_test "WARNING: TANGD_KEYGEN is not defined." [ -z "${TANGD_UPDATE}" ] && skip_test "WARNING: TANGD_UPDATE is not defined." local db="${basedir}/db" local cache="${basedir}/cache" mkdir -p "${db}" if [ -n "${rotate}" ]; then pushd "${db}" local k k=$(find . -name "*.jwk" | wc -l) if [ "${k}" -gt 0 ]; then for k in *.jwk; do mv -f -- "${k}" ".${k}" done fi popd fi "${TANGD_KEYGEN}" "${db}" "${TANGD_UPDATE}" "${db}" "${cache}" return 0 } # Start a test tang server. tang_run() { local basedir="${1}" local port="${2}" if [ -z "${basedir}" ]; then echo "Please pass a valid base directory for tang" >&2 return 1 fi if [ -z "${port}" ]; then echo "Please pass a valid port for tang" >&2 return 1 fi if ! tang_new_keys "${basedir}"; then echo "Error creating new keys for tang server" >&2 return 1 fi local KEYS="${basedir}/cache" local inetd='--inetd' [ "${SD_ACTIVATE##*/}" = "systemd-activate" ] && inetd= local pid pidfile pidfile="${basedir}/tang.pid" "${SD_ACTIVATE}" ${inetd} -l "${TANG_HOST}":"${port}" \ -a "${TANGD}" "${KEYS}" & pid=$! echo "${pid}" > "${pidfile}" } # Stop tang server. tang_stop() { local basedir="${1}" local pidfile="${basedir}/tang.pid" [ -f "${pidfile}" ] || return 0 local pid pid=$(<"${pidfile}") kill "${pid}" } # Wait for the tang server to be operational. tang_wait_until_ready() { local port="${1}" while ! curl --output /dev/null --silent --fail \ http://"${TANG_HOST}":"${port}"/adv; do sleep 0.1 echo -n . >&2 done } # Get tang advertisement. tang_get_adv() { local port="${1}" local adv="${2}" curl -o "${adv}" http://"${TANG_HOST}":"${port}"/adv } export TANG_HOST=127.0.0.1 export DEFAULT_PASS='just-some-test-password-here'