Skip to content

Commit fb36b49

Browse files
iamjosephmjclaude
andcommitted
attestation: update verdict status to CRITICAL for non-Verified boot + ship proof
Updated the tee_integrity_verdict status: any boot state that is not Google- Verified (SelfSigned/yellow, Unverified/orange, Failed/red) is now CRITICAL (previously MEDIUM for self-signed, HIGH for unverified) and no longer earns MEETS_DEVICE_INTEGRITY. A custom-signed or unverified boot is the unavoidable footprint of a modified OS (custom kernel / ROM / KernelSU) => most probably rooted. The finding now carries hardware-attested proof of why: verified_boot_key_sha256 (the boot-signing key, which is not Google's on a modified device) plus an os_modified_proof message tailored per boot state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f1ac244 commit fb36b49

2 files changed

Lines changed: 53 additions & 6 deletions

File tree

deviceintelligence/src/main/kotlin/io/ssemaj/deviceintelligence/internal/IntegrityVerdict.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,19 @@ internal fun deriveIntegrityVerdict(
120120
firstFailure = "software_attestation"
121121
}
122122

123+
// Only a Google-VERIFIED (green) boot is a genuine OS. SELF_SIGNED (yellow)
124+
// means the boot image is signed with a NON-Google (custom) AVB key — the
125+
// unavoidable footprint of a relocked-with-own-key boot, i.e. a custom
126+
// kernel / KernelSU / custom ROM. It is bootloader-"locked" but not stock,
127+
// so it is most probably rooted and must NOT pass device integrity. Treat
128+
// yellow like unverified (orange): not genuine -> CRITICAL severity below.
123129
val isGenuineOs = isHardware &&
124-
(parsed.verifiedBootState == VerifiedBootState.VERIFIED ||
125-
parsed.verifiedBootState == VerifiedBootState.SELF_SIGNED)
130+
parsed.verifiedBootState == VerifiedBootState.VERIFIED
126131
if (isGenuineOs) {
127132
tiers += DeviceTier.MEETS_DEVICE_INTEGRITY
128133
} else if (isHardware && firstFailure == null) {
129134
firstFailure = when (parsed.verifiedBootState) {
135+
VerifiedBootState.SELF_SIGNED -> "boot_self_signed"
130136
VerifiedBootState.UNVERIFIED, null -> "boot_unverified"
131137
VerifiedBootState.FAILED -> "boot_failed"
132138
else -> "boot_unrecognized"
@@ -161,7 +167,9 @@ internal fun deriveIntegrityVerdict(
161167
// means there is no hardware-backed evidence at all.
162168
!isHardware -> Severity.CRITICAL
163169
app == AppRecognition.UNRECOGNIZED_VERSION -> Severity.CRITICAL
164-
!isGenuineOs -> Severity.HIGH
170+
// Not a Google-VERIFIED boot (yellow/self-signed, orange/unverified, or
171+
// red/failed) => the boot chain was modified => most probably rooted.
172+
!isGenuineOs -> Severity.CRITICAL
165173
!isStrong -> Severity.MEDIUM
166174
else -> Severity.LOW
167175
}

deviceintelligence/src/main/kotlin/io/ssemaj/deviceintelligence/internal/KeyAttestationDetector.kt

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -475,24 +475,63 @@ internal object KeyAttestationDetector : Detector {
475475
verdict: IntegrityVerdict,
476476
pkg: String,
477477
): Finding {
478-
val details = LinkedHashMap<String, String>(8)
478+
val details = LinkedHashMap<String, String>(10)
479479
details["device_recognition"] = verdict.deviceRecognition
480480
.joinToString(",") { it.wire }
481481
details["app_recognition"] = verdict.appRecognition.wire
482-
details["bootloader_locked"] = (c.parsed?.deviceLocked?.toString() ?: "unknown")
482+
val locked = c.parsed?.deviceLocked
483+
details["bootloader_locked"] = locked?.toString() ?: "unknown"
483484
details["verified_boot_state"] = c.parsed?.verifiedBootState?.wire ?: "Unknown"
485+
// The hardware-attested hash of the key that signed the running boot
486+
// image. On stock it chains to Google's AVB key; any other value is
487+
// proof the boot partition was re-signed with a custom key.
488+
val bootKeyHash = c.parsed?.verifiedBootKey?.let { sha256Hex(it) }
489+
bootKeyHash?.let { details["verified_boot_key_sha256"] = it }
484490
details["verdict_authoritative"] = "false"
485491
verdict.reason?.let { details["reason"] = it }
486492

493+
// Attach the concrete, hardware-attested proof of *why* the OS is
494+
// considered modified (when the boot state says so).
495+
val proof = osModifiedProof(c.parsed?.verifiedBootState, locked, bootKeyHash)
496+
proof?.let { details["os_modified_proof"] = it }
497+
487498
return Finding(
488499
kind = "tee_integrity_verdict",
489500
severity = verdict.severity,
490501
subject = pkg,
491-
message = "TEE evidence indicates degraded device or app integrity (advisory; verify chain server-side)",
502+
message = proof
503+
?: "TEE evidence indicates degraded device or app integrity (advisory; verify chain server-side)",
492504
details = details,
493505
)
494506
}
495507

508+
/**
509+
* Hardware-attested proof of *why* the OS is considered modified, derived
510+
* from the Verified Boot state in the attestation cert. Returns null for a
511+
* Google-VERIFIED (green) boot — there is nothing to prove. These fields
512+
* come from the secure element's RootOfTrust and cannot be forged on-device.
513+
*/
514+
private fun osModifiedProof(
515+
state: VerifiedBootState?,
516+
locked: Boolean?,
517+
bootKeyHash: String?,
518+
): String? = when (state) {
519+
VerifiedBootState.SELF_SIGNED ->
520+
"OS MODIFIED (hardware-attested): the running boot image is signed by a NON-Google " +
521+
"(custom) AVB key [verifiedBootKey=${bootKeyHash ?: "?"}], verifiedBootState=SelfSigned. " +
522+
"A stock device chains to Google's key; a custom key is the unavoidable footprint of a " +
523+
"flashed + relocked boot (custom kernel / ROM / KernelSU). Bootloader-locked but not stock " +
524+
"=> most probably rooted."
525+
VerifiedBootState.UNVERIFIED ->
526+
"OS MODIFIED (hardware-attested): bootloader is UNLOCKED (verifiedBootState=Unverified, " +
527+
"deviceLocked=${locked ?: "?"}) — boot is not verified and can be arbitrarily replaced " +
528+
"=> most probably rooted."
529+
VerifiedBootState.FAILED ->
530+
"OS MODIFIED (hardware-attested): boot verification FAILED (verifiedBootState=Failed) — the " +
531+
"boot image does not match its expected signature. OS modified or corrupted."
532+
else -> null
533+
}
534+
496535
// ---- AttestationReport construction -----------------------------------
497536

498537
/**

0 commit comments

Comments
 (0)