Skip to content

Commit e6ac1d9

Browse files
SecAI-Hubclaude
andcommitted
Add Secure Boot chain with MOK signing, TPM2 vault sealing, and measured boot (M17)
UEFI Secure Boot with custom Machine Owner Key (RSA 4096), TPM2 vault key sealing to PCR 0/2/4/7 (firmware, option ROM, bootloader, SB state), and boot chain integrity verification on every boot. Passphrase fallback on PCR mismatch allows recovery after legitimate updates. vTPM detection for VM environments. 35 validation tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 63bcdaf commit e6ac1d9

10 files changed

Lines changed: 1104 additions & 0 deletions

File tree

files/scripts/generate-mok.sh

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Secure AI Appliance — Machine Owner Key (MOK) Generation
4+
#
5+
# Generates a MOK key pair for UEFI Secure Boot signing during image build.
6+
# The private key signs the bootloader and kernel; the public key (DER cert)
7+
# is enrolled in the MOK database on first boot via mokutil.
8+
#
9+
# Usage: generate-mok.sh [output-dir]
10+
#
11+
# Output:
12+
# <output-dir>/secureai-mok.key — private key (PEM, keep secret)
13+
# <output-dir>/secureai-mok.pem — public certificate (PEM)
14+
# <output-dir>/secureai-mok.der — public certificate (DER, for MOK enrollment)
15+
#
16+
set -euo pipefail
17+
18+
OUTPUT_DIR="${1:-/etc/secure-ai/keys}"
19+
MOK_KEY="${OUTPUT_DIR}/secureai-mok.key"
20+
MOK_PEM="${OUTPUT_DIR}/secureai-mok.pem"
21+
MOK_DER="${OUTPUT_DIR}/secureai-mok.der"
22+
23+
CERT_SUBJECT="/CN=SecAI OS Secure Boot Signing Key/O=SecAI"
24+
CERT_DAYS=3650 # 10 years
25+
26+
echo "=== Generating Machine Owner Key (MOK) ==="
27+
28+
mkdir -p "$OUTPUT_DIR"
29+
30+
if [ -f "$MOK_KEY" ] && [ -f "$MOK_DER" ]; then
31+
echo "MOK already exists at ${OUTPUT_DIR}, skipping generation."
32+
echo "Delete existing keys to regenerate."
33+
exit 0
34+
fi
35+
36+
# Generate RSA 4096 key + self-signed certificate
37+
openssl req -new -x509 \
38+
-newkey rsa:4096 \
39+
-keyout "$MOK_KEY" \
40+
-out "$MOK_PEM" \
41+
-nodes \
42+
-days "$CERT_DAYS" \
43+
-subj "$CERT_SUBJECT" \
44+
-addext "extendedKeyUsage=codeSigning" \
45+
-sha256
46+
47+
# Convert PEM to DER (required by mokutil)
48+
openssl x509 -in "$MOK_PEM" -outform DER -out "$MOK_DER"
49+
50+
# Restrictive permissions
51+
chmod 600 "$MOK_KEY"
52+
chmod 644 "$MOK_PEM" "$MOK_DER"
53+
54+
echo "MOK generated:"
55+
echo " Private key: ${MOK_KEY}"
56+
echo " Certificate: ${MOK_PEM}"
57+
echo " DER cert: ${MOK_DER}"
58+
echo ""
59+
echo "To sign a kernel/bootloader:"
60+
echo " sbsign --key ${MOK_KEY} --cert ${MOK_PEM} --output signed.efi unsigned.efi"
61+
echo ""
62+
echo "To enroll on first boot:"
63+
echo " mokutil --import ${MOK_DER}"
64+
echo "=== MOK Generation Complete ==="

files/system/etc/secure-ai/config/appliance.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ auth:
9090
# Escalated lockout after 15 failed attempts (seconds).
9191
escalated_lockout: 900
9292

93+
secure_boot:
94+
# UEFI Secure Boot + TPM2 measured boot (M17).
95+
# MOK (Machine Owner Key) signs bootloader and kernel.
96+
# TPM2 seals the LUKS vault key to PCR values (firmware, kernel, bootloader,
97+
# secure boot state). If the boot chain is tampered, TPM refuses to unseal
98+
# and the user must enter the passphrase manually.
99+
# PCR binding: PCR 0 (firmware), 2 (option ROM), 4 (bootloader), 7 (SB state).
100+
tpm2_pcr_binding: "sha256:0,2,4,7"
101+
# Allow passphrase fallback when PCR mismatch (e.g., after legitimate update).
102+
passphrase_fallback: true
103+
# Auto-reseal after rpm-ostree upgrade (requires user passphrase confirmation).
104+
auto_reseal_on_update: false
105+
93106
sandbox:
94107
# Per-service process isolation (M16).
95108
# seccomp: JSON profiles in /etc/secure-ai/seccomp/ define allowed syscalls.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[Unit]
2+
Description=Secure AI Boot Chain Integrity Verification
3+
After=local-fs.target
4+
Before=secure-ai-registry.service secure-ai-ui.service
5+
DefaultDependencies=no
6+
7+
[Service]
8+
Type=oneshot
9+
ExecStart=/usr/libexec/secure-ai/verify-boot-chain.sh
10+
RemainAfterExit=yes
11+
12+
# Needs access to /sys/firmware/efi, /dev/tpmrm0, /boot
13+
ProtectSystem=full
14+
ProtectHome=yes
15+
PrivateTmp=yes
16+
PrivateNetwork=yes
17+
NoNewPrivileges=yes
18+
19+
# Write results to logs
20+
ReadWritePaths=/var/lib/secure-ai/logs /run/secure-ai
21+
22+
[Install]
23+
WantedBy=multi-user.target
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Secure AI Appliance — Secure Boot MOK Enrollment
4+
#
5+
# Enrolls the SecAI Machine Owner Key (MOK) into the system's UEFI MOK
6+
# database. After enrollment, only kernels/bootloaders signed with this
7+
# key will be trusted.
8+
#
9+
# This script is called during firstboot. The user must reboot and
10+
# confirm enrollment in the MOK Manager (MokManager.efi) screen.
11+
#
12+
# Usage: enroll-secureboot.sh [--check-only]
13+
#
14+
set -euo pipefail
15+
16+
MOK_DER="/etc/secure-ai/keys/secureai-mok.der"
17+
MOK_PEM="/etc/secure-ai/keys/secureai-mok.pem"
18+
STATE_FILE="/run/secure-ai/secureboot-state"
19+
20+
log() {
21+
echo "[enroll-secureboot] $*"
22+
logger -t enroll-secureboot "$*"
23+
}
24+
25+
write_state() {
26+
mkdir -p "$(dirname "$STATE_FILE")" 2>/dev/null || true
27+
echo "{\"secure_boot\":\"$1\",\"mok_enrolled\":\"$2\",\"detail\":\"$3\"}" > "$STATE_FILE"
28+
}
29+
30+
check_secure_boot() {
31+
# Check if Secure Boot is enabled
32+
if [ -d /sys/firmware/efi ]; then
33+
local sb_state
34+
sb_state=$(mokutil --sb-state 2>/dev/null || echo "unknown")
35+
case "$sb_state" in
36+
*enabled*|*Enabled*)
37+
echo "enabled"
38+
return 0
39+
;;
40+
*disabled*|*Disabled*)
41+
echo "disabled"
42+
return 1
43+
;;
44+
esac
45+
fi
46+
echo "unavailable"
47+
return 1
48+
}
49+
50+
check_mok_enrolled() {
51+
# Check if our MOK is already enrolled
52+
if [ ! -f "$MOK_DER" ]; then
53+
return 1
54+
fi
55+
56+
if command -v mokutil &>/dev/null; then
57+
# Get the fingerprint of our cert
58+
local our_fp
59+
our_fp=$(openssl x509 -in "$MOK_PEM" -noout -fingerprint -sha256 2>/dev/null | \
60+
sed 's/.*=//;s/://g' | tr '[:upper:]' '[:lower:]')
61+
62+
# Check enrolled keys
63+
if mokutil --list-enrolled 2>/dev/null | grep -qi "$our_fp"; then
64+
return 0
65+
fi
66+
fi
67+
return 1
68+
}
69+
70+
cmd_check() {
71+
echo "=== Secure Boot Status ==="
72+
73+
local sb_state
74+
sb_state=$(check_secure_boot)
75+
echo "UEFI Secure Boot: ${sb_state}"
76+
77+
if [ "$sb_state" = "unavailable" ]; then
78+
echo "System does not support UEFI Secure Boot."
79+
echo "This may be a legacy BIOS system or a VM without UEFI."
80+
write_state "unavailable" "false" "no_uefi"
81+
return
82+
fi
83+
84+
if [ "$sb_state" = "disabled" ]; then
85+
echo "Secure Boot is disabled in firmware."
86+
echo "Enable it in BIOS/UEFI settings for full boot chain verification."
87+
write_state "disabled" "false" "sb_disabled"
88+
return
89+
fi
90+
91+
# Check MOK enrollment
92+
if [ ! -f "$MOK_DER" ]; then
93+
echo "SecAI MOK certificate not found at ${MOK_DER}"
94+
echo "Run generate-mok.sh during image build to create it."
95+
write_state "enabled" "false" "no_mok_cert"
96+
return
97+
fi
98+
99+
if check_mok_enrolled; then
100+
echo "SecAI MOK: ENROLLED"
101+
write_state "enabled" "true" "fully_configured"
102+
else
103+
echo "SecAI MOK: NOT ENROLLED"
104+
echo "Run: enroll-secureboot.sh (without --check-only) to enroll."
105+
write_state "enabled" "false" "mok_not_enrolled"
106+
fi
107+
108+
# Check if kernel is signed
109+
if command -v sbverify &>/dev/null; then
110+
local kernel
111+
kernel=$(ls /boot/vmlinuz-* 2>/dev/null | sort -V | tail -1 || echo "")
112+
if [ -n "$kernel" ]; then
113+
if sbverify --cert "$MOK_PEM" "$kernel" 2>/dev/null; then
114+
echo "Kernel signature: VALID"
115+
else
116+
echo "Kernel signature: NOT SIGNED or INVALID"
117+
echo " The kernel may not be signed with the SecAI MOK."
118+
fi
119+
fi
120+
fi
121+
122+
echo "=== End Secure Boot Status ==="
123+
}
124+
125+
cmd_enroll() {
126+
log "Starting MOK enrollment..."
127+
128+
if [ ! -f "$MOK_DER" ]; then
129+
log "ERROR: MOK certificate not found at ${MOK_DER}"
130+
log "Generate it first: /usr/libexec/secure-ai/generate-mok.sh"
131+
exit 1
132+
fi
133+
134+
local sb_state
135+
sb_state=$(check_secure_boot)
136+
137+
if [ "$sb_state" = "unavailable" ]; then
138+
log "WARNING: UEFI not available. MOK enrollment skipped."
139+
write_state "unavailable" "false" "no_uefi"
140+
exit 0
141+
fi
142+
143+
if check_mok_enrolled; then
144+
log "SecAI MOK is already enrolled."
145+
write_state "$sb_state" "true" "already_enrolled"
146+
exit 0
147+
fi
148+
149+
if ! command -v mokutil &>/dev/null; then
150+
log "ERROR: mokutil not found. Install it: dnf install mokutil"
151+
exit 1
152+
fi
153+
154+
log "Importing MOK certificate..."
155+
log "You will be prompted for a one-time password."
156+
log "Remember this password — you'll need it on the next reboot"
157+
log "when the MOK Manager asks you to confirm enrollment."
158+
159+
mokutil --import "$MOK_DER"
160+
161+
log ""
162+
log "MOK import request registered."
163+
log "IMPORTANT: Reboot the system. At the MOK Manager screen:"
164+
log " 1. Select 'Enroll MOK'"
165+
log " 2. Select 'Continue'"
166+
log " 3. Enter the one-time password you just set"
167+
log " 4. Select 'Reboot'"
168+
log ""
169+
log "After reboot, the SecAI signing key will be trusted."
170+
171+
write_state "$sb_state" "pending" "enrollment_pending_reboot"
172+
}
173+
174+
# --- Main ---
175+
case "${1:---check-only}" in
176+
--check-only|check|status) cmd_check ;;
177+
enroll|--enroll) cmd_enroll ;;
178+
*)
179+
echo "Usage: $0 [--check-only | enroll]"
180+
exit 1
181+
;;
182+
esac

files/system/usr/libexec/secure-ai/firstboot.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,34 @@ if [ -w /proc/sys/kernel/yama/ptrace_scope ]; then
153153
echo 1 > /proc/sys/kernel/yama/ptrace_scope 2>/dev/null || true
154154
fi
155155

156+
# --- Secure Boot + TPM2 checks (M17) ---
157+
log "Checking Secure Boot status..."
158+
/usr/libexec/secure-ai/enroll-secureboot.sh --check-only 2>&1 | while IFS= read -r line; do log "$line"; done || true
159+
160+
log "Checking TPM2 status..."
161+
/usr/libexec/secure-ai/tpm2-seal-vault.sh status 2>&1 | while IFS= read -r line; do log "$line"; done || true
162+
163+
# Create TPM2 key directory
164+
mkdir -p "${SECURE_AI_ROOT}/keys/tpm2"
165+
chmod 700 "${SECURE_AI_ROOT}/keys/tpm2"
166+
167+
# If TPM2 is available and vault key is not yet sealed, log instructions
168+
if [ -e /dev/tpmrm0 ] || [ -e /dev/tpm0 ]; then
169+
if [ ! -f "${SECURE_AI_ROOT}/keys/tpm2/vault-key.sealed.pub" ]; then
170+
log ""
171+
log "TPM2 detected. To seal the vault key to the boot chain:"
172+
log " sudo /usr/libexec/secure-ai/tpm2-seal-vault.sh seal"
173+
log "This binds vault auto-unlock to the current firmware + kernel state."
174+
log ""
175+
fi
176+
fi
177+
178+
# Run boot chain verification
179+
log "Running boot chain integrity verification..."
180+
/usr/libexec/secure-ai/verify-boot-chain.sh 2>&1 | while IFS= read -r line; do log "$line"; done || {
181+
log "WARNING: boot chain verification failed"
182+
}
183+
156184
# Write marker (read-only to prevent tampering)
157185
date -Iseconds > "$MARKER"
158186
chmod 444 "$MARKER"

0 commit comments

Comments
 (0)