diff --git a/src/main/java/app/attestation/server/AttestationProtocol.java b/src/main/java/app/attestation/server/AttestationProtocol.java index ac644e58..6797b083 100644 --- a/src/main/java/app/attestation/server/AttestationProtocol.java +++ b/src/main/java/app/attestation/server/AttestationProtocol.java @@ -807,6 +807,13 @@ private static String toYesNoString(final boolean value) { return value ? "yes" : "no"; } + record SecurityStateExt(short autoRebootMinutes, byte portSecurityMode, byte userCount) { + static int UNKNOWN_VALUE = -1; + static int INVALID_VALUE = -2; + static SecurityStateExt UNKNOWN = new SecurityStateExt( + (short) UNKNOWN_VALUE, (byte) UNKNOWN_VALUE, (byte) UNKNOWN_VALUE); + } + private static void verify(final byte[] fingerprint, final Cache pendingChallenges, final long userId, final boolean paired, final ByteBuffer signedMessage, final byte[] signature, @@ -814,7 +821,8 @@ private static void verify(final byte[] fingerprint, final boolean accessibility, final boolean deviceAdmin, final boolean deviceAdminNonSystem, final boolean adbEnabled, final boolean addUsersWhenLocked, final boolean enrolledBiometrics, - final boolean oemUnlockAllowed, final boolean systemUser) + final boolean oemUnlockAllowed, final boolean systemUser, + final SecurityStateExt securityStateExt) throws GeneralSecurityException, IOException, SQLiteException { final String fingerprintHex = BaseEncoding.base16().encode(fingerprint); final byte[] currentFingerprint = getFingerprint(attestationCertificates[0]); @@ -952,6 +960,9 @@ private static void verify(final byte[] fingerprint, addUsersWhenLocked = ?, oemUnlockAllowed = ?, systemUser = ?, + autoRebootMinutes = ?, + portSecurityMode = ?, + userCount = ?, verifiedTimeLast = ? WHERE fingerprint = ?"""); try { @@ -974,8 +985,11 @@ private static void verify(final byte[] fingerprint, update.bind(13, addUsersWhenLocked ? 1 : 0); update.bind(14, oemUnlockAllowed ? 1 : 0); update.bind(15, systemUser ? 1 : 0); - update.bind(16, now); - update.bind(17, fingerprint); + update.bind(16, securityStateExt.autoRebootMinutes); + update.bind(17, securityStateExt.portSecurityMode); + update.bind(18, securityStateExt.userCount); + update.bind(19, now); + update.bind(20, fingerprint); update.step(); } finally { update.dispose(); @@ -1005,10 +1019,13 @@ INSERT INTO Devices ( addUsersWhenLocked, oemUnlockAllowed, systemUser, + autoRebootMinutes, + portSecurityMode, + userCount, verifiedTimeFirst, verifiedTimeLast, userId - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""); + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""); try { insert.bind(1, fingerprint); insert.bind(2, encodeChain(DEFLATE_DICTIONARY_4, attestationCertificates)); @@ -1034,9 +1051,12 @@ INSERT INTO Devices ( insert.bind(18, addUsersWhenLocked ? 1 : 0); insert.bind(19, oemUnlockAllowed ? 1 : 0); insert.bind(20, systemUser ? 1 : 0); - insert.bind(21, now); - insert.bind(22, now); - insert.bind(23, userId); + insert.bind(21, securityStateExt.autoRebootMinutes); + insert.bind(22, securityStateExt.portSecurityMode); + insert.bind(23, securityStateExt.userCount); + insert.bind(24, now); + insert.bind(25, now); + insert.bind(26, userId); insert.step(); } finally { insert.dispose(); @@ -1061,8 +1081,11 @@ INSERT INTO Attestations ( adbEnabled, addUsersWhenLocked, oemUnlockAllowed, - systemUser - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""); + systemUser, + autoRebootMinutes, + portSecurityMode, + userCount + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""); try { insert.bind(1, fingerprint); insert.bind(2, now); @@ -1085,6 +1108,9 @@ INSERT INTO Attestations ( insert.bind(15, addUsersWhenLocked ? 1 : 0); insert.bind(16, oemUnlockAllowed ? 1 : 0); insert.bind(17, systemUser ? 1 : 0); + insert.bind(18, securityStateExt.autoRebootMinutes); + insert.bind(19, securityStateExt.portSecurityMode); + insert.bind(20, securityStateExt.userCount); insert.step(); } finally { @@ -1189,10 +1215,14 @@ static void verifySerialized(final byte[] attestationResult, throw new GeneralSecurityException("invalid device administrator state"); } + SecurityStateExt securityStateExt; if (version >= 6) { final short autoRebootMinutes = deserializer.getShort(); final byte portSecurityMode = deserializer.get(); final byte userCount = deserializer.get(); + securityStateExt = new SecurityStateExt(autoRebootMinutes, portSecurityMode, userCount); + } else { + securityStateExt = SecurityStateExt.UNKNOWN; } final int signatureLength = deserializer.remaining(); @@ -1204,6 +1234,7 @@ static void verifySerialized(final byte[] attestationResult, verify(fingerprint, pendingChallenges, userId, paired, deserializer.asReadOnlyBuffer(), signature, certificates, userProfileSecure, accessibility, deviceAdmin, deviceAdminNonSystem, - adbEnabled, addUsersWhenLocked, enrolledBiometrics, oemUnlockAllowed, systemUser); + adbEnabled, addUsersWhenLocked, enrolledBiometrics, oemUnlockAllowed, systemUser, + securityStateExt); } } diff --git a/src/main/java/app/attestation/server/AttestationServer.java b/src/main/java/app/attestation/server/AttestationServer.java index cf5a9902..7a5c843a 100644 --- a/src/main/java/app/attestation/server/AttestationServer.java +++ b/src/main/java/app/attestation/server/AttestationServer.java @@ -234,6 +234,9 @@ adbEnabled INTEGER NOT NULL CHECK (adbEnabled in (0, 1)), addUsersWhenLocked INTEGER NOT NULL CHECK (addUsersWhenLocked in (0, 1)), oemUnlockAllowed INTEGER NOT NULL CHECK (oemUnlockAllowed in (0, 1)), systemUser INTEGER NOT NULL CHECK (systemUser in (0, 1)), + autoRebootMinutes INTEGER NOT NULL CHECK (autoRebootMinutes in (-2, -1) OR autoRebootMinutes >= 0), + portSecurityMode INTEGER NOT NULL CHECK (portSecurityMode in (-2, -1) OR portSecurityMode >= 0), + userCount INTEGER NOT NULL CHECK (userCount in (-2, -1) OR userCount >= 1), verifiedTimeFirst INTEGER NOT NULL, verifiedTimeLast INTEGER NOT NULL, expiredTimeLast INTEGER, @@ -261,7 +264,10 @@ deviceAdmin INTEGER NOT NULL CHECK (deviceAdmin in (0, 1, 2)), adbEnabled INTEGER NOT NULL CHECK (adbEnabled in (0, 1)), addUsersWhenLocked INTEGER NOT NULL CHECK (addUsersWhenLocked in (0, 1)), oemUnlockAllowed INTEGER NOT NULL CHECK (oemUnlockAllowed in (0, 1)), - systemUser INTEGER NOT NULL CHECK (systemUser in (0, 1)) + systemUser INTEGER NOT NULL CHECK (systemUser in (0, 1)), + autoRebootMinutes INTEGER NOT NULL CHECK (autoRebootMinutes in (-2, -1) OR autoRebootMinutes >= 0), + portSecurityMode INTEGER NOT NULL CHECK (portSecurityMode in (-2, -1) OR portSecurityMode >= 0), + userCount INTEGER NOT NULL CHECK (userCount in (-2, -1) OR userCount >= 1) ) STRICT"""; private static final String CREATE_ATTESTATION_INDICES = """ @@ -575,6 +581,140 @@ INSERT INTO Attestations ( logger.info("Migrated to schema version: " + userVersion); } + // add failureAlertTime column to Devices + targetUserVersion = 15; + if (userVersion < targetUserVersion) { + conn.exec("PRAGMA foreign_keys = OFF"); + conn.exec("BEGIN IMMEDIATE TRANSACTION"); + + conn.exec("ALTER TABLE Devices RENAME TO OldDevices"); + conn.exec("ALTER TABLE Attestations RENAME TO OldAttestations"); + + conn.exec(CREATE_ATTESTATION_TABLES); + + conn.exec(""" + INSERT INTO Devices ( + fingerprint, + pinnedCertificates, + attestKey, + pinnedVerifiedBootKey, + verifiedBootHash, + pinnedOsVersion, + pinnedOsPatchLevel, + pinnedVendorPatchLevel, + pinnedBootPatchLevel, + pinnedAppVersion, + pinnedAppVariant, + pinnedSecurityLevel, + userProfileSecure, + enrolledBiometrics, + accessibility, + deviceAdmin, + adbEnabled, + addUsersWhenLocked, + oemUnlockAllowed, + systemUser, + autoRebootMinutes, + portSecurityMode, + userCount, + verifiedTimeFirst, + verifiedTimeLast, + expiredTimeLast, + failureTimeLast, + failureAlertTime, + userId, + deletionTime) + SELECT + fingerprint, + pinnedCertificates, + attestKey, + pinnedVerifiedBootKey, + verifiedBootHash, + pinnedOsVersion, + pinnedOsPatchLevel, + pinnedVendorPatchLevel, + pinnedBootPatchLevel, + pinnedAppVersion, + pinnedAppVariant, + pinnedSecurityLevel, + userProfileSecure, + enrolledBiometrics, + accessibility, + deviceAdmin, + adbEnabled, + addUsersWhenLocked, + oemUnlockAllowed, + systemUser, + -1, + -1, + -1, + verifiedTimeFirst, + verifiedTimeLast, + expiredTimeLast, + failureTimeLast, + failureAlertTime, + userId, + deletionTime + FROM OldDevices"""); + + conn.exec(""" + INSERT INTO Attestations ( + id, + fingerprint, + time, + strong, + osVersion, + osPatchLevel, + vendorPatchLevel, + bootPatchLevel, + verifiedBootHash, + appVersion, + userProfileSecure, + enrolledBiometrics, + accessibility, + deviceAdmin, + adbEnabled, + addUsersWhenLocked, + oemUnlockAllowed, + systemUser, + autoRebootMinutes, + portSecurityMode, + userCount + ) SELECT + id, + fingerprint, + time, + strong, + osVersion, + osPatchLevel, + vendorPatchLevel, + bootPatchLevel, + verifiedBootHash, + appVersion, + userProfileSecure, + enrolledBiometrics, + accessibility, + deviceAdmin, + adbEnabled, + addUsersWhenLocked, + oemUnlockAllowed, + systemUser, + -1, + -1, + -1 + FROM OldAttestations"""); + + conn.exec("DROP TABLE OldDevices"); + conn.exec("DROP TABLE OldAttestations"); + + conn.exec(CREATE_ATTESTATION_INDICES); + conn.exec("PRAGMA user_version = " + targetUserVersion); + conn.exec("COMMIT TRANSACTION"); + userVersion = targetUserVersion; + conn.exec("PRAGMA foreign_keys = ON"); + logger.info("Migrated to schema version: " + userVersion); + } + logger.info("Finished database setup for " + ATTESTATION_DATABASE); } finally { conn.dispose(); @@ -1392,6 +1532,9 @@ private static void writeDevicesJson(final HttpExchange exchange, final long use addUsersWhenLocked, oemUnlockAllowed, systemUser, + autoRebootMinutes, + portSecurityMode, + userCount, verifiedTimeFirst, verifiedTimeLast, (SELECT min(id) FROM Attestations WHERE Attestations.fingerprint = Devices.fingerprint), @@ -1460,10 +1603,13 @@ private static void writeDevicesJson(final HttpExchange exchange, final long use device.add("addUsersWhenLocked", select.columnInt(17)); device.add("oemUnlockAllowed", select.columnInt(18)); device.add("systemUser", select.columnInt(19)); - device.add("verifiedTimeFirst", select.columnLong(20)); - device.add("verifiedTimeLast", select.columnLong(21)); - device.add("minId", select.columnLong(22)); - device.add("maxId", select.columnLong(23)); + device.add("autoRebootMinutes", select.columnInt(20)); + device.add("portSecurityMode", select.columnInt(21)); + device.add("userCount", select.columnInt(22)); + device.add("verifiedTimeFirst", select.columnLong(23)); + device.add("verifiedTimeLast", select.columnLong(24)); + device.add("minId", select.columnLong(25)); + device.add("maxId", select.columnLong(26)); devices.add(device); } } finally { @@ -1533,7 +1679,10 @@ private static void writeAttestationHistoryJson(final HttpExchange exchange, fin Attestations.adbEnabled, Attestations.addUsersWhenLocked, Attestations.oemUnlockAllowed, - Attestations.systemUser + Attestations.systemUser, + Attestations.autoRebootMinutes, + Attestations.portSecurityMode, + Attestations.userCount FROM Attestations INNER JOIN Devices ON Attestations.fingerprint = Devices.fingerprint WHERE Devices.fingerprint = ? AND userid = ? @@ -1568,6 +1717,9 @@ private static void writeAttestationHistoryJson(final HttpExchange exchange, fin attestation.add("addUsersWhenLocked", history.columnInt(14)); attestation.add("oemUnlockAllowed", history.columnInt(15)); attestation.add("systemUser", history.columnInt(16)); + attestation.add("autoRebootMinutes", history.columnInt(17)); + attestation.add("portSecurityMode", history.columnInt(18)); + attestation.add("userCount", history.columnInt(19)); attestations.add(attestation); rowCount += 1; } diff --git a/static/monitoring.js b/static/monitoring.js index 2fc661fc..a52871f2 100644 --- a/static/monitoring.js +++ b/static/monitoring.js @@ -85,6 +85,60 @@ function toSecurityLevelString(securityLevel, attestKey) { throw new Error("Invalid security level"); } +function autoRebootTimeoutString(autoRebootMinutes) { + if (autoRebootMinutes >= 0) { + if (autoRebootMinutes < 60) { + return autoRebootMinutes + " minutes"; + } + const autoRebootHours = autoRebootMinutes / 60; + const autoRebootMinutesRemainder = autoRebootMinutes % 60; + if (autoRebootHours < 24) { + return autoRebootHours + " hours" + + autoRebootMinutesRemainder > 0 ? (" " + autoRebootMinutesRemainder + " minutes") : ""; + } + + const autoRebootDays = autoRebootHours / 24; + const autoRebootHoursRemainder = autoRebootHours % 24; + return autoRebootDays + " days" + + autoRebootHoursRemainder > 0 ? (" " + autoRebootHoursRemainder + " hours") : "" + + autoRebootMinutesRemainder > 0 ? (" " + autoRebootMinutesRemainder + " minutes") : ""; + } else if (autoRebootMinutes == -1) { + return "Unknown"; + } else if (autoRebootMinutes == -2) { + return "Invalid"; + } + throw new Error("Invalid auto reboot minutes value"); +} + +function usbPortSecurityModeString(portSecurityMode) { + if (portSecurityMode >= 0) { + switch (portSecurityMode) { + case 0: return "Off"; + case 1: return "Charging-only"; + case 2: return "Charging-only when locked"; + case 3: return "Charging-only when locked, except before first unlock"; + case 4: return "On"; + default: break; + } + } else if (portSecurityMode == -1) { + return "Unknown"; + } else if (portSecurityMode == -2) { + return "Invalid"; + } + throw new Error("Invalid port security mode value"); +} + +function userCountString(userCount) { + if (userCount > 0) { + return userCount + " users"; + } else if (userCount == -1) { + return "Unknown"; + } else if (userCount == -2) { + return "Invalid"; + } + throw new Error("Invalid port security mode value"); +} + function reloadQrCode() { qr.src = "/placeholder.gif"; qr.alt = ""; @@ -177,6 +231,9 @@ function fetchHistory(parent, nextOffset) { appendLine(parent, "Add users from lock screen: " + toYesNoString(attestation.addUsersWhenLocked)); appendLine(parent, "OEM unlocking allowed: " + toYesNoString(attestation.oemUnlockAllowed)); appendLine(parent, "Main user account: " + toYesNoString(attestation.systemUser)); + appendLine(parent, "Auto reboot timeout: " + autoRebootTimeoutString(attestation.autoRebootMinutes)); + appendLine(parent, "USB-C port security mode: " + usbPortSecurityModeString(attestation.portSecurityMode)); + appendLine(parent, "User count: " + userCountString(attestation.userCount)); } const earliestCurrentId = attestations.slice(-1)[0].id; function fetchHistoryNextPage() { @@ -301,6 +358,9 @@ function fetchDevices() { appendLine(info, "Add users from lock screen: " + toYesNoString(device.addUsersWhenLocked)); appendLine(info, "OEM unlocking allowed: " + toYesNoString(device.oemUnlockAllowed)); appendLine(info, "Main user account: " + toYesNoString(device.systemUser)); + appendLine(info, "Auto reboot timeout: " + autoRebootTimeoutString(device.autoRebootMinutes)); + appendLine(info, "USB-C port security mode: " + usbPortSecurityModeString(device.portSecurityMode)); + appendLine(info, "User count: " + userCountString(device.userCount)); info.appendChild(create("h3", "Attestation history")); appendLine(info, "First verified time: " + new Date(device.verifiedTimeFirst));