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