mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-04-02 09:21:19 -04:00
selftests: add dm-verity keyring selftests
Add selftests that verify the keyring behaves correctly. For simplicity this works with dm-verity as a module. Signed-off-by: Christian Brauner <brauner@kernel.org> Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
This commit is contained in:
committed by
Mikulas Patocka
parent
033724b1c6
commit
f93bc86982
5
tools/testing/selftests/dm-verity/Makefile
Normal file
5
tools/testing/selftests/dm-verity/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
TEST_PROGS := test-dm-verity-keyring.sh
|
||||
|
||||
include ../lib.mk
|
||||
10
tools/testing/selftests/dm-verity/config
Normal file
10
tools/testing/selftests/dm-verity/config
Normal file
@@ -0,0 +1,10 @@
|
||||
CONFIG_BLK_DEV_DM=y
|
||||
CONFIG_DM_VERITY=m
|
||||
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
|
||||
CONFIG_MODULE_UNLOAD=y
|
||||
CONFIG_KEYS=y
|
||||
CONFIG_ASYMMETRIC_KEY_TYPE=y
|
||||
CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=y
|
||||
CONFIG_X509_CERTIFICATE_PARSER=y
|
||||
CONFIG_PKCS7_MESSAGE_PARSER=y
|
||||
CONFIG_SYSTEM_DATA_VERIFICATION=y
|
||||
873
tools/testing/selftests/dm-verity/test-dm-verity-keyring.sh
Executable file
873
tools/testing/selftests/dm-verity/test-dm-verity-keyring.sh
Executable file
@@ -0,0 +1,873 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Test script for dm-verity keyring functionality
|
||||
#
|
||||
# This script has two modes depending on kernel configuration:
|
||||
#
|
||||
# 1. keyring_unsealed=1 AND require_signatures=1:
|
||||
# - Upload a test key to the .dm-verity keyring
|
||||
# - Seal the keyring
|
||||
# - Create a dm-verity device with a signed root hash
|
||||
# - Verify signature verification works
|
||||
#
|
||||
# 2. keyring_unsealed=0 (default) OR require_signatures=0:
|
||||
# - Verify the keyring is already sealed (if unsealed=0)
|
||||
# - Verify keys cannot be added to a sealed keyring
|
||||
# - Verify the keyring is inactive (not used for verification)
|
||||
#
|
||||
# Requirements:
|
||||
# - Root privileges
|
||||
# - openssl
|
||||
# - veritysetup (cryptsetup)
|
||||
# - keyctl (keyutils)
|
||||
|
||||
set -e
|
||||
|
||||
WORK_DIR=""
|
||||
DATA_DEV=""
|
||||
HASH_DEV=""
|
||||
DM_NAME="verity-test-$$"
|
||||
CLEANUP_DONE=0
|
||||
|
||||
# Module parameters (detected at runtime)
|
||||
KEYRING_UNSEALED=""
|
||||
REQUIRE_SIGNATURES=""
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $*"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $*"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $*" >&2
|
||||
}
|
||||
|
||||
log_pass() {
|
||||
echo -e "${GREEN}[PASS]${NC} $*"
|
||||
}
|
||||
|
||||
log_fail() {
|
||||
echo -e "${RED}[FAIL]${NC} $*" >&2
|
||||
}
|
||||
|
||||
log_skip() {
|
||||
echo -e "${YELLOW}[SKIP]${NC} $*"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [ "$CLEANUP_DONE" -eq 1 ]; then
|
||||
return
|
||||
fi
|
||||
CLEANUP_DONE=1
|
||||
|
||||
log_info "Cleaning up..."
|
||||
|
||||
# Remove dm-verity device if it exists
|
||||
if dmsetup info "$DM_NAME" &>/dev/null; then
|
||||
dmsetup remove "$DM_NAME" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Detach loop devices
|
||||
if [ -n "$DATA_DEV" ] && [[ "$DATA_DEV" == /dev/loop* ]]; then
|
||||
losetup -d "$DATA_DEV" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "$HASH_DEV" ] && [[ "$HASH_DEV" == /dev/loop* ]]; then
|
||||
losetup -d "$HASH_DEV" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Remove work directory
|
||||
if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
|
||||
rm -rf "$WORK_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
die() {
|
||||
log_error "$*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
find_dm_verity_keyring() {
|
||||
# The .dm-verity keyring is not linked to user-accessible keyrings,
|
||||
# so we need to find it via /proc/keys
|
||||
local serial_hex
|
||||
serial_hex=$(awk '/\.dm-verity/ {print $1}' /proc/keys 2>/dev/null)
|
||||
|
||||
if [ -z "$serial_hex" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Convert hex to decimal for keyctl
|
||||
echo $((16#$serial_hex))
|
||||
}
|
||||
|
||||
get_module_param() {
|
||||
local param="$1"
|
||||
local path="/sys/module/dm_verity/parameters/$param"
|
||||
|
||||
if [ -f "$path" ]; then
|
||||
cat "$path"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
check_requirements() {
|
||||
log_info "Checking requirements..."
|
||||
|
||||
# Check for root
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
die "This script must be run as root"
|
||||
fi
|
||||
|
||||
# Check for required tools
|
||||
for cmd in openssl veritysetup keyctl losetup dmsetup dd awk; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
die "Required command not found: $cmd"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for dm-verity module
|
||||
if ! modprobe -n dm-verity &>/dev/null; then
|
||||
die "dm-verity module not available"
|
||||
fi
|
||||
|
||||
# Verify OpenSSL can create signatures
|
||||
# OpenSSL cms -sign with -binary -outform DER creates detached signatures by default
|
||||
log_info "Using OpenSSL for PKCS#7 signatures"
|
||||
}
|
||||
|
||||
load_dm_verity_module() {
|
||||
local keyring_unsealed="${1:-0}"
|
||||
local require_signatures="${2:-0}"
|
||||
|
||||
log_info "Loading dm-verity module with keyring_unsealed=$keyring_unsealed require_signatures=$require_signatures"
|
||||
|
||||
# Unload if already loaded
|
||||
if lsmod | grep -q '^dm_verity'; then
|
||||
log_info "Unloading existing dm-verity module..."
|
||||
modprobe -r dm-verity 2>/dev/null || \
|
||||
die "Failed to unload dm-verity module (may be in use)"
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# Load with specified parameters
|
||||
modprobe dm-verity keyring_unsealed="$keyring_unsealed" require_signatures="$require_signatures" || \
|
||||
die "Failed to load dm-verity module"
|
||||
|
||||
# Wait for keyring to be created (poll with timeout)
|
||||
local keyring_id=""
|
||||
local timeout=50 # 5 seconds (50 * 0.1s)
|
||||
while [ $timeout -gt 0 ]; do
|
||||
keyring_id=$(find_dm_verity_keyring) && break
|
||||
sleep 0.1
|
||||
timeout=$((timeout - 1))
|
||||
done
|
||||
|
||||
if [ -z "$keyring_id" ]; then
|
||||
die "dm-verity keyring not found after module load (timeout)"
|
||||
fi
|
||||
|
||||
log_info "Found .dm-verity keyring: $keyring_id"
|
||||
echo "$keyring_id" > "$WORK_DIR/keyring_id"
|
||||
|
||||
# Read and display module parameters
|
||||
KEYRING_UNSEALED=$(get_module_param "keyring_unsealed")
|
||||
REQUIRE_SIGNATURES=$(get_module_param "require_signatures")
|
||||
|
||||
log_info "Module parameters:"
|
||||
log_info " keyring_unsealed=$KEYRING_UNSEALED"
|
||||
log_info " require_signatures=$REQUIRE_SIGNATURES"
|
||||
}
|
||||
|
||||
unload_dm_verity_module() {
|
||||
log_info "Unloading dm-verity module..."
|
||||
|
||||
# Clean up any dm-verity devices first
|
||||
local dm_dev
|
||||
while read -r dm_dev _; do
|
||||
[ -n "$dm_dev" ] || continue
|
||||
log_info "Removing dm-verity device: $dm_dev"
|
||||
dmsetup remove "$dm_dev" 2>/dev/null || true
|
||||
done < <(dmsetup ls --target verity 2>/dev/null)
|
||||
|
||||
if lsmod | grep -q '^dm_verity'; then
|
||||
modprobe -r dm-verity 2>/dev/null || \
|
||||
log_warn "Failed to unload dm-verity module"
|
||||
sleep 1
|
||||
fi
|
||||
}
|
||||
|
||||
generate_keys() {
|
||||
log_info "Generating signing key pair..."
|
||||
|
||||
# Generate private key (2048-bit for faster test execution)
|
||||
openssl genrsa -out "$WORK_DIR/private.pem" 2048 2>/dev/null
|
||||
|
||||
# Create OpenSSL config for certificate extensions
|
||||
# The kernel requires digitalSignature key usage for signature verification
|
||||
# Both subjectKeyIdentifier and authorityKeyIdentifier are needed for
|
||||
# the kernel to match keys in the keyring (especially for self-signed certs)
|
||||
cat > "$WORK_DIR/openssl.cnf" << 'EOF'
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
x509_extensions = v3_ca
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
CN = dm-verity-test-key
|
||||
|
||||
[v3_ca]
|
||||
basicConstraints = critical,CA:FALSE
|
||||
keyUsage = digitalSignature
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid
|
||||
EOF
|
||||
|
||||
# Generate self-signed certificate with proper extensions
|
||||
openssl req -new -x509 -key "$WORK_DIR/private.pem" \
|
||||
-out "$WORK_DIR/cert.pem" -days 365 \
|
||||
-config "$WORK_DIR/openssl.cnf" 2>/dev/null
|
||||
|
||||
# Convert certificate to DER format for kernel
|
||||
openssl x509 -in "$WORK_DIR/cert.pem" -outform DER \
|
||||
-out "$WORK_DIR/cert.der"
|
||||
|
||||
# Show certificate info for debugging
|
||||
log_info "Certificate details:"
|
||||
openssl x509 -in "$WORK_DIR/cert.pem" -noout -text 2>/dev/null | \
|
||||
grep -E "Subject:|Issuer:|Key Usage|Extended" | head -10
|
||||
|
||||
log_info "Keys generated successfully"
|
||||
}
|
||||
|
||||
seal_keyring() {
|
||||
log_info "Sealing the .dm-verity keyring..."
|
||||
|
||||
local keyring_id
|
||||
keyring_id=$(cat "$WORK_DIR/keyring_id")
|
||||
|
||||
keyctl restrict_keyring "$keyring_id" || \
|
||||
die "Failed to seal keyring"
|
||||
|
||||
log_info "Keyring sealed successfully"
|
||||
}
|
||||
|
||||
create_test_device() {
|
||||
log_info "Creating test device images..."
|
||||
|
||||
# Create data image with random content (8MB is sufficient for testing)
|
||||
dd if=/dev/urandom of="$WORK_DIR/data.img" bs=1M count=8 status=none
|
||||
|
||||
# Create hash image (will be populated by veritysetup)
|
||||
dd if=/dev/zero of="$WORK_DIR/hash.img" bs=1M count=1 status=none
|
||||
|
||||
# Setup loop devices
|
||||
DATA_DEV=$(losetup --find --show "$WORK_DIR/data.img")
|
||||
HASH_DEV=$(losetup --find --show "$WORK_DIR/hash.img")
|
||||
|
||||
log_info "Data device: $DATA_DEV"
|
||||
log_info "Hash device: $HASH_DEV"
|
||||
}
|
||||
|
||||
create_verity_hash() {
|
||||
log_info "Creating dm-verity hash tree..."
|
||||
|
||||
local root_hash output
|
||||
output=$(veritysetup format "$DATA_DEV" "$HASH_DEV" 2>&1)
|
||||
root_hash=$(echo "$output" | grep "Root hash:" | awk '{print $3}')
|
||||
|
||||
if [ -z "$root_hash" ]; then
|
||||
log_error "veritysetup format output:"
|
||||
echo "$output" | sed 's/^/ /'
|
||||
die "Failed to get root hash from veritysetup format"
|
||||
fi
|
||||
|
||||
echo "$root_hash" > "$WORK_DIR/root_hash"
|
||||
log_info "Root hash: $root_hash"
|
||||
}
|
||||
|
||||
create_detached_signature() {
|
||||
local infile="$1"
|
||||
local outfile="$2"
|
||||
local cert="$3"
|
||||
local key="$4"
|
||||
|
||||
# Use openssl smime (not cms) for PKCS#7 signatures compatible with kernel
|
||||
# Flags from working veritysetup example:
|
||||
# -nocerts: don't include certificate in signature
|
||||
# -noattr: no signed attributes
|
||||
# -binary: binary input mode
|
||||
if openssl smime -sign -nocerts -noattr -binary \
|
||||
-in "$infile" \
|
||||
-inkey "$key" \
|
||||
-signer "$cert" \
|
||||
-outform der \
|
||||
-out "$outfile" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_error "Failed to create signature"
|
||||
return 1
|
||||
}
|
||||
|
||||
activate_verity_device() {
|
||||
local with_sig="$1"
|
||||
local root_hash
|
||||
root_hash=$(cat "$WORK_DIR/root_hash")
|
||||
|
||||
# Clear dmesg and capture any kernel messages during activation
|
||||
dmesg -C 2>/dev/null || true
|
||||
|
||||
if [ "$with_sig" = "yes" ]; then
|
||||
log_info "Activating dm-verity device with signature..."
|
||||
veritysetup open "$DATA_DEV" "$DM_NAME" "$HASH_DEV" "$root_hash" \
|
||||
--root-hash-signature="$WORK_DIR/root_hash.p7s" 2>&1
|
||||
local ret=$?
|
||||
else
|
||||
log_info "Activating dm-verity device without signature..."
|
||||
veritysetup open "$DATA_DEV" "$DM_NAME" "$HASH_DEV" "$root_hash" 2>&1
|
||||
local ret=$?
|
||||
fi
|
||||
|
||||
# Show relevant kernel messages
|
||||
local kmsg
|
||||
kmsg=$(dmesg 2>/dev/null | grep -i -E 'verity|pkcs|signature|asymmetric|key' | tail -10)
|
||||
if [ -n "$kmsg" ]; then
|
||||
log_info "Kernel messages:"
|
||||
echo "$kmsg" | while read -r line; do echo " $line"; done
|
||||
fi
|
||||
|
||||
return $ret
|
||||
}
|
||||
|
||||
deactivate_verity_device() {
|
||||
if dmsetup info "$DM_NAME" &>/dev/null; then
|
||||
dmsetup remove "$DM_NAME" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
show_keyring_status() {
|
||||
log_info "Keyring status:"
|
||||
|
||||
local keyring_id
|
||||
keyring_id=$(find_dm_verity_keyring) || true
|
||||
|
||||
if [ -n "$keyring_id" ]; then
|
||||
echo " Keyring ID: $keyring_id"
|
||||
keyctl show "$keyring_id" 2>/dev/null || true
|
||||
grep '\.dm-verity' /proc/keys 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
list_keyring_keys() {
|
||||
log_info "Keys in .dm-verity keyring:"
|
||||
|
||||
local keyring_id
|
||||
keyring_id=$(cat "$WORK_DIR/keyring_id" 2>/dev/null) || \
|
||||
keyring_id=$(find_dm_verity_keyring) || true
|
||||
|
||||
if [ -z "$keyring_id" ]; then
|
||||
log_warn "Could not find keyring"
|
||||
return
|
||||
fi
|
||||
|
||||
# List all keys in the keyring
|
||||
local keys
|
||||
keys=$(keyctl list "$keyring_id" 2>/dev/null)
|
||||
if [ -z "$keys" ] || [ "$keys" = "keyring is empty" ]; then
|
||||
echo " (empty)"
|
||||
else
|
||||
echo "$keys" | while read -r line; do
|
||||
echo " $line"
|
||||
done
|
||||
|
||||
# Show detailed info for each key
|
||||
log_info "Key details:"
|
||||
keyctl list "$keyring_id" 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]+$' | while read -r key_id; do
|
||||
echo " Key $key_id:"
|
||||
keyctl describe "$key_id" 2>/dev/null | sed 's/^/ /'
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
generate_named_key() {
|
||||
local name="$1"
|
||||
local key_dir="$WORK_DIR/keys/$name"
|
||||
|
||||
mkdir -p "$key_dir"
|
||||
|
||||
# Log to stderr so it doesn't interfere with return value
|
||||
echo "[INFO] Generating key pair: $name" >&2
|
||||
|
||||
# Generate private key
|
||||
openssl genrsa -out "$key_dir/private.pem" 2048 2>/dev/null
|
||||
|
||||
# Create OpenSSL config for certificate extensions
|
||||
# Both subjectKeyIdentifier and authorityKeyIdentifier are needed for
|
||||
# the kernel to match keys in the keyring (especially for self-signed certs)
|
||||
cat > "$key_dir/openssl.cnf" << EOF
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
x509_extensions = v3_ca
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
CN = dm-verity-test-$name
|
||||
|
||||
[v3_ca]
|
||||
basicConstraints = critical,CA:FALSE
|
||||
keyUsage = digitalSignature
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid
|
||||
EOF
|
||||
|
||||
# Generate self-signed certificate with proper extensions
|
||||
openssl req -new -x509 -key "$key_dir/private.pem" \
|
||||
-out "$key_dir/cert.pem" -days 365 \
|
||||
-config "$key_dir/openssl.cnf" 2>/dev/null
|
||||
|
||||
# Convert certificate to DER format for kernel
|
||||
openssl x509 -in "$key_dir/cert.pem" -outform DER \
|
||||
-out "$key_dir/cert.der"
|
||||
|
||||
# Return the key directory path (only this goes to stdout)
|
||||
echo "$key_dir"
|
||||
}
|
||||
|
||||
upload_named_key() {
|
||||
local name="$1"
|
||||
local key_dir="$2"
|
||||
|
||||
local keyring_id
|
||||
keyring_id=$(cat "$WORK_DIR/keyring_id")
|
||||
|
||||
log_info "Uploading key '$name' to keyring..."
|
||||
|
||||
local key_id
|
||||
if key_id=$(keyctl padd asymmetric "$name" "$keyring_id" \
|
||||
< "$key_dir/cert.der" 2>&1); then
|
||||
log_info "Key '$name' uploaded with ID: $key_id"
|
||||
echo "$key_id" > "$key_dir/key_id"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to upload key '$name': $key_id"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Test: Verify sealed keyring rejects key additions
|
||||
#
|
||||
test_sealed_keyring_rejects_keys() {
|
||||
log_info "TEST: Verify sealed keyring rejects key additions"
|
||||
|
||||
local keyring_id
|
||||
keyring_id=$(cat "$WORK_DIR/keyring_id")
|
||||
|
||||
generate_keys
|
||||
|
||||
# Try to add a key - should fail
|
||||
if keyctl padd asymmetric "dm-verity-test" "$keyring_id" \
|
||||
< "$WORK_DIR/cert.der" 2>/dev/null; then
|
||||
log_fail "Key addition should have been rejected on sealed keyring"
|
||||
return 1
|
||||
else
|
||||
log_pass "Sealed keyring correctly rejected key addition"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Test: Multiple keys in keyring
|
||||
#
|
||||
test_multiple_keys() {
|
||||
log_info "TEST: Multiple keys in keyring"
|
||||
|
||||
local key1_dir key2_dir key3_dir
|
||||
|
||||
# Generate three different keys
|
||||
key1_dir=$(generate_named_key "vendor-a")
|
||||
key2_dir=$(generate_named_key "vendor-b")
|
||||
key3_dir=$(generate_named_key "vendor-c")
|
||||
|
||||
# Upload all three keys
|
||||
upload_named_key "vendor-a" "$key1_dir" || return 1
|
||||
upload_named_key "vendor-b" "$key2_dir" || return 1
|
||||
upload_named_key "vendor-c" "$key3_dir" || return 1
|
||||
|
||||
log_info ""
|
||||
log_info "Keys in keyring before sealing:"
|
||||
list_keyring_keys
|
||||
show_keyring_status
|
||||
|
||||
# Seal the keyring
|
||||
log_info ""
|
||||
seal_keyring
|
||||
|
||||
# List keys after sealing
|
||||
log_info ""
|
||||
log_info "Keys in keyring after sealing:"
|
||||
list_keyring_keys
|
||||
show_keyring_status
|
||||
|
||||
log_pass "Key upload and keyring sealing succeeded"
|
||||
|
||||
# Create test device
|
||||
log_info ""
|
||||
create_test_device
|
||||
create_verity_hash
|
||||
|
||||
# Test 1: Sign with key1, should verify successfully
|
||||
log_info ""
|
||||
log_info "Sub-test: Verify with vendor-a key"
|
||||
if ! sign_root_hash_with_key "$key1_dir"; then
|
||||
log_fail "Failed to sign with vendor-a key"
|
||||
return 1
|
||||
fi
|
||||
if activate_verity_device "yes"; then
|
||||
log_pass "Verification with vendor-a key succeeded"
|
||||
deactivate_verity_device
|
||||
else
|
||||
log_fail "Verification with vendor-a key should succeed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test 2: Sign with key2, should also verify successfully
|
||||
log_info ""
|
||||
log_info "Sub-test: Verify with vendor-b key"
|
||||
if ! sign_root_hash_with_key "$key2_dir"; then
|
||||
log_fail "Failed to sign with vendor-b key"
|
||||
return 1
|
||||
fi
|
||||
if activate_verity_device "yes"; then
|
||||
log_pass "Verification with vendor-b key succeeded"
|
||||
deactivate_verity_device
|
||||
else
|
||||
log_fail "Verification with vendor-b key should succeed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test 3: Sign with key3, should also verify successfully
|
||||
log_info ""
|
||||
log_info "Sub-test: Verify with vendor-c key"
|
||||
if ! sign_root_hash_with_key "$key3_dir"; then
|
||||
log_fail "Failed to sign with vendor-c key"
|
||||
return 1
|
||||
fi
|
||||
if activate_verity_device "yes"; then
|
||||
log_pass "Verification with vendor-c key succeeded"
|
||||
deactivate_verity_device
|
||||
else
|
||||
log_fail "Verification with vendor-c key should succeed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test 4: Generate a key NOT in the keyring, should fail
|
||||
log_info ""
|
||||
log_info "Sub-test: Verify with unknown key (should fail)"
|
||||
local unknown_key_dir
|
||||
unknown_key_dir=$(generate_named_key "unknown-vendor")
|
||||
if ! sign_root_hash_with_key "$unknown_key_dir"; then
|
||||
log_fail "Failed to sign with unknown-vendor key"
|
||||
return 1
|
||||
fi
|
||||
if activate_verity_device "yes"; then
|
||||
log_fail "Verification with unknown key should fail"
|
||||
deactivate_verity_device
|
||||
return 1
|
||||
else
|
||||
log_pass "Verification with unknown key correctly rejected"
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
log_pass "Multiple keys test completed successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
sign_root_hash_with_key() {
|
||||
local key_dir="$1"
|
||||
|
||||
local root_hash
|
||||
root_hash=$(cat "$WORK_DIR/root_hash")
|
||||
|
||||
# Create the data to sign (hex string, not binary)
|
||||
echo -n "$root_hash" > "$WORK_DIR/root_hash.txt"
|
||||
|
||||
# Debug: show exactly what we're signing
|
||||
log_info "Root hash (hex): $root_hash"
|
||||
log_info "Root hash hex string size: $(wc -c < "$WORK_DIR/root_hash.txt") bytes"
|
||||
|
||||
# Create detached PKCS#7 signature
|
||||
if ! create_detached_signature "$WORK_DIR/root_hash.txt" "$WORK_DIR/root_hash.p7s" \
|
||||
"$key_dir/cert.pem" "$key_dir/private.pem"; then
|
||||
log_error "Failed to sign root hash with key from $key_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Debug: show signing certificate info
|
||||
log_info "Signed with certificate:"
|
||||
openssl x509 -in "$key_dir/cert.pem" -noout -subject 2>/dev/null | sed 's/^/ /'
|
||||
|
||||
# Debug: verify signature locally
|
||||
# -nointern: cert not in signature, use -certfile
|
||||
# -noverify: skip certificate chain validation (self-signed)
|
||||
if openssl smime -verify -binary -inform der -nointern -noverify \
|
||||
-in "$WORK_DIR/root_hash.p7s" \
|
||||
-content "$WORK_DIR/root_hash.txt" \
|
||||
-certfile "$key_dir/cert.pem" \
|
||||
-out /dev/null 2>/dev/null; then
|
||||
log_info "Local signature verification: PASSED"
|
||||
else
|
||||
log_warn "Local signature verification: FAILED"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Test: Verify corrupted signatures are rejected
|
||||
#
|
||||
test_corrupted_signature() {
|
||||
log_info "TEST: Verify corrupted signatures are rejected"
|
||||
|
||||
# This test requires a valid setup from test_multiple_keys or similar
|
||||
# It modifies the signature file and verifies rejection
|
||||
|
||||
if [ ! -f "$WORK_DIR/root_hash.p7s" ]; then
|
||||
log_warn "No signature file found, skipping corrupted signature test"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Save original signature
|
||||
cp "$WORK_DIR/root_hash.p7s" "$WORK_DIR/root_hash.p7s.orig"
|
||||
|
||||
# Test 1: Truncated signature
|
||||
log_info "Sub-test: Truncated signature (should fail)"
|
||||
head -c 100 "$WORK_DIR/root_hash.p7s.orig" > "$WORK_DIR/root_hash.p7s"
|
||||
if activate_verity_device "yes"; then
|
||||
log_fail "Truncated signature should be rejected"
|
||||
deactivate_verity_device
|
||||
cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
|
||||
return 1
|
||||
else
|
||||
log_pass "Truncated signature correctly rejected"
|
||||
fi
|
||||
|
||||
# Test 2: Corrupted signature (flip some bytes)
|
||||
log_info "Sub-test: Corrupted signature bytes (should fail)"
|
||||
cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
|
||||
# Corrupt bytes in the middle of the signature
|
||||
local sig_size
|
||||
sig_size=$(wc -c < "$WORK_DIR/root_hash.p7s")
|
||||
local corrupt_offset=$((sig_size / 2))
|
||||
printf '\xff\xff\xff\xff' | dd of="$WORK_DIR/root_hash.p7s" bs=1 seek=$corrupt_offset conv=notrunc 2>/dev/null
|
||||
if activate_verity_device "yes"; then
|
||||
log_fail "Corrupted signature should be rejected"
|
||||
deactivate_verity_device
|
||||
cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
|
||||
return 1
|
||||
else
|
||||
log_pass "Corrupted signature correctly rejected"
|
||||
fi
|
||||
|
||||
# Test 3: Signature over wrong data (sign different content)
|
||||
log_info "Sub-test: Signature over wrong data (should fail)"
|
||||
# Create a different root hash (all zeros as hex string)
|
||||
printf '%064d' 0 > "$WORK_DIR/wrong_hash.txt"
|
||||
# Get the first key directory that was used
|
||||
local key_dir="$WORK_DIR/keys/vendor-a"
|
||||
if [ -d "$key_dir" ]; then
|
||||
create_detached_signature "$WORK_DIR/wrong_hash.txt" "$WORK_DIR/root_hash.p7s" \
|
||||
"$key_dir/cert.pem" "$key_dir/private.pem"
|
||||
if activate_verity_device "yes"; then
|
||||
log_fail "Signature over wrong data should be rejected"
|
||||
deactivate_verity_device
|
||||
cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
|
||||
return 1
|
||||
else
|
||||
log_pass "Signature over wrong data correctly rejected"
|
||||
fi
|
||||
else
|
||||
log_warn "Key directory not found, skipping wrong data test"
|
||||
fi
|
||||
|
||||
# Restore original signature
|
||||
cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
|
||||
|
||||
log_pass "Corrupted signature test completed successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Test: Verify keyring is sealed when keyring_unsealed=0
|
||||
#
|
||||
test_keyring_sealed_by_default() {
|
||||
log_info "TEST: Verify keyring is sealed by default (keyring_unsealed=0)"
|
||||
|
||||
local keyring_id
|
||||
keyring_id=$(cat "$WORK_DIR/keyring_id")
|
||||
|
||||
log_info "Current keyring state (should be empty and sealed):"
|
||||
list_keyring_keys
|
||||
show_keyring_status
|
||||
|
||||
generate_keys
|
||||
|
||||
# Try to add a key - should fail if keyring is sealed
|
||||
log_info "Attempting to add key to sealed keyring..."
|
||||
if keyctl padd asymmetric "dm-verity-test" "$keyring_id" \
|
||||
< "$WORK_DIR/cert.der" 2>/dev/null; then
|
||||
log_fail "Keyring should be sealed when keyring_unsealed=0"
|
||||
list_keyring_keys
|
||||
return 1
|
||||
else
|
||||
log_pass "Keyring is correctly sealed when keyring_unsealed=0"
|
||||
log_info "Keyring state after failed add attempt:"
|
||||
list_keyring_keys
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Test: Verify dm-verity keyring is inactive when sealed empty
|
||||
#
|
||||
test_keyring_inactive_when_empty() {
|
||||
log_info "TEST: Verify dm-verity keyring is inactive when sealed empty"
|
||||
|
||||
# When keyring_unsealed=0, the keyring is sealed immediately while empty
|
||||
# This means it should NOT be used for verification (nr_leaves_on_tree=0)
|
||||
|
||||
log_info "Keyring state (should be empty and sealed):"
|
||||
list_keyring_keys
|
||||
show_keyring_status
|
||||
|
||||
create_test_device
|
||||
create_verity_hash
|
||||
|
||||
# Without any keys in the dm-verity keyring, and with it sealed,
|
||||
# verification should fall through to the secondary/platform keyrings
|
||||
# and likely succeed (if require_signatures=0) or fail (if =1)
|
||||
|
||||
log_info "Sub-test: Device activation with sealed empty keyring"
|
||||
if [ "$REQUIRE_SIGNATURES" = "Y" ] || [ "$REQUIRE_SIGNATURES" = "1" ]; then
|
||||
if activate_verity_device "no"; then
|
||||
log_fail "Device should NOT activate without signature when require_signatures=1"
|
||||
deactivate_verity_device
|
||||
return 1
|
||||
else
|
||||
log_pass "Device correctly rejected (require_signatures=1, no valid signature)"
|
||||
fi
|
||||
else
|
||||
if activate_verity_device "no"; then
|
||||
log_pass "Device activated (require_signatures=0, empty dm-verity keyring is inactive)"
|
||||
deactivate_verity_device
|
||||
else
|
||||
log_fail "Device should activate when require_signatures=0"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
main() {
|
||||
local rc=0
|
||||
|
||||
log_info "=== dm-verity keyring test ==="
|
||||
log_info ""
|
||||
|
||||
# Create work directory
|
||||
WORK_DIR=$(mktemp -d -t dm-verity-test.XXXXXX)
|
||||
log_info "Work directory: $WORK_DIR"
|
||||
|
||||
check_requirements
|
||||
|
||||
#
|
||||
# Test 1: UNSEALED keyring mode (keyring_unsealed=1)
|
||||
#
|
||||
log_info ""
|
||||
log_info "========================================"
|
||||
log_info "=== TEST MODE: UNSEALED KEYRING ==="
|
||||
log_info "========================================"
|
||||
log_info ""
|
||||
|
||||
load_dm_verity_module 1 1 # keyring_unsealed=1, require_signatures=1
|
||||
show_keyring_status
|
||||
|
||||
log_info ""
|
||||
if ! test_multiple_keys; then
|
||||
rc=1
|
||||
fi
|
||||
|
||||
# After sealing, verify it rejects new keys
|
||||
log_info ""
|
||||
if ! test_sealed_keyring_rejects_keys; then
|
||||
rc=1
|
||||
fi
|
||||
|
||||
# Test corrupted signatures are rejected
|
||||
log_info ""
|
||||
if ! test_corrupted_signature; then
|
||||
rc=1
|
||||
fi
|
||||
|
||||
# Clean up devices before reloading module
|
||||
deactivate_verity_device
|
||||
if [ -n "$DATA_DEV" ] && [[ "$DATA_DEV" == /dev/loop* ]]; then
|
||||
losetup -d "$DATA_DEV" 2>/dev/null || true
|
||||
DATA_DEV=""
|
||||
fi
|
||||
if [ -n "$HASH_DEV" ] && [[ "$HASH_DEV" == /dev/loop* ]]; then
|
||||
losetup -d "$HASH_DEV" 2>/dev/null || true
|
||||
HASH_DEV=""
|
||||
fi
|
||||
|
||||
#
|
||||
# Test 2: SEALED keyring mode (keyring_unsealed=0, default)
|
||||
#
|
||||
log_info ""
|
||||
log_info "========================================"
|
||||
log_info "=== TEST MODE: SEALED KEYRING (default) ==="
|
||||
log_info "========================================"
|
||||
log_info ""
|
||||
|
||||
load_dm_verity_module 0 0 # keyring_unsealed=0, require_signatures=0
|
||||
show_keyring_status
|
||||
|
||||
log_info ""
|
||||
if ! test_keyring_sealed_by_default; then
|
||||
rc=1
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
if ! test_keyring_inactive_when_empty; then
|
||||
rc=1
|
||||
fi
|
||||
|
||||
#
|
||||
# Summary
|
||||
#
|
||||
log_info ""
|
||||
log_info "========================================"
|
||||
if [ $rc -eq 0 ]; then
|
||||
log_info "=== All tests PASSED ==="
|
||||
else
|
||||
log_error "=== Some tests FAILED ==="
|
||||
fi
|
||||
log_info "========================================"
|
||||
|
||||
return $rc
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user