xref: /linux/tools/testing/selftests/dm-verity/test-dm-verity-keyring.sh (revision c17ee635fd3a482b2ad2bf5e269755c2eae5f25e)
1*f93bc869SChristian Brauner#!/bin/bash
2*f93bc869SChristian Brauner# SPDX-License-Identifier: GPL-2.0
3*f93bc869SChristian Brauner#
4*f93bc869SChristian Brauner# Test script for dm-verity keyring functionality
5*f93bc869SChristian Brauner#
6*f93bc869SChristian Brauner# This script has two modes depending on kernel configuration:
7*f93bc869SChristian Brauner#
8*f93bc869SChristian Brauner# 1. keyring_unsealed=1 AND require_signatures=1:
9*f93bc869SChristian Brauner#    - Upload a test key to the .dm-verity keyring
10*f93bc869SChristian Brauner#    - Seal the keyring
11*f93bc869SChristian Brauner#    - Create a dm-verity device with a signed root hash
12*f93bc869SChristian Brauner#    - Verify signature verification works
13*f93bc869SChristian Brauner#
14*f93bc869SChristian Brauner# 2. keyring_unsealed=0 (default) OR require_signatures=0:
15*f93bc869SChristian Brauner#    - Verify the keyring is already sealed (if unsealed=0)
16*f93bc869SChristian Brauner#    - Verify keys cannot be added to a sealed keyring
17*f93bc869SChristian Brauner#    - Verify the keyring is inactive (not used for verification)
18*f93bc869SChristian Brauner#
19*f93bc869SChristian Brauner# Requirements:
20*f93bc869SChristian Brauner# - Root privileges
21*f93bc869SChristian Brauner# - openssl
22*f93bc869SChristian Brauner# - veritysetup (cryptsetup)
23*f93bc869SChristian Brauner# - keyctl (keyutils)
24*f93bc869SChristian Brauner
25*f93bc869SChristian Braunerset -e
26*f93bc869SChristian Brauner
27*f93bc869SChristian BraunerWORK_DIR=""
28*f93bc869SChristian BraunerDATA_DEV=""
29*f93bc869SChristian BraunerHASH_DEV=""
30*f93bc869SChristian BraunerDM_NAME="verity-test-$$"
31*f93bc869SChristian BraunerCLEANUP_DONE=0
32*f93bc869SChristian Brauner
33*f93bc869SChristian Brauner# Module parameters (detected at runtime)
34*f93bc869SChristian BraunerKEYRING_UNSEALED=""
35*f93bc869SChristian BraunerREQUIRE_SIGNATURES=""
36*f93bc869SChristian Brauner
37*f93bc869SChristian Brauner# Colors for output
38*f93bc869SChristian BraunerRED='\033[0;31m'
39*f93bc869SChristian BraunerGREEN='\033[0;32m'
40*f93bc869SChristian BraunerYELLOW='\033[1;33m'
41*f93bc869SChristian BraunerNC='\033[0m' # No Color
42*f93bc869SChristian Brauner
43*f93bc869SChristian Braunerlog_info() {
44*f93bc869SChristian Brauner    echo -e "${GREEN}[INFO]${NC} $*"
45*f93bc869SChristian Brauner}
46*f93bc869SChristian Brauner
47*f93bc869SChristian Braunerlog_warn() {
48*f93bc869SChristian Brauner    echo -e "${YELLOW}[WARN]${NC} $*"
49*f93bc869SChristian Brauner}
50*f93bc869SChristian Brauner
51*f93bc869SChristian Braunerlog_error() {
52*f93bc869SChristian Brauner    echo -e "${RED}[ERROR]${NC} $*" >&2
53*f93bc869SChristian Brauner}
54*f93bc869SChristian Brauner
55*f93bc869SChristian Braunerlog_pass() {
56*f93bc869SChristian Brauner    echo -e "${GREEN}[PASS]${NC} $*"
57*f93bc869SChristian Brauner}
58*f93bc869SChristian Brauner
59*f93bc869SChristian Braunerlog_fail() {
60*f93bc869SChristian Brauner    echo -e "${RED}[FAIL]${NC} $*" >&2
61*f93bc869SChristian Brauner}
62*f93bc869SChristian Brauner
63*f93bc869SChristian Braunerlog_skip() {
64*f93bc869SChristian Brauner    echo -e "${YELLOW}[SKIP]${NC} $*"
65*f93bc869SChristian Brauner}
66*f93bc869SChristian Brauner
67*f93bc869SChristian Braunercleanup() {
68*f93bc869SChristian Brauner    if [ "$CLEANUP_DONE" -eq 1 ]; then
69*f93bc869SChristian Brauner        return
70*f93bc869SChristian Brauner    fi
71*f93bc869SChristian Brauner    CLEANUP_DONE=1
72*f93bc869SChristian Brauner
73*f93bc869SChristian Brauner    log_info "Cleaning up..."
74*f93bc869SChristian Brauner
75*f93bc869SChristian Brauner    # Remove dm-verity device if it exists
76*f93bc869SChristian Brauner    if dmsetup info "$DM_NAME" &>/dev/null; then
77*f93bc869SChristian Brauner        dmsetup remove "$DM_NAME" 2>/dev/null || true
78*f93bc869SChristian Brauner    fi
79*f93bc869SChristian Brauner
80*f93bc869SChristian Brauner    # Detach loop devices
81*f93bc869SChristian Brauner    if [ -n "$DATA_DEV" ] && [[ "$DATA_DEV" == /dev/loop* ]]; then
82*f93bc869SChristian Brauner        losetup -d "$DATA_DEV" 2>/dev/null || true
83*f93bc869SChristian Brauner    fi
84*f93bc869SChristian Brauner    if [ -n "$HASH_DEV" ] && [[ "$HASH_DEV" == /dev/loop* ]]; then
85*f93bc869SChristian Brauner        losetup -d "$HASH_DEV" 2>/dev/null || true
86*f93bc869SChristian Brauner    fi
87*f93bc869SChristian Brauner
88*f93bc869SChristian Brauner    # Remove work directory
89*f93bc869SChristian Brauner    if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
90*f93bc869SChristian Brauner        rm -rf "$WORK_DIR"
91*f93bc869SChristian Brauner    fi
92*f93bc869SChristian Brauner}
93*f93bc869SChristian Brauner
94*f93bc869SChristian Braunertrap cleanup EXIT
95*f93bc869SChristian Brauner
96*f93bc869SChristian Braunerdie() {
97*f93bc869SChristian Brauner    log_error "$*"
98*f93bc869SChristian Brauner    exit 1
99*f93bc869SChristian Brauner}
100*f93bc869SChristian Brauner
101*f93bc869SChristian Braunerfind_dm_verity_keyring() {
102*f93bc869SChristian Brauner    # The .dm-verity keyring is not linked to user-accessible keyrings,
103*f93bc869SChristian Brauner    # so we need to find it via /proc/keys
104*f93bc869SChristian Brauner    local serial_hex
105*f93bc869SChristian Brauner    serial_hex=$(awk '/\.dm-verity/ {print $1}' /proc/keys 2>/dev/null)
106*f93bc869SChristian Brauner
107*f93bc869SChristian Brauner    if [ -z "$serial_hex" ]; then
108*f93bc869SChristian Brauner        return 1
109*f93bc869SChristian Brauner    fi
110*f93bc869SChristian Brauner
111*f93bc869SChristian Brauner    # Convert hex to decimal for keyctl
112*f93bc869SChristian Brauner    echo $((16#$serial_hex))
113*f93bc869SChristian Brauner}
114*f93bc869SChristian Brauner
115*f93bc869SChristian Braunerget_module_param() {
116*f93bc869SChristian Brauner    local param="$1"
117*f93bc869SChristian Brauner    local path="/sys/module/dm_verity/parameters/$param"
118*f93bc869SChristian Brauner
119*f93bc869SChristian Brauner    if [ -f "$path" ]; then
120*f93bc869SChristian Brauner        cat "$path"
121*f93bc869SChristian Brauner    else
122*f93bc869SChristian Brauner        echo ""
123*f93bc869SChristian Brauner    fi
124*f93bc869SChristian Brauner}
125*f93bc869SChristian Brauner
126*f93bc869SChristian Braunercheck_requirements() {
127*f93bc869SChristian Brauner    log_info "Checking requirements..."
128*f93bc869SChristian Brauner
129*f93bc869SChristian Brauner    # Check for root
130*f93bc869SChristian Brauner    if [ "$(id -u)" -ne 0 ]; then
131*f93bc869SChristian Brauner        die "This script must be run as root"
132*f93bc869SChristian Brauner    fi
133*f93bc869SChristian Brauner
134*f93bc869SChristian Brauner    # Check for required tools
135*f93bc869SChristian Brauner    for cmd in openssl veritysetup keyctl losetup dmsetup dd awk; do
136*f93bc869SChristian Brauner        if ! command -v "$cmd" &>/dev/null; then
137*f93bc869SChristian Brauner            die "Required command not found: $cmd"
138*f93bc869SChristian Brauner        fi
139*f93bc869SChristian Brauner    done
140*f93bc869SChristian Brauner
141*f93bc869SChristian Brauner    # Check for dm-verity module
142*f93bc869SChristian Brauner    if ! modprobe -n dm-verity &>/dev/null; then
143*f93bc869SChristian Brauner        die "dm-verity module not available"
144*f93bc869SChristian Brauner    fi
145*f93bc869SChristian Brauner
146*f93bc869SChristian Brauner    # Verify OpenSSL can create signatures
147*f93bc869SChristian Brauner    # OpenSSL cms -sign with -binary -outform DER creates detached signatures by default
148*f93bc869SChristian Brauner    log_info "Using OpenSSL for PKCS#7 signatures"
149*f93bc869SChristian Brauner}
150*f93bc869SChristian Brauner
151*f93bc869SChristian Braunerload_dm_verity_module() {
152*f93bc869SChristian Brauner    local keyring_unsealed="${1:-0}"
153*f93bc869SChristian Brauner    local require_signatures="${2:-0}"
154*f93bc869SChristian Brauner
155*f93bc869SChristian Brauner    log_info "Loading dm-verity module with keyring_unsealed=$keyring_unsealed require_signatures=$require_signatures"
156*f93bc869SChristian Brauner
157*f93bc869SChristian Brauner    # Unload if already loaded
158*f93bc869SChristian Brauner    if lsmod | grep -q '^dm_verity'; then
159*f93bc869SChristian Brauner        log_info "Unloading existing dm-verity module..."
160*f93bc869SChristian Brauner        modprobe -r dm-verity 2>/dev/null || \
161*f93bc869SChristian Brauner            die "Failed to unload dm-verity module (may be in use)"
162*f93bc869SChristian Brauner        sleep 1
163*f93bc869SChristian Brauner    fi
164*f93bc869SChristian Brauner
165*f93bc869SChristian Brauner    # Load with specified parameters
166*f93bc869SChristian Brauner    modprobe dm-verity keyring_unsealed="$keyring_unsealed" require_signatures="$require_signatures" || \
167*f93bc869SChristian Brauner        die "Failed to load dm-verity module"
168*f93bc869SChristian Brauner
169*f93bc869SChristian Brauner    # Wait for keyring to be created (poll with timeout)
170*f93bc869SChristian Brauner    local keyring_id=""
171*f93bc869SChristian Brauner    local timeout=50  # 5 seconds (50 * 0.1s)
172*f93bc869SChristian Brauner    while [ $timeout -gt 0 ]; do
173*f93bc869SChristian Brauner        keyring_id=$(find_dm_verity_keyring) && break
174*f93bc869SChristian Brauner        sleep 0.1
175*f93bc869SChristian Brauner        timeout=$((timeout - 1))
176*f93bc869SChristian Brauner    done
177*f93bc869SChristian Brauner
178*f93bc869SChristian Brauner    if [ -z "$keyring_id" ]; then
179*f93bc869SChristian Brauner        die "dm-verity keyring not found after module load (timeout)"
180*f93bc869SChristian Brauner    fi
181*f93bc869SChristian Brauner
182*f93bc869SChristian Brauner    log_info "Found .dm-verity keyring: $keyring_id"
183*f93bc869SChristian Brauner    echo "$keyring_id" > "$WORK_DIR/keyring_id"
184*f93bc869SChristian Brauner
185*f93bc869SChristian Brauner    # Read and display module parameters
186*f93bc869SChristian Brauner    KEYRING_UNSEALED=$(get_module_param "keyring_unsealed")
187*f93bc869SChristian Brauner    REQUIRE_SIGNATURES=$(get_module_param "require_signatures")
188*f93bc869SChristian Brauner
189*f93bc869SChristian Brauner    log_info "Module parameters:"
190*f93bc869SChristian Brauner    log_info "  keyring_unsealed=$KEYRING_UNSEALED"
191*f93bc869SChristian Brauner    log_info "  require_signatures=$REQUIRE_SIGNATURES"
192*f93bc869SChristian Brauner}
193*f93bc869SChristian Brauner
194*f93bc869SChristian Braunerunload_dm_verity_module() {
195*f93bc869SChristian Brauner    log_info "Unloading dm-verity module..."
196*f93bc869SChristian Brauner
197*f93bc869SChristian Brauner    # Clean up any dm-verity devices first
198*f93bc869SChristian Brauner    local dm_dev
199*f93bc869SChristian Brauner    while read -r dm_dev _; do
200*f93bc869SChristian Brauner        [ -n "$dm_dev" ] || continue
201*f93bc869SChristian Brauner        log_info "Removing dm-verity device: $dm_dev"
202*f93bc869SChristian Brauner        dmsetup remove "$dm_dev" 2>/dev/null || true
203*f93bc869SChristian Brauner    done < <(dmsetup ls --target verity 2>/dev/null)
204*f93bc869SChristian Brauner
205*f93bc869SChristian Brauner    if lsmod | grep -q '^dm_verity'; then
206*f93bc869SChristian Brauner        modprobe -r dm-verity 2>/dev/null || \
207*f93bc869SChristian Brauner            log_warn "Failed to unload dm-verity module"
208*f93bc869SChristian Brauner        sleep 1
209*f93bc869SChristian Brauner    fi
210*f93bc869SChristian Brauner}
211*f93bc869SChristian Brauner
212*f93bc869SChristian Braunergenerate_keys() {
213*f93bc869SChristian Brauner    log_info "Generating signing key pair..."
214*f93bc869SChristian Brauner
215*f93bc869SChristian Brauner    # Generate private key (2048-bit for faster test execution)
216*f93bc869SChristian Brauner    openssl genrsa -out "$WORK_DIR/private.pem" 2048 2>/dev/null
217*f93bc869SChristian Brauner
218*f93bc869SChristian Brauner    # Create OpenSSL config for certificate extensions
219*f93bc869SChristian Brauner    # The kernel requires digitalSignature key usage for signature verification
220*f93bc869SChristian Brauner    # Both subjectKeyIdentifier and authorityKeyIdentifier are needed for
221*f93bc869SChristian Brauner    # the kernel to match keys in the keyring (especially for self-signed certs)
222*f93bc869SChristian Brauner    cat > "$WORK_DIR/openssl.cnf" << 'EOF'
223*f93bc869SChristian Brauner[req]
224*f93bc869SChristian Braunerdistinguished_name = req_distinguished_name
225*f93bc869SChristian Braunerx509_extensions = v3_ca
226*f93bc869SChristian Braunerprompt = no
227*f93bc869SChristian Brauner
228*f93bc869SChristian Brauner[req_distinguished_name]
229*f93bc869SChristian BraunerCN = dm-verity-test-key
230*f93bc869SChristian Brauner
231*f93bc869SChristian Brauner[v3_ca]
232*f93bc869SChristian BraunerbasicConstraints = critical,CA:FALSE
233*f93bc869SChristian BraunerkeyUsage = digitalSignature
234*f93bc869SChristian BraunersubjectKeyIdentifier = hash
235*f93bc869SChristian BraunerauthorityKeyIdentifier = keyid
236*f93bc869SChristian BraunerEOF
237*f93bc869SChristian Brauner
238*f93bc869SChristian Brauner    # Generate self-signed certificate with proper extensions
239*f93bc869SChristian Brauner    openssl req -new -x509 -key "$WORK_DIR/private.pem" \
240*f93bc869SChristian Brauner        -out "$WORK_DIR/cert.pem" -days 365 \
241*f93bc869SChristian Brauner        -config "$WORK_DIR/openssl.cnf" 2>/dev/null
242*f93bc869SChristian Brauner
243*f93bc869SChristian Brauner    # Convert certificate to DER format for kernel
244*f93bc869SChristian Brauner    openssl x509 -in "$WORK_DIR/cert.pem" -outform DER \
245*f93bc869SChristian Brauner        -out "$WORK_DIR/cert.der"
246*f93bc869SChristian Brauner
247*f93bc869SChristian Brauner    # Show certificate info for debugging
248*f93bc869SChristian Brauner    log_info "Certificate details:"
249*f93bc869SChristian Brauner    openssl x509 -in "$WORK_DIR/cert.pem" -noout -text 2>/dev/null | \
250*f93bc869SChristian Brauner        grep -E "Subject:|Issuer:|Key Usage|Extended" | head -10
251*f93bc869SChristian Brauner
252*f93bc869SChristian Brauner    log_info "Keys generated successfully"
253*f93bc869SChristian Brauner}
254*f93bc869SChristian Brauner
255*f93bc869SChristian Braunerseal_keyring() {
256*f93bc869SChristian Brauner    log_info "Sealing the .dm-verity keyring..."
257*f93bc869SChristian Brauner
258*f93bc869SChristian Brauner    local keyring_id
259*f93bc869SChristian Brauner    keyring_id=$(cat "$WORK_DIR/keyring_id")
260*f93bc869SChristian Brauner
261*f93bc869SChristian Brauner    keyctl restrict_keyring "$keyring_id" || \
262*f93bc869SChristian Brauner        die "Failed to seal keyring"
263*f93bc869SChristian Brauner
264*f93bc869SChristian Brauner    log_info "Keyring sealed successfully"
265*f93bc869SChristian Brauner}
266*f93bc869SChristian Brauner
267*f93bc869SChristian Braunercreate_test_device() {
268*f93bc869SChristian Brauner    log_info "Creating test device images..."
269*f93bc869SChristian Brauner
270*f93bc869SChristian Brauner    # Create data image with random content (8MB is sufficient for testing)
271*f93bc869SChristian Brauner    dd if=/dev/urandom of="$WORK_DIR/data.img" bs=1M count=8 status=none
272*f93bc869SChristian Brauner
273*f93bc869SChristian Brauner    # Create hash image (will be populated by veritysetup)
274*f93bc869SChristian Brauner    dd if=/dev/zero of="$WORK_DIR/hash.img" bs=1M count=1 status=none
275*f93bc869SChristian Brauner
276*f93bc869SChristian Brauner    # Setup loop devices
277*f93bc869SChristian Brauner    DATA_DEV=$(losetup --find --show "$WORK_DIR/data.img")
278*f93bc869SChristian Brauner    HASH_DEV=$(losetup --find --show "$WORK_DIR/hash.img")
279*f93bc869SChristian Brauner
280*f93bc869SChristian Brauner    log_info "Data device: $DATA_DEV"
281*f93bc869SChristian Brauner    log_info "Hash device: $HASH_DEV"
282*f93bc869SChristian Brauner}
283*f93bc869SChristian Brauner
284*f93bc869SChristian Braunercreate_verity_hash() {
285*f93bc869SChristian Brauner    log_info "Creating dm-verity hash tree..."
286*f93bc869SChristian Brauner
287*f93bc869SChristian Brauner    local root_hash output
288*f93bc869SChristian Brauner    output=$(veritysetup format "$DATA_DEV" "$HASH_DEV" 2>&1)
289*f93bc869SChristian Brauner    root_hash=$(echo "$output" | grep "Root hash:" | awk '{print $3}')
290*f93bc869SChristian Brauner
291*f93bc869SChristian Brauner    if [ -z "$root_hash" ]; then
292*f93bc869SChristian Brauner        log_error "veritysetup format output:"
293*f93bc869SChristian Brauner        echo "$output" | sed 's/^/  /'
294*f93bc869SChristian Brauner        die "Failed to get root hash from veritysetup format"
295*f93bc869SChristian Brauner    fi
296*f93bc869SChristian Brauner
297*f93bc869SChristian Brauner    echo "$root_hash" > "$WORK_DIR/root_hash"
298*f93bc869SChristian Brauner    log_info "Root hash: $root_hash"
299*f93bc869SChristian Brauner}
300*f93bc869SChristian Brauner
301*f93bc869SChristian Braunercreate_detached_signature() {
302*f93bc869SChristian Brauner    local infile="$1"
303*f93bc869SChristian Brauner    local outfile="$2"
304*f93bc869SChristian Brauner    local cert="$3"
305*f93bc869SChristian Brauner    local key="$4"
306*f93bc869SChristian Brauner
307*f93bc869SChristian Brauner    # Use openssl smime (not cms) for PKCS#7 signatures compatible with kernel
308*f93bc869SChristian Brauner    # Flags from working veritysetup example:
309*f93bc869SChristian Brauner    #   -nocerts: don't include certificate in signature
310*f93bc869SChristian Brauner    #   -noattr: no signed attributes
311*f93bc869SChristian Brauner    #   -binary: binary input mode
312*f93bc869SChristian Brauner    if openssl smime -sign -nocerts -noattr -binary \
313*f93bc869SChristian Brauner        -in "$infile" \
314*f93bc869SChristian Brauner        -inkey "$key" \
315*f93bc869SChristian Brauner        -signer "$cert" \
316*f93bc869SChristian Brauner        -outform der \
317*f93bc869SChristian Brauner        -out "$outfile" 2>/dev/null; then
318*f93bc869SChristian Brauner        return 0
319*f93bc869SChristian Brauner    fi
320*f93bc869SChristian Brauner
321*f93bc869SChristian Brauner    log_error "Failed to create signature"
322*f93bc869SChristian Brauner    return 1
323*f93bc869SChristian Brauner}
324*f93bc869SChristian Brauner
325*f93bc869SChristian Brauneractivate_verity_device() {
326*f93bc869SChristian Brauner    local with_sig="$1"
327*f93bc869SChristian Brauner    local root_hash
328*f93bc869SChristian Brauner    root_hash=$(cat "$WORK_DIR/root_hash")
329*f93bc869SChristian Brauner
330*f93bc869SChristian Brauner    # Clear dmesg and capture any kernel messages during activation
331*f93bc869SChristian Brauner    dmesg -C 2>/dev/null || true
332*f93bc869SChristian Brauner
333*f93bc869SChristian Brauner    if [ "$with_sig" = "yes" ]; then
334*f93bc869SChristian Brauner        log_info "Activating dm-verity device with signature..."
335*f93bc869SChristian Brauner        veritysetup open "$DATA_DEV" "$DM_NAME" "$HASH_DEV" "$root_hash" \
336*f93bc869SChristian Brauner            --root-hash-signature="$WORK_DIR/root_hash.p7s" 2>&1
337*f93bc869SChristian Brauner        local ret=$?
338*f93bc869SChristian Brauner    else
339*f93bc869SChristian Brauner        log_info "Activating dm-verity device without signature..."
340*f93bc869SChristian Brauner        veritysetup open "$DATA_DEV" "$DM_NAME" "$HASH_DEV" "$root_hash" 2>&1
341*f93bc869SChristian Brauner        local ret=$?
342*f93bc869SChristian Brauner    fi
343*f93bc869SChristian Brauner
344*f93bc869SChristian Brauner    # Show relevant kernel messages
345*f93bc869SChristian Brauner    local kmsg
346*f93bc869SChristian Brauner    kmsg=$(dmesg 2>/dev/null | grep -i -E 'verity|pkcs|signature|asymmetric|key' | tail -10)
347*f93bc869SChristian Brauner    if [ -n "$kmsg" ]; then
348*f93bc869SChristian Brauner        log_info "Kernel messages:"
349*f93bc869SChristian Brauner        echo "$kmsg" | while read -r line; do echo "  $line"; done
350*f93bc869SChristian Brauner    fi
351*f93bc869SChristian Brauner
352*f93bc869SChristian Brauner    return $ret
353*f93bc869SChristian Brauner}
354*f93bc869SChristian Brauner
355*f93bc869SChristian Braunerdeactivate_verity_device() {
356*f93bc869SChristian Brauner    if dmsetup info "$DM_NAME" &>/dev/null; then
357*f93bc869SChristian Brauner        dmsetup remove "$DM_NAME" 2>/dev/null || true
358*f93bc869SChristian Brauner    fi
359*f93bc869SChristian Brauner}
360*f93bc869SChristian Brauner
361*f93bc869SChristian Braunershow_keyring_status() {
362*f93bc869SChristian Brauner    log_info "Keyring status:"
363*f93bc869SChristian Brauner
364*f93bc869SChristian Brauner    local keyring_id
365*f93bc869SChristian Brauner    keyring_id=$(find_dm_verity_keyring) || true
366*f93bc869SChristian Brauner
367*f93bc869SChristian Brauner    if [ -n "$keyring_id" ]; then
368*f93bc869SChristian Brauner        echo "  Keyring ID: $keyring_id"
369*f93bc869SChristian Brauner        keyctl show "$keyring_id" 2>/dev/null || true
370*f93bc869SChristian Brauner        grep '\.dm-verity' /proc/keys 2>/dev/null || true
371*f93bc869SChristian Brauner    fi
372*f93bc869SChristian Brauner}
373*f93bc869SChristian Brauner
374*f93bc869SChristian Braunerlist_keyring_keys() {
375*f93bc869SChristian Brauner    log_info "Keys in .dm-verity keyring:"
376*f93bc869SChristian Brauner
377*f93bc869SChristian Brauner    local keyring_id
378*f93bc869SChristian Brauner    keyring_id=$(cat "$WORK_DIR/keyring_id" 2>/dev/null) || \
379*f93bc869SChristian Brauner        keyring_id=$(find_dm_verity_keyring) || true
380*f93bc869SChristian Brauner
381*f93bc869SChristian Brauner    if [ -z "$keyring_id" ]; then
382*f93bc869SChristian Brauner        log_warn "Could not find keyring"
383*f93bc869SChristian Brauner        return
384*f93bc869SChristian Brauner    fi
385*f93bc869SChristian Brauner
386*f93bc869SChristian Brauner    # List all keys in the keyring
387*f93bc869SChristian Brauner    local keys
388*f93bc869SChristian Brauner    keys=$(keyctl list "$keyring_id" 2>/dev/null)
389*f93bc869SChristian Brauner    if [ -z "$keys" ] || [ "$keys" = "keyring is empty" ]; then
390*f93bc869SChristian Brauner        echo "  (empty)"
391*f93bc869SChristian Brauner    else
392*f93bc869SChristian Brauner        echo "$keys" | while read -r line; do
393*f93bc869SChristian Brauner            echo "  $line"
394*f93bc869SChristian Brauner        done
395*f93bc869SChristian Brauner
396*f93bc869SChristian Brauner        # Show detailed info for each key
397*f93bc869SChristian Brauner        log_info "Key details:"
398*f93bc869SChristian Brauner        keyctl list "$keyring_id" 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]+$' | while read -r key_id; do
399*f93bc869SChristian Brauner            echo "  Key $key_id:"
400*f93bc869SChristian Brauner            keyctl describe "$key_id" 2>/dev/null | sed 's/^/    /'
401*f93bc869SChristian Brauner        done
402*f93bc869SChristian Brauner    fi
403*f93bc869SChristian Brauner}
404*f93bc869SChristian Brauner
405*f93bc869SChristian Braunergenerate_named_key() {
406*f93bc869SChristian Brauner    local name="$1"
407*f93bc869SChristian Brauner    local key_dir="$WORK_DIR/keys/$name"
408*f93bc869SChristian Brauner
409*f93bc869SChristian Brauner    mkdir -p "$key_dir"
410*f93bc869SChristian Brauner
411*f93bc869SChristian Brauner    # Log to stderr so it doesn't interfere with return value
412*f93bc869SChristian Brauner    echo "[INFO] Generating key pair: $name" >&2
413*f93bc869SChristian Brauner
414*f93bc869SChristian Brauner    # Generate private key
415*f93bc869SChristian Brauner    openssl genrsa -out "$key_dir/private.pem" 2048 2>/dev/null
416*f93bc869SChristian Brauner
417*f93bc869SChristian Brauner    # Create OpenSSL config for certificate extensions
418*f93bc869SChristian Brauner    # Both subjectKeyIdentifier and authorityKeyIdentifier are needed for
419*f93bc869SChristian Brauner    # the kernel to match keys in the keyring (especially for self-signed certs)
420*f93bc869SChristian Brauner    cat > "$key_dir/openssl.cnf" << EOF
421*f93bc869SChristian Brauner[req]
422*f93bc869SChristian Braunerdistinguished_name = req_distinguished_name
423*f93bc869SChristian Braunerx509_extensions = v3_ca
424*f93bc869SChristian Braunerprompt = no
425*f93bc869SChristian Brauner
426*f93bc869SChristian Brauner[req_distinguished_name]
427*f93bc869SChristian BraunerCN = dm-verity-test-$name
428*f93bc869SChristian Brauner
429*f93bc869SChristian Brauner[v3_ca]
430*f93bc869SChristian BraunerbasicConstraints = critical,CA:FALSE
431*f93bc869SChristian BraunerkeyUsage = digitalSignature
432*f93bc869SChristian BraunersubjectKeyIdentifier = hash
433*f93bc869SChristian BraunerauthorityKeyIdentifier = keyid
434*f93bc869SChristian BraunerEOF
435*f93bc869SChristian Brauner
436*f93bc869SChristian Brauner    # Generate self-signed certificate with proper extensions
437*f93bc869SChristian Brauner    openssl req -new -x509 -key "$key_dir/private.pem" \
438*f93bc869SChristian Brauner        -out "$key_dir/cert.pem" -days 365 \
439*f93bc869SChristian Brauner        -config "$key_dir/openssl.cnf" 2>/dev/null
440*f93bc869SChristian Brauner
441*f93bc869SChristian Brauner    # Convert certificate to DER format for kernel
442*f93bc869SChristian Brauner    openssl x509 -in "$key_dir/cert.pem" -outform DER \
443*f93bc869SChristian Brauner        -out "$key_dir/cert.der"
444*f93bc869SChristian Brauner
445*f93bc869SChristian Brauner    # Return the key directory path (only this goes to stdout)
446*f93bc869SChristian Brauner    echo "$key_dir"
447*f93bc869SChristian Brauner}
448*f93bc869SChristian Brauner
449*f93bc869SChristian Braunerupload_named_key() {
450*f93bc869SChristian Brauner    local name="$1"
451*f93bc869SChristian Brauner    local key_dir="$2"
452*f93bc869SChristian Brauner
453*f93bc869SChristian Brauner    local keyring_id
454*f93bc869SChristian Brauner    keyring_id=$(cat "$WORK_DIR/keyring_id")
455*f93bc869SChristian Brauner
456*f93bc869SChristian Brauner    log_info "Uploading key '$name' to keyring..."
457*f93bc869SChristian Brauner
458*f93bc869SChristian Brauner    local key_id
459*f93bc869SChristian Brauner    if key_id=$(keyctl padd asymmetric "$name" "$keyring_id" \
460*f93bc869SChristian Brauner        < "$key_dir/cert.der" 2>&1); then
461*f93bc869SChristian Brauner        log_info "Key '$name' uploaded with ID: $key_id"
462*f93bc869SChristian Brauner        echo "$key_id" > "$key_dir/key_id"
463*f93bc869SChristian Brauner        return 0
464*f93bc869SChristian Brauner    else
465*f93bc869SChristian Brauner        log_error "Failed to upload key '$name': $key_id"
466*f93bc869SChristian Brauner        return 1
467*f93bc869SChristian Brauner    fi
468*f93bc869SChristian Brauner}
469*f93bc869SChristian Brauner
470*f93bc869SChristian Brauner#
471*f93bc869SChristian Brauner# Test: Verify sealed keyring rejects key additions
472*f93bc869SChristian Brauner#
473*f93bc869SChristian Braunertest_sealed_keyring_rejects_keys() {
474*f93bc869SChristian Brauner    log_info "TEST: Verify sealed keyring rejects key additions"
475*f93bc869SChristian Brauner
476*f93bc869SChristian Brauner    local keyring_id
477*f93bc869SChristian Brauner    keyring_id=$(cat "$WORK_DIR/keyring_id")
478*f93bc869SChristian Brauner
479*f93bc869SChristian Brauner    generate_keys
480*f93bc869SChristian Brauner
481*f93bc869SChristian Brauner    # Try to add a key - should fail
482*f93bc869SChristian Brauner    if keyctl padd asymmetric "dm-verity-test" "$keyring_id" \
483*f93bc869SChristian Brauner        < "$WORK_DIR/cert.der" 2>/dev/null; then
484*f93bc869SChristian Brauner        log_fail "Key addition should have been rejected on sealed keyring"
485*f93bc869SChristian Brauner        return 1
486*f93bc869SChristian Brauner    else
487*f93bc869SChristian Brauner        log_pass "Sealed keyring correctly rejected key addition"
488*f93bc869SChristian Brauner        return 0
489*f93bc869SChristian Brauner    fi
490*f93bc869SChristian Brauner}
491*f93bc869SChristian Brauner
492*f93bc869SChristian Brauner#
493*f93bc869SChristian Brauner# Test: Multiple keys in keyring
494*f93bc869SChristian Brauner#
495*f93bc869SChristian Braunertest_multiple_keys() {
496*f93bc869SChristian Brauner    log_info "TEST: Multiple keys in keyring"
497*f93bc869SChristian Brauner
498*f93bc869SChristian Brauner    local key1_dir key2_dir key3_dir
499*f93bc869SChristian Brauner
500*f93bc869SChristian Brauner    # Generate three different keys
501*f93bc869SChristian Brauner    key1_dir=$(generate_named_key "vendor-a")
502*f93bc869SChristian Brauner    key2_dir=$(generate_named_key "vendor-b")
503*f93bc869SChristian Brauner    key3_dir=$(generate_named_key "vendor-c")
504*f93bc869SChristian Brauner
505*f93bc869SChristian Brauner    # Upload all three keys
506*f93bc869SChristian Brauner    upload_named_key "vendor-a" "$key1_dir" || return 1
507*f93bc869SChristian Brauner    upload_named_key "vendor-b" "$key2_dir" || return 1
508*f93bc869SChristian Brauner    upload_named_key "vendor-c" "$key3_dir" || return 1
509*f93bc869SChristian Brauner
510*f93bc869SChristian Brauner    log_info ""
511*f93bc869SChristian Brauner    log_info "Keys in keyring before sealing:"
512*f93bc869SChristian Brauner    list_keyring_keys
513*f93bc869SChristian Brauner    show_keyring_status
514*f93bc869SChristian Brauner
515*f93bc869SChristian Brauner    # Seal the keyring
516*f93bc869SChristian Brauner    log_info ""
517*f93bc869SChristian Brauner    seal_keyring
518*f93bc869SChristian Brauner
519*f93bc869SChristian Brauner    # List keys after sealing
520*f93bc869SChristian Brauner    log_info ""
521*f93bc869SChristian Brauner    log_info "Keys in keyring after sealing:"
522*f93bc869SChristian Brauner    list_keyring_keys
523*f93bc869SChristian Brauner    show_keyring_status
524*f93bc869SChristian Brauner
525*f93bc869SChristian Brauner    log_pass "Key upload and keyring sealing succeeded"
526*f93bc869SChristian Brauner
527*f93bc869SChristian Brauner    # Create test device
528*f93bc869SChristian Brauner    log_info ""
529*f93bc869SChristian Brauner    create_test_device
530*f93bc869SChristian Brauner    create_verity_hash
531*f93bc869SChristian Brauner
532*f93bc869SChristian Brauner    # Test 1: Sign with key1, should verify successfully
533*f93bc869SChristian Brauner    log_info ""
534*f93bc869SChristian Brauner    log_info "Sub-test: Verify with vendor-a key"
535*f93bc869SChristian Brauner    if ! sign_root_hash_with_key "$key1_dir"; then
536*f93bc869SChristian Brauner        log_fail "Failed to sign with vendor-a key"
537*f93bc869SChristian Brauner        return 1
538*f93bc869SChristian Brauner    fi
539*f93bc869SChristian Brauner    if activate_verity_device "yes"; then
540*f93bc869SChristian Brauner        log_pass "Verification with vendor-a key succeeded"
541*f93bc869SChristian Brauner        deactivate_verity_device
542*f93bc869SChristian Brauner    else
543*f93bc869SChristian Brauner        log_fail "Verification with vendor-a key should succeed"
544*f93bc869SChristian Brauner        return 1
545*f93bc869SChristian Brauner    fi
546*f93bc869SChristian Brauner
547*f93bc869SChristian Brauner    # Test 2: Sign with key2, should also verify successfully
548*f93bc869SChristian Brauner    log_info ""
549*f93bc869SChristian Brauner    log_info "Sub-test: Verify with vendor-b key"
550*f93bc869SChristian Brauner    if ! sign_root_hash_with_key "$key2_dir"; then
551*f93bc869SChristian Brauner        log_fail "Failed to sign with vendor-b key"
552*f93bc869SChristian Brauner        return 1
553*f93bc869SChristian Brauner    fi
554*f93bc869SChristian Brauner    if activate_verity_device "yes"; then
555*f93bc869SChristian Brauner        log_pass "Verification with vendor-b key succeeded"
556*f93bc869SChristian Brauner        deactivate_verity_device
557*f93bc869SChristian Brauner    else
558*f93bc869SChristian Brauner        log_fail "Verification with vendor-b key should succeed"
559*f93bc869SChristian Brauner        return 1
560*f93bc869SChristian Brauner    fi
561*f93bc869SChristian Brauner
562*f93bc869SChristian Brauner    # Test 3: Sign with key3, should also verify successfully
563*f93bc869SChristian Brauner    log_info ""
564*f93bc869SChristian Brauner    log_info "Sub-test: Verify with vendor-c key"
565*f93bc869SChristian Brauner    if ! sign_root_hash_with_key "$key3_dir"; then
566*f93bc869SChristian Brauner        log_fail "Failed to sign with vendor-c key"
567*f93bc869SChristian Brauner        return 1
568*f93bc869SChristian Brauner    fi
569*f93bc869SChristian Brauner    if activate_verity_device "yes"; then
570*f93bc869SChristian Brauner        log_pass "Verification with vendor-c key succeeded"
571*f93bc869SChristian Brauner        deactivate_verity_device
572*f93bc869SChristian Brauner    else
573*f93bc869SChristian Brauner        log_fail "Verification with vendor-c key should succeed"
574*f93bc869SChristian Brauner        return 1
575*f93bc869SChristian Brauner    fi
576*f93bc869SChristian Brauner
577*f93bc869SChristian Brauner    # Test 4: Generate a key NOT in the keyring, should fail
578*f93bc869SChristian Brauner    log_info ""
579*f93bc869SChristian Brauner    log_info "Sub-test: Verify with unknown key (should fail)"
580*f93bc869SChristian Brauner    local unknown_key_dir
581*f93bc869SChristian Brauner    unknown_key_dir=$(generate_named_key "unknown-vendor")
582*f93bc869SChristian Brauner    if ! sign_root_hash_with_key "$unknown_key_dir"; then
583*f93bc869SChristian Brauner        log_fail "Failed to sign with unknown-vendor key"
584*f93bc869SChristian Brauner        return 1
585*f93bc869SChristian Brauner    fi
586*f93bc869SChristian Brauner    if activate_verity_device "yes"; then
587*f93bc869SChristian Brauner        log_fail "Verification with unknown key should fail"
588*f93bc869SChristian Brauner        deactivate_verity_device
589*f93bc869SChristian Brauner        return 1
590*f93bc869SChristian Brauner    else
591*f93bc869SChristian Brauner        log_pass "Verification with unknown key correctly rejected"
592*f93bc869SChristian Brauner    fi
593*f93bc869SChristian Brauner
594*f93bc869SChristian Brauner    log_info ""
595*f93bc869SChristian Brauner    log_pass "Multiple keys test completed successfully"
596*f93bc869SChristian Brauner    return 0
597*f93bc869SChristian Brauner}
598*f93bc869SChristian Brauner
599*f93bc869SChristian Braunersign_root_hash_with_key() {
600*f93bc869SChristian Brauner    local key_dir="$1"
601*f93bc869SChristian Brauner
602*f93bc869SChristian Brauner    local root_hash
603*f93bc869SChristian Brauner    root_hash=$(cat "$WORK_DIR/root_hash")
604*f93bc869SChristian Brauner
605*f93bc869SChristian Brauner    # Create the data to sign (hex string, not binary)
606*f93bc869SChristian Brauner    echo -n "$root_hash" > "$WORK_DIR/root_hash.txt"
607*f93bc869SChristian Brauner
608*f93bc869SChristian Brauner    # Debug: show exactly what we're signing
609*f93bc869SChristian Brauner    log_info "Root hash (hex): $root_hash"
610*f93bc869SChristian Brauner    log_info "Root hash hex string size: $(wc -c < "$WORK_DIR/root_hash.txt") bytes"
611*f93bc869SChristian Brauner
612*f93bc869SChristian Brauner    # Create detached PKCS#7 signature
613*f93bc869SChristian Brauner    if ! create_detached_signature "$WORK_DIR/root_hash.txt" "$WORK_DIR/root_hash.p7s" \
614*f93bc869SChristian Brauner            "$key_dir/cert.pem" "$key_dir/private.pem"; then
615*f93bc869SChristian Brauner        log_error "Failed to sign root hash with key from $key_dir"
616*f93bc869SChristian Brauner        return 1
617*f93bc869SChristian Brauner    fi
618*f93bc869SChristian Brauner
619*f93bc869SChristian Brauner    # Debug: show signing certificate info
620*f93bc869SChristian Brauner    log_info "Signed with certificate:"
621*f93bc869SChristian Brauner    openssl x509 -in "$key_dir/cert.pem" -noout -subject 2>/dev/null | sed 's/^/  /'
622*f93bc869SChristian Brauner
623*f93bc869SChristian Brauner    # Debug: verify signature locally
624*f93bc869SChristian Brauner    # -nointern: cert not in signature, use -certfile
625*f93bc869SChristian Brauner    # -noverify: skip certificate chain validation (self-signed)
626*f93bc869SChristian Brauner    if openssl smime -verify -binary -inform der -nointern -noverify \
627*f93bc869SChristian Brauner        -in "$WORK_DIR/root_hash.p7s" \
628*f93bc869SChristian Brauner        -content "$WORK_DIR/root_hash.txt" \
629*f93bc869SChristian Brauner        -certfile "$key_dir/cert.pem" \
630*f93bc869SChristian Brauner        -out /dev/null 2>/dev/null; then
631*f93bc869SChristian Brauner        log_info "Local signature verification: PASSED"
632*f93bc869SChristian Brauner    else
633*f93bc869SChristian Brauner        log_warn "Local signature verification: FAILED"
634*f93bc869SChristian Brauner    fi
635*f93bc869SChristian Brauner    return 0
636*f93bc869SChristian Brauner}
637*f93bc869SChristian Brauner
638*f93bc869SChristian Brauner#
639*f93bc869SChristian Brauner# Test: Verify corrupted signatures are rejected
640*f93bc869SChristian Brauner#
641*f93bc869SChristian Braunertest_corrupted_signature() {
642*f93bc869SChristian Brauner    log_info "TEST: Verify corrupted signatures are rejected"
643*f93bc869SChristian Brauner
644*f93bc869SChristian Brauner    # This test requires a valid setup from test_multiple_keys or similar
645*f93bc869SChristian Brauner    # It modifies the signature file and verifies rejection
646*f93bc869SChristian Brauner
647*f93bc869SChristian Brauner    if [ ! -f "$WORK_DIR/root_hash.p7s" ]; then
648*f93bc869SChristian Brauner        log_warn "No signature file found, skipping corrupted signature test"
649*f93bc869SChristian Brauner        return 0
650*f93bc869SChristian Brauner    fi
651*f93bc869SChristian Brauner
652*f93bc869SChristian Brauner    # Save original signature
653*f93bc869SChristian Brauner    cp "$WORK_DIR/root_hash.p7s" "$WORK_DIR/root_hash.p7s.orig"
654*f93bc869SChristian Brauner
655*f93bc869SChristian Brauner    # Test 1: Truncated signature
656*f93bc869SChristian Brauner    log_info "Sub-test: Truncated signature (should fail)"
657*f93bc869SChristian Brauner    head -c 100 "$WORK_DIR/root_hash.p7s.orig" > "$WORK_DIR/root_hash.p7s"
658*f93bc869SChristian Brauner    if activate_verity_device "yes"; then
659*f93bc869SChristian Brauner        log_fail "Truncated signature should be rejected"
660*f93bc869SChristian Brauner        deactivate_verity_device
661*f93bc869SChristian Brauner        cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
662*f93bc869SChristian Brauner        return 1
663*f93bc869SChristian Brauner    else
664*f93bc869SChristian Brauner        log_pass "Truncated signature correctly rejected"
665*f93bc869SChristian Brauner    fi
666*f93bc869SChristian Brauner
667*f93bc869SChristian Brauner    # Test 2: Corrupted signature (flip some bytes)
668*f93bc869SChristian Brauner    log_info "Sub-test: Corrupted signature bytes (should fail)"
669*f93bc869SChristian Brauner    cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
670*f93bc869SChristian Brauner    # Corrupt bytes in the middle of the signature
671*f93bc869SChristian Brauner    local sig_size
672*f93bc869SChristian Brauner    sig_size=$(wc -c < "$WORK_DIR/root_hash.p7s")
673*f93bc869SChristian Brauner    local corrupt_offset=$((sig_size / 2))
674*f93bc869SChristian Brauner    printf '\xff\xff\xff\xff' | dd of="$WORK_DIR/root_hash.p7s" bs=1 seek=$corrupt_offset conv=notrunc 2>/dev/null
675*f93bc869SChristian Brauner    if activate_verity_device "yes"; then
676*f93bc869SChristian Brauner        log_fail "Corrupted signature should be rejected"
677*f93bc869SChristian Brauner        deactivate_verity_device
678*f93bc869SChristian Brauner        cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
679*f93bc869SChristian Brauner        return 1
680*f93bc869SChristian Brauner    else
681*f93bc869SChristian Brauner        log_pass "Corrupted signature correctly rejected"
682*f93bc869SChristian Brauner    fi
683*f93bc869SChristian Brauner
684*f93bc869SChristian Brauner    # Test 3: Signature over wrong data (sign different content)
685*f93bc869SChristian Brauner    log_info "Sub-test: Signature over wrong data (should fail)"
686*f93bc869SChristian Brauner    # Create a different root hash (all zeros as hex string)
687*f93bc869SChristian Brauner    printf '%064d' 0 > "$WORK_DIR/wrong_hash.txt"
688*f93bc869SChristian Brauner    # Get the first key directory that was used
689*f93bc869SChristian Brauner    local key_dir="$WORK_DIR/keys/vendor-a"
690*f93bc869SChristian Brauner    if [ -d "$key_dir" ]; then
691*f93bc869SChristian Brauner        create_detached_signature "$WORK_DIR/wrong_hash.txt" "$WORK_DIR/root_hash.p7s" \
692*f93bc869SChristian Brauner            "$key_dir/cert.pem" "$key_dir/private.pem"
693*f93bc869SChristian Brauner        if activate_verity_device "yes"; then
694*f93bc869SChristian Brauner            log_fail "Signature over wrong data should be rejected"
695*f93bc869SChristian Brauner            deactivate_verity_device
696*f93bc869SChristian Brauner            cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
697*f93bc869SChristian Brauner            return 1
698*f93bc869SChristian Brauner        else
699*f93bc869SChristian Brauner            log_pass "Signature over wrong data correctly rejected"
700*f93bc869SChristian Brauner        fi
701*f93bc869SChristian Brauner    else
702*f93bc869SChristian Brauner        log_warn "Key directory not found, skipping wrong data test"
703*f93bc869SChristian Brauner    fi
704*f93bc869SChristian Brauner
705*f93bc869SChristian Brauner    # Restore original signature
706*f93bc869SChristian Brauner    cp "$WORK_DIR/root_hash.p7s.orig" "$WORK_DIR/root_hash.p7s"
707*f93bc869SChristian Brauner
708*f93bc869SChristian Brauner    log_pass "Corrupted signature test completed successfully"
709*f93bc869SChristian Brauner    return 0
710*f93bc869SChristian Brauner}
711*f93bc869SChristian Brauner
712*f93bc869SChristian Brauner#
713*f93bc869SChristian Brauner# Test: Verify keyring is sealed when keyring_unsealed=0
714*f93bc869SChristian Brauner#
715*f93bc869SChristian Braunertest_keyring_sealed_by_default() {
716*f93bc869SChristian Brauner    log_info "TEST: Verify keyring is sealed by default (keyring_unsealed=0)"
717*f93bc869SChristian Brauner
718*f93bc869SChristian Brauner    local keyring_id
719*f93bc869SChristian Brauner    keyring_id=$(cat "$WORK_DIR/keyring_id")
720*f93bc869SChristian Brauner
721*f93bc869SChristian Brauner    log_info "Current keyring state (should be empty and sealed):"
722*f93bc869SChristian Brauner    list_keyring_keys
723*f93bc869SChristian Brauner    show_keyring_status
724*f93bc869SChristian Brauner
725*f93bc869SChristian Brauner    generate_keys
726*f93bc869SChristian Brauner
727*f93bc869SChristian Brauner    # Try to add a key - should fail if keyring is sealed
728*f93bc869SChristian Brauner    log_info "Attempting to add key to sealed keyring..."
729*f93bc869SChristian Brauner    if keyctl padd asymmetric "dm-verity-test" "$keyring_id" \
730*f93bc869SChristian Brauner        < "$WORK_DIR/cert.der" 2>/dev/null; then
731*f93bc869SChristian Brauner        log_fail "Keyring should be sealed when keyring_unsealed=0"
732*f93bc869SChristian Brauner        list_keyring_keys
733*f93bc869SChristian Brauner        return 1
734*f93bc869SChristian Brauner    else
735*f93bc869SChristian Brauner        log_pass "Keyring is correctly sealed when keyring_unsealed=0"
736*f93bc869SChristian Brauner        log_info "Keyring state after failed add attempt:"
737*f93bc869SChristian Brauner        list_keyring_keys
738*f93bc869SChristian Brauner        return 0
739*f93bc869SChristian Brauner    fi
740*f93bc869SChristian Brauner}
741*f93bc869SChristian Brauner
742*f93bc869SChristian Brauner#
743*f93bc869SChristian Brauner# Test: Verify dm-verity keyring is inactive when sealed empty
744*f93bc869SChristian Brauner#
745*f93bc869SChristian Braunertest_keyring_inactive_when_empty() {
746*f93bc869SChristian Brauner    log_info "TEST: Verify dm-verity keyring is inactive when sealed empty"
747*f93bc869SChristian Brauner
748*f93bc869SChristian Brauner    # When keyring_unsealed=0, the keyring is sealed immediately while empty
749*f93bc869SChristian Brauner    # This means it should NOT be used for verification (nr_leaves_on_tree=0)
750*f93bc869SChristian Brauner
751*f93bc869SChristian Brauner    log_info "Keyring state (should be empty and sealed):"
752*f93bc869SChristian Brauner    list_keyring_keys
753*f93bc869SChristian Brauner    show_keyring_status
754*f93bc869SChristian Brauner
755*f93bc869SChristian Brauner    create_test_device
756*f93bc869SChristian Brauner    create_verity_hash
757*f93bc869SChristian Brauner
758*f93bc869SChristian Brauner    # Without any keys in the dm-verity keyring, and with it sealed,
759*f93bc869SChristian Brauner    # verification should fall through to the secondary/platform keyrings
760*f93bc869SChristian Brauner    # and likely succeed (if require_signatures=0) or fail (if =1)
761*f93bc869SChristian Brauner
762*f93bc869SChristian Brauner    log_info "Sub-test: Device activation with sealed empty keyring"
763*f93bc869SChristian Brauner    if [ "$REQUIRE_SIGNATURES" = "Y" ] || [ "$REQUIRE_SIGNATURES" = "1" ]; then
764*f93bc869SChristian Brauner        if activate_verity_device "no"; then
765*f93bc869SChristian Brauner            log_fail "Device should NOT activate without signature when require_signatures=1"
766*f93bc869SChristian Brauner            deactivate_verity_device
767*f93bc869SChristian Brauner            return 1
768*f93bc869SChristian Brauner        else
769*f93bc869SChristian Brauner            log_pass "Device correctly rejected (require_signatures=1, no valid signature)"
770*f93bc869SChristian Brauner        fi
771*f93bc869SChristian Brauner    else
772*f93bc869SChristian Brauner        if activate_verity_device "no"; then
773*f93bc869SChristian Brauner            log_pass "Device activated (require_signatures=0, empty dm-verity keyring is inactive)"
774*f93bc869SChristian Brauner            deactivate_verity_device
775*f93bc869SChristian Brauner        else
776*f93bc869SChristian Brauner            log_fail "Device should activate when require_signatures=0"
777*f93bc869SChristian Brauner            return 1
778*f93bc869SChristian Brauner        fi
779*f93bc869SChristian Brauner    fi
780*f93bc869SChristian Brauner
781*f93bc869SChristian Brauner    return 0
782*f93bc869SChristian Brauner}
783*f93bc869SChristian Brauner
784*f93bc869SChristian Braunermain() {
785*f93bc869SChristian Brauner    local rc=0
786*f93bc869SChristian Brauner
787*f93bc869SChristian Brauner    log_info "=== dm-verity keyring test ==="
788*f93bc869SChristian Brauner    log_info ""
789*f93bc869SChristian Brauner
790*f93bc869SChristian Brauner    # Create work directory
791*f93bc869SChristian Brauner    WORK_DIR=$(mktemp -d -t dm-verity-test.XXXXXX)
792*f93bc869SChristian Brauner    log_info "Work directory: $WORK_DIR"
793*f93bc869SChristian Brauner
794*f93bc869SChristian Brauner    check_requirements
795*f93bc869SChristian Brauner
796*f93bc869SChristian Brauner    #
797*f93bc869SChristian Brauner    # Test 1: UNSEALED keyring mode (keyring_unsealed=1)
798*f93bc869SChristian Brauner    #
799*f93bc869SChristian Brauner    log_info ""
800*f93bc869SChristian Brauner    log_info "========================================"
801*f93bc869SChristian Brauner    log_info "=== TEST MODE: UNSEALED KEYRING ==="
802*f93bc869SChristian Brauner    log_info "========================================"
803*f93bc869SChristian Brauner    log_info ""
804*f93bc869SChristian Brauner
805*f93bc869SChristian Brauner    load_dm_verity_module 1 1  # keyring_unsealed=1, require_signatures=1
806*f93bc869SChristian Brauner    show_keyring_status
807*f93bc869SChristian Brauner
808*f93bc869SChristian Brauner    log_info ""
809*f93bc869SChristian Brauner    if ! test_multiple_keys; then
810*f93bc869SChristian Brauner        rc=1
811*f93bc869SChristian Brauner    fi
812*f93bc869SChristian Brauner
813*f93bc869SChristian Brauner    # After sealing, verify it rejects new keys
814*f93bc869SChristian Brauner    log_info ""
815*f93bc869SChristian Brauner    if ! test_sealed_keyring_rejects_keys; then
816*f93bc869SChristian Brauner        rc=1
817*f93bc869SChristian Brauner    fi
818*f93bc869SChristian Brauner
819*f93bc869SChristian Brauner    # Test corrupted signatures are rejected
820*f93bc869SChristian Brauner    log_info ""
821*f93bc869SChristian Brauner    if ! test_corrupted_signature; then
822*f93bc869SChristian Brauner        rc=1
823*f93bc869SChristian Brauner    fi
824*f93bc869SChristian Brauner
825*f93bc869SChristian Brauner    # Clean up devices before reloading module
826*f93bc869SChristian Brauner    deactivate_verity_device
827*f93bc869SChristian Brauner    if [ -n "$DATA_DEV" ] && [[ "$DATA_DEV" == /dev/loop* ]]; then
828*f93bc869SChristian Brauner        losetup -d "$DATA_DEV" 2>/dev/null || true
829*f93bc869SChristian Brauner        DATA_DEV=""
830*f93bc869SChristian Brauner    fi
831*f93bc869SChristian Brauner    if [ -n "$HASH_DEV" ] && [[ "$HASH_DEV" == /dev/loop* ]]; then
832*f93bc869SChristian Brauner        losetup -d "$HASH_DEV" 2>/dev/null || true
833*f93bc869SChristian Brauner        HASH_DEV=""
834*f93bc869SChristian Brauner    fi
835*f93bc869SChristian Brauner
836*f93bc869SChristian Brauner    #
837*f93bc869SChristian Brauner    # Test 2: SEALED keyring mode (keyring_unsealed=0, default)
838*f93bc869SChristian Brauner    #
839*f93bc869SChristian Brauner    log_info ""
840*f93bc869SChristian Brauner    log_info "========================================"
841*f93bc869SChristian Brauner    log_info "=== TEST MODE: SEALED KEYRING (default) ==="
842*f93bc869SChristian Brauner    log_info "========================================"
843*f93bc869SChristian Brauner    log_info ""
844*f93bc869SChristian Brauner
845*f93bc869SChristian Brauner    load_dm_verity_module 0 0  # keyring_unsealed=0, require_signatures=0
846*f93bc869SChristian Brauner    show_keyring_status
847*f93bc869SChristian Brauner
848*f93bc869SChristian Brauner    log_info ""
849*f93bc869SChristian Brauner    if ! test_keyring_sealed_by_default; then
850*f93bc869SChristian Brauner        rc=1
851*f93bc869SChristian Brauner    fi
852*f93bc869SChristian Brauner
853*f93bc869SChristian Brauner    log_info ""
854*f93bc869SChristian Brauner    if ! test_keyring_inactive_when_empty; then
855*f93bc869SChristian Brauner        rc=1
856*f93bc869SChristian Brauner    fi
857*f93bc869SChristian Brauner
858*f93bc869SChristian Brauner    #
859*f93bc869SChristian Brauner    # Summary
860*f93bc869SChristian Brauner    #
861*f93bc869SChristian Brauner    log_info ""
862*f93bc869SChristian Brauner    log_info "========================================"
863*f93bc869SChristian Brauner    if [ $rc -eq 0 ]; then
864*f93bc869SChristian Brauner        log_info "=== All tests PASSED ==="
865*f93bc869SChristian Brauner    else
866*f93bc869SChristian Brauner        log_error "=== Some tests FAILED ==="
867*f93bc869SChristian Brauner    fi
868*f93bc869SChristian Brauner    log_info "========================================"
869*f93bc869SChristian Brauner
870*f93bc869SChristian Brauner    return $rc
871*f93bc869SChristian Brauner}
872*f93bc869SChristian Brauner
873*f93bc869SChristian Braunermain "$@"
874