package app.attestation.server;

import com.almworks.sqlite4java.SQLiteConnection;
import com.almworks.sqlite4java.SQLiteException;
import com.almworks.sqlite4java.SQLiteStatement;

import com.github.benmanes.caffeine.cache.Cache;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import app.attestation.server.attestation.Attestation;
import app.attestation.server.attestation.AttestationApplicationId;
import app.attestation.server.attestation.AttestationPackageInfo;
import app.attestation.server.attestation.AuthorizationList;
import app.attestation.server.attestation.RootOfTrust;

class AttestationProtocol {
    static final File ATTESTATION_DATABASE = new File("attestation.db");

    // Developer previews set osVersion to 0 as a placeholder value.
    private static final int DEVELOPER_PREVIEW_OS_VERSION = 0;

    static final int CHALLENGE_LENGTH = 32;
    private static final String SIGNATURE_ALGORITHM = "SHA256WithECDSA";
    private static final HashFunction FINGERPRINT_HASH_FUNCTION = Hashing.sha256();
    private static final int FINGERPRINT_LENGTH = FINGERPRINT_HASH_FUNCTION.bits() / 8;

    static final int SECURITY_LEVEL_STRONGBOX = 2;

    // Challenge message:
    //
    // byte maxVersion = PROTOCOL_VERSION
    // byte[] challenge index (length: CHALLENGE_LENGTH)
    // byte[] challenge (length: CHALLENGE_LENGTH)
    //
    // The challenge index is randomly generated by Auditor and used for all future challenge
    // messages from that Auditor. It's used on the Auditee as an index to choose the correct
    // persistent key to satisfy the Auditor, rather than only supporting pairing with one. In
    // theory, the Auditor could authenticate to the Auditee, but this app already provides a
    // better way to do that by doing the same process in reverse for a supported device.
    //
    // The challenge is randomly generated by the Auditor and serves the security function of
    // enforcing that the results are fresh. It's returned inside the attestation certificate
    // which has a signature from the device's provisioned key (not usable by the OS) and the
    // outer signature from the hardware-backed key generated for the initial pairing.
    //
    // Attestation message:
    //
    // The Auditor will eventually start trying to be backwards compatible with older Auditee app
    // versions but not the other way around.
    //
    // Compression is done with raw DEFLATE (no zlib wrapper) with a preset dictionary generated from
    // sample certificates.
    //
    // signed message {
    // byte version = min(maxVersion, PROTOCOL_VERSION)
    // short compressedChainLength
    // byte[] compressedChain { [short encodedCertificateLength, byte[] encodedCertificate] }
    // byte[] fingerprint (length: FINGERPRINT_LENGTH)
    // int osEnforcedFlags
    // }
    // byte[] signature (rest of message)
    //
    // For each audit, the Auditee generates a fresh hardware-backed key with key attestation
    // using the provided challenge. It reports back the certificate chain to be verified by the
    // Auditor. The public key certificate of the generated key is signed by a key provisioned on
    // the device (not usable by the OS) chaining up to a known Pixel 2 (XL) intermediate and the
    // Google root. The certificate contains the key attestation metadata including the important
    // fields with the lock state, verified boot state, the verified boot public key fingerprint
    // and the OS version / patch level:
    //
    // https://developer.android.com/training/articles/security-key-attestation.html#certificate_schema
    //
    // The Auditee keeps the first hardware-backed key generated for a challenge index and uses it
    // to sign all future attestations. The fingerprint of the persistent key is included in the
    // attestation message for the Auditor to find the corresponding pinning data. Other keys are
    // never actually used, only generated for fresh key attestation data.
    //
    // The OS can use the persistent generated hardware-backed key for signing but cannot obtain
    // the private key. The key isn't be usable if verified boot fails or the OS is downgraded and
    // the keys are protected against replay attacks via the Replay Protected Memory Block.
    // Devices launching with Android P or later can provide a StrongBox Keymaster to support
    // storing the keys in a dedicated hardware security module to substantially reduce the attack
    // surface for obtaining the keys. StrongBox is paired with the TEE and the TEE corroborates
    // the validity of the keys and attestation. The Pixel 3 and 3 XL are the first devices with a
    // StrongBox implementation via the Titan M security chip.
    //
    // https://android-developers.googleblog.com/2018/10/building-titan-better-security-through.html
    //
    // The attestation API could be improved with better guarantees about the certificate chain
    // remaining the same, including rollback indexes in key attestation metadata and adding a
    // per-app-install generated intermediate to the chain to be pinned with the others.
    //
    // The attestation message also includes osEnforcedFlags with data obtained at the OS level,
    // which is vulnerable to tampering by an attacker with control over the OS. However, the OS
    // did get verified by verified boot so without a verified boot bypass they would need to keep
    // exploiting it after booting. The bootloader / TEE verified OS version / OS patch level are
    // a useful mitigation as they reveal that the OS isn't upgraded even if an attacker has root.
    //
    // The Auditor saves the initial certificate chain, using the initial certificate to verify
    // the outer signature and the rest of the chain for pinning the expected chain. It enforces
    // downgrade protection for the OS version/patch (bootloader/TEE enforced) and app version (OS
    // enforced) by keeping them updated.
    static final byte PROTOCOL_VERSION = 1;
    private static final byte PROTOCOL_VERSION_MINIMUM = 1;
    // can become longer in the future, but this is the minimum length
    private static final byte CHALLENGE_MESSAGE_LENGTH = 1 + CHALLENGE_LENGTH * 2;
    private static final int MAX_ENCODED_CHAIN_LENGTH = 3000;
    static final int MAX_MESSAGE_SIZE = 2953;

    private static final int OS_ENFORCED_FLAGS_NONE = 0;
    private static final int OS_ENFORCED_FLAGS_USER_PROFILE_SECURE = 1;
    private static final int OS_ENFORCED_FLAGS_ACCESSIBILITY = 1 << 1;
    private static final int OS_ENFORCED_FLAGS_DEVICE_ADMIN = 1 << 2;
    private static final int OS_ENFORCED_FLAGS_ADB_ENABLED = 1 << 3;
    private static final int OS_ENFORCED_FLAGS_ADD_USERS_WHEN_LOCKED = 1 << 4;
    private static final int OS_ENFORCED_FLAGS_ENROLLED_FINGERPRINTS = 1 << 5;
    private static final int OS_ENFORCED_FLAGS_DENY_NEW_USB = 1 << 6;
    private static final int OS_ENFORCED_FLAGS_DEVICE_ADMIN_NON_SYSTEM = 1 << 7;
    private static final int OS_ENFORCED_FLAGS_OEM_UNLOCK_ALLOWED = 1 << 8;
    private static final int OS_ENFORCED_FLAGS_SYSTEM_USER = 1 << 9;
    private static final int OS_ENFORCED_FLAGS_ALL =
            OS_ENFORCED_FLAGS_USER_PROFILE_SECURE |
            OS_ENFORCED_FLAGS_ACCESSIBILITY |
            OS_ENFORCED_FLAGS_DEVICE_ADMIN |
            OS_ENFORCED_FLAGS_ADB_ENABLED |
            OS_ENFORCED_FLAGS_ADD_USERS_WHEN_LOCKED |
            OS_ENFORCED_FLAGS_ENROLLED_FINGERPRINTS |
            OS_ENFORCED_FLAGS_DENY_NEW_USB |
            OS_ENFORCED_FLAGS_DEVICE_ADMIN_NON_SYSTEM |
            OS_ENFORCED_FLAGS_OEM_UNLOCK_ALLOWED |
            OS_ENFORCED_FLAGS_SYSTEM_USER;

    private static final String ATTESTATION_APP_PACKAGE_NAME = "app.attestation.auditor";
    private static final int ATTESTATION_APP_MINIMUM_VERSION = 5;
    private static final String ATTESTATION_APP_SIGNATURE_DIGEST_DEBUG =
            "17727D8B61D55A864936B1A7B4A2554A15151F32EBCF44CDAA6E6C3258231890";
    private static final String ATTESTATION_APP_SIGNATURE_DIGEST_RELEASE =
            "990E04F0864B19F14F84E0E432F7A393F297AB105A22C1E1B10B442A4A62C42C";
    private static final int OS_VERSION_MINIMUM = 80000;
    private static final int OS_PATCH_LEVEL_MINIMUM = 201801;
    private static final int VENDOR_PATCH_LEVEL_MINIMUM = 201808;
    private static final int BOOT_PATCH_LEVEL_MINIMUM = 201809;

    private static final String DEVICE_HUAWEI = "Huawei Honor 9 lite / Honor 10 / View 10 / Mate 10 / Mate 20 / Mate 20 lite / P smart 2019 / Pro / P20 / P20 Pro / Y9 2019";
    private static final String DEVICE_HUAWEI_HONOR_7A_PRO = "Huawei Honor 7A Pro / Y7 2019";
    private static final String DEVICE_NOKIA = "Nokia (6.1, 6.1 Plus, 7 Plus)";
    private static final String DEVICE_NOKIA_3_1 = "Nokia 3.1";
    private static final String DEVICE_NOKIA_7_1 = "Nokia 7.1";
    private static final String DEVICE_PIXEL_2 = "Google Pixel 2";
    private static final String DEVICE_PIXEL_2_XL = "Google Pixel 2 XL";
    private static final String DEVICE_PIXEL_2_GENERIC = "Google Pixel 2 / Pixel 2 XL";
    private static final String DEVICE_PIXEL_3_GENERIC = "Google Pixel 3 / Pixel 3 XL";
    private static final String DEVICE_PIXEL_3 = "Google Pixel 3";
    private static final String DEVICE_PIXEL_3_XL = "Google Pixel 3 XL";
    private static final String DEVICE_PIXEL_3A_GENERIC = "Google Pixel 3a / Pixel 3a XL";
    private static final String DEVICE_PIXEL_3A = "Google Pixel 3a";
    private static final String DEVICE_PIXEL_3A_XL = "Google Pixel 3a XL";
    private static final String DEVICE_PIXEL_4_GENERIC = "Google Pixel 4 / Pixel 4 XL";
    private static final String DEVICE_PIXEL_4 = "Google Pixel 4";
    private static final String DEVICE_PIXEL_4_XL = "Google Pixel 4 XL";
    private static final String DEVICE_SM_A705FN = "Samsung Galaxy A70 (SM-A705FN)";
    private static final String DEVICE_SM_G960F = "Samsung Galaxy S9 (SM-G960F)";
    private static final String DEVICE_SM_G960_NA = "Samsung Galaxy S9 USA/Canada (SM-G960U/SM-G960U1/SM-G960W)";
    private static final String DEVICE_SM_G9600 = "Samsung Galaxy S9 China (G9600)";
    private static final String DEVICE_SM_G965F = "Samsung Galaxy S9+ (SM-G965F)";
    private static final String DEVICE_SM_G965_MSM = "Samsung Galaxy S9+ (Snapdragon)";
    private static final String DEVICE_SM_G970F = "Samsung Galaxy S10e (SM-G970F)";
    private static final String DEVICE_SM_G975F = "Samsung Galaxy S10+ (SM-G975F)";
    private static final String DEVICE_SM_J260A = "Samsung Galaxy J2 Core (SM-J260A)";
    private static final String DEVICE_SM_J260T1 = "Samsung Galaxy J2 Core (SM-J260T1)";
    private static final String DEVICE_SM_J260F = "Samsung Galaxy J2 Core (SM-J260F)";
    private static final String DEVICE_SM_J337A = "Samsung Galaxy J3 2018 (SM-J337A) / Galaxy Amp Prime 3 (SM-J337AZ)";
    private static final String DEVICE_SM_J337T = "Samsung Galaxy J3 (SM-J337T)";
    private static final String DEVICE_SM_J720F = "Samsung Galaxy J7 Duo (SM-J720F)";
    private static final String DEVICE_SM_M205F = "Samsung Galaxy M20 (SM-M205F)";
    private static final String DEVICE_SM_N960F = "Samsung Galaxy Note 9 (SM-N960F)";
    private static final String DEVICE_SM_N960U = "Samsung Galaxy Note 9 (SM-N960U)";
    private static final String DEVICE_SM_N970F = "Samsung Galaxy Note 10 (SM-N970F)";
    private static final String DEVICE_SM_N970U = "Samsung Galaxy Note 10 (SM-N970U)";
    private static final String DEVICE_SM_N975U = "Samsung Galaxy Note 10+ (SM-N975U)";
    private static final String DEVICE_SM_T510 = "Samsung Galaxy Tab A 10.1 (2019)";
    private static final String DEVICE_SM_T835 = "Samsung Galaxy Tab S4";
    private static final String DEVICE_SM_J737T1 = "Samsung Galaxy J7 (SM-J737T1)";
    private static final String DEVICE_SM_S367VL = "Samsung Galaxy J3 (SM-S367VL)";
    private static final String DEVICE_SONY_XPERIA_XA2 = "Sony Xperia XA2";
    private static final String DEVICE_SONY_XPERIA_XZ1 = "Sony Xperia XZ1 / Xperia XZ1 Compact";
    private static final String DEVICE_SONY_XPERIA_XZ2 = "Sony Xperia XZ2 (H8216)";
    private static final String DEVICE_SONY_XPERIA_XZ2_COMPACT = "Sony Xperia XZ2 Compact";
    private static final String DEVICE_ONEPLUS_6_A6003 = "OnePlus 6 A6003";
    private static final String DEVICE_ONEPLUS_6T_A6013 = "OnePlus 6T A6013";
    private static final String DEVICE_ONEPLUS_7_PRO_GM1913 = "OnePlus 7 Pro GM1913";
    private static final String DEVICE_BLACKBERRY_KEY2 = "BlackBerry Key2";
    private static final String DEVICE_BQ_AQUARIS_X2_PRO = "BQ Aquaris X2 Pro";
    private static final String DEVICE_XIAOMI_MI_A2 = "Xiaomi Mi A2 / POCOPHONE F1";
    private static final String DEVICE_XIAOMI_MI_A2_LITE = "Xiaomi Mi A2 Lite";
    private static final String DEVICE_XIAOMI_MI_9 = "Xiaomi Mi 9";
    private static final String DEVICE_HTC = "HTC EXODUS 1 / U12+";
    private static final String DEVICE_MOTO_G7 = "Motorola moto g⁷";
    private static final String DEVICE_MOTOROLA_ONE_VISION = "Motorola One Vision";
    private static final String DEVICE_VIVO_1807 = "Vivo 1807";
    private static final String DEVICE_REVVL_2 = "T-Mobile REVVL 2";
    private static final String DEVICE_OPPO_CPH1831 = "Oppo R15 Pro (CPH1831)";
    private static final String DEVICE_OPPO_CPH1903 = "Oppo A7 (CPH1903)";
    private static final String DEVICE_OPPO_CPH1909 = "Oppo A5s (CPH1909)";
    private static final String DEVICE_LM_Q720 = "LG Stylo 5 (LM-Q720)";
    private static final String DEVICE_LG_Q710AL = "LG Q Stylo 4 (LG-Q710AL)";
    private static final String DEVICE_RMX1941 = "Realme C2 (RMX1941)";

    private static final String OS_STOCK = "Stock";
    private static final String OS_GRAPHENE = "GrapheneOS";
    private static final String OS_CALYX = "CalyxOS";

    static class DeviceInfo {
        final String name;
        final int attestationVersion;
        final int keymasterVersion;
        final boolean rollbackResistant;
        final boolean perUserEncryption;
        final String osName;

        DeviceInfo(final String name, final int attestationVersion, final int keymasterVersion,
                final boolean rollbackResistant, final boolean perUserEncryption, final String osName) {
            this.name = name;
            this.attestationVersion = attestationVersion;
            this.keymasterVersion = keymasterVersion;
            this.rollbackResistant = rollbackResistant;
            this.perUserEncryption = perUserEncryption;
            this.osName = osName;
        }
    }

    private static final ImmutableSet<String> extraPatchLevelMissing = ImmutableSet.of(
            DEVICE_SM_A705FN,
            DEVICE_SM_G970F,
            DEVICE_SM_G975F,
            DEVICE_SM_N970F,
            DEVICE_SM_N970U,
            DEVICE_SM_N975U,
            DEVICE_SM_T510);

    private static final ImmutableMap<String, String> fingerprintsMigration = ImmutableMap
            .<String, String>builder()
            // GrapheneOS Pixel 3
            .put("0F9A9CC8ADE73064A54A35C5509E77994E3AA37B6FB889DD53AF82C3C570C5CF", // v2
                    "213AA4392BF7CABB9676C2680E134FB5FD3E5937D7E607B4EB907CB0A9D9E400") // v1
            // GrapheneOS Pixel 3 XL
            .put("06DD526EE9B1CB92AA19D9835B68B4FF1A48A3AD31D813F27C9A7D6C271E9451", // v2
                    "60D551860CC7FD32A9DC65FB3BCEB87A5E5C1F88928026F454A234D69B385580") // v1
            // Stock OS Pixel 3 and Pixel 3 XL
            .put("61FDA12B32ED84214A9CF13D1AFFB7AA80BD8A268A861ED4BB7A15170F1AB00C", // v2
                    "B799391AFAE3B35522D1EDC5C70A3746B097BDD1CABD59F72BB049705C7A03EF") // v1
            .build();

    static final ImmutableMap<String, DeviceInfo> fingerprintsCustomOS = ImmutableMap
            .<String, DeviceInfo>builder()
            .put("B094E48B27C6E15661223CEFF539CF35E481DEB4E3250331E973AC2C15CAD6CD",
                    new DeviceInfo(DEVICE_PIXEL_2, 2, 3, true, true, OS_GRAPHENE))
            .put("B6851E9B9C0EBB7185420BD0E79D20A84CB15AB0B018505EFFAA4A72B9D9DAC7",
                    new DeviceInfo(DEVICE_PIXEL_2_XL, 2, 3, true, true, OS_GRAPHENE))
            .put("213AA4392BF7CABB9676C2680E134FB5FD3E5937D7E607B4EB907CB0A9D9E400", // v1
                    new DeviceInfo(DEVICE_PIXEL_3, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("0F9A9CC8ADE73064A54A35C5509E77994E3AA37B6FB889DD53AF82C3C570C5CF", // v2
                    new DeviceInfo(DEVICE_PIXEL_3, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("60D551860CC7FD32A9DC65FB3BCEB87A5E5C1F88928026F454A234D69B385580", // v1
                    new DeviceInfo(DEVICE_PIXEL_3_XL, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("06DD526EE9B1CB92AA19D9835B68B4FF1A48A3AD31D813F27C9A7D6C271E9451", // v2
                    new DeviceInfo(DEVICE_PIXEL_3_XL, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("8FF8B9B4F831114963669E04EA4F849F33F3744686A0B33B833682746645ABC8",
                    new DeviceInfo(DEVICE_PIXEL_3A, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("91943FAA75DCB6392AE87DA18CA57D072BFFB80BC30F8FAFC7FFE13D76C5736E",
                    new DeviceInfo(DEVICE_PIXEL_3A_XL, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("80EF268700EE42686F779A47B4A155FE1FFC2EEDF836B4803CAAB8FA61439746",
                    new DeviceInfo(DEVICE_PIXEL_4, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("3F15FDCB82847FED97427CE00563B8F9FF34627070DE5FDB17ACA7849AB98CC8",
                    new DeviceInfo(DEVICE_PIXEL_4_XL, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            // CalyxOS
            .put("BCEBF6844F6B0FA2ABE8E62A9D0D57A324D0C02CEFDFA019FD49832F9ED39105",
                    new DeviceInfo(DEVICE_PIXEL_2_GENERIC, 2, 3, true, true, OS_CALYX))
            .put("B4DE537A5F4B8FDAB6789EB2C06EC6E065E48A79EDD493A91F635004DD89F3E2",
                    new DeviceInfo(DEVICE_PIXEL_3_GENERIC, 3, 4, false /* uses new API */, true, OS_CALYX))
            .build();
    static final ImmutableMap<String, DeviceInfo> fingerprintsStock = ImmutableMap
            .<String, DeviceInfo>builder()
            .put("5341E6B2646979A70E57653007A1F310169421EC9BDD9F1A5648F75ADE005AF1",
                    new DeviceInfo(DEVICE_HUAWEI, 2, 3, false, true, OS_STOCK))
            .put("7E2E8CC82A77CA74554457E5DF3A3ED82E7032B3182D17FE17919BC6E989FF09",
                    new DeviceInfo(DEVICE_HUAWEI_HONOR_7A_PRO, 2, 3, false, true, OS_STOCK))
            .put("DFC2920C81E136FDD2A510478FDA137B262DC51D449EDD7D0BDB554745725CFE",
                    new DeviceInfo(DEVICE_NOKIA, 2, 3, true, true, OS_STOCK))
            .put("4D790FA0A5FE81D6B352B90AFE430684D9BC817518CD24C50E6343395F7C51F2",
                    new DeviceInfo(DEVICE_NOKIA_3_1, 2, 3, false, false, OS_STOCK))
            .put("893A17FD918235DB2865F7F6439EB0134A45B766AA452E0675BAC6CFB5A773AA",
                    new DeviceInfo(DEVICE_NOKIA_7_1, 2, 3, true, true, OS_STOCK))
            .put("6101853DFF451FAE5B137DF914D5E6C15C659337F2C405AC50B513A159071958",
                    new DeviceInfo(DEVICE_ONEPLUS_6_A6003, 2, 3, true, true, OS_STOCK))
            .put("1B90B7D1449D697FB2732A7D2DFA405D587254593F5137F7B6E64F7A0CE03BFD",
                    new DeviceInfo(DEVICE_ONEPLUS_6T_A6013, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("4B9201B11685BE6710E2B2BA8482F444E237E0C8A3D1F7F447FE29C37CECC559",
                    new DeviceInfo(DEVICE_ONEPLUS_7_PRO_GM1913, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("1962B0538579FFCE9AC9F507C46AFE3B92055BAC7146462283C85C500BE78D82",
                    new DeviceInfo(DEVICE_PIXEL_2, 2, 3, true, true, OS_STOCK))
            .put("171616EAEF26009FC46DC6D89F3D24217E926C81A67CE65D2E3A9DC27040C7AB",
                    new DeviceInfo(DEVICE_PIXEL_2_XL, 2, 3, true, true, OS_STOCK))
            .put("B799391AFAE3B35522D1EDC5C70A3746B097BDD1CABD59F72BB049705C7A03EF", // v1
                    new DeviceInfo(DEVICE_PIXEL_3_GENERIC, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("61FDA12B32ED84214A9CF13D1AFFB7AA80BD8A268A861ED4BB7A15170F1AB00C", // v2
                    new DeviceInfo(DEVICE_PIXEL_3_GENERIC, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("E75B86C52C7496255A95FB1E2B1C044BFA9D5FE34DD1E4EEBD752EEF0EA89875",
                    new DeviceInfo(DEVICE_PIXEL_3A_GENERIC, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("AE6316B4753C61F5855B95B9B98484AF784F2E83648D0FCC8107FCA752CAEA34",
                    new DeviceInfo(DEVICE_PIXEL_4_GENERIC, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("72376CAACF11726D4922585732429FB97D0D1DD69F0D2E0770B9E61D14ADDE65",
                    new DeviceInfo(DEVICE_SM_A705FN, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("33D9484FD512E610BCF00C502827F3D55A415088F276C6506657215E622FA770",
                    new DeviceInfo(DEVICE_SM_G960F, 1, 2, false, false, OS_STOCK))
            .put("266869F7CF2FB56008EFC4BE8946C8F84190577F9CA688F59C72DD585E696488",
                    new DeviceInfo(DEVICE_SM_G960_NA, 1, 2, false, false, OS_STOCK))
            .put("12E8460A7BAF709F3B6CF41C7E5A37C6EB4D11CB36CF7F61F7793C8DCDC3C2E4",
                    new DeviceInfo(DEVICE_SM_G9600, 1, 2, false, false, OS_STOCK))
            .put("D1C53B7A931909EC37F1939B14621C6E4FD19BF9079D195F86B3CEA47CD1F92D",
                    new DeviceInfo(DEVICE_SM_G965F, 1, 2, false, false, OS_STOCK))
            .put("A4A544C2CFBAEAA88C12360C2E4B44C29722FC8DBB81392A6C1FAEDB7BF63010",
                    new DeviceInfo(DEVICE_SM_G965_MSM, 1, 2, false, false, OS_STOCK))
            .put("9D77474FA4FEA6F0B28636222FBCEE2BB1E6FF9856C736C85B8EA6E3467F2BBA",
                    new DeviceInfo(DEVICE_SM_G970F, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("08B2B5C6EC8F54C00C505756E1EF516BB4537B2F02D640410D287A43FCF92E3F",
                    new DeviceInfo(DEVICE_SM_G975F, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("F0FC0AF47D3FE4F27D79CF629AD6AC42AA1EEDE0A29C0AE109A91BBD1E7CD76D",
                    new DeviceInfo(DEVICE_SM_J260A, 1, 2, false, false, OS_STOCK))
            .put("410102030405060708090001020304050607080900010203040506070809005A",
                    new DeviceInfo(DEVICE_SM_J260F, 1, 2, false, false, OS_STOCK))
            .put("D6B902D9E77DFC0FB3627FFEFA6D05405932EBB3A6ED077874B5E2A0CCBDB632",
                    new DeviceInfo(DEVICE_SM_J260T1, 1, 2, false, false, OS_STOCK))
            .put("4558C1AFB30D1B46CB93F85462BC7D7FCF70B0103B9DBB0FE96DD828F43F29FC",
                    new DeviceInfo(DEVICE_SM_J337A, 1, 2, false, false, OS_STOCK))
            .put("45E3AB5D61A03915AE10BF0465B186CB5D9A2FB6A46BEFAA76E4483BBA5A358D",
                    new DeviceInfo(DEVICE_SM_J337T, 1, 2, false, false, OS_STOCK))
            .put("D95279A8F2E832FD68D919DBF33CFE159D5A1179686DB0BD2D7BBBF2382C4DD3",
                    new DeviceInfo(DEVICE_SM_J720F, 1, 2, false, false, OS_STOCK))
            .put("BB053A5F64D3E3F17C4611340FF2BBE2F605B832A9FA412B2C87F2A163ECE2FB",
                    new DeviceInfo(DEVICE_SM_J737T1, 1, 2, false, false, OS_STOCK))
            .put("4E0570011025D01386D057B2B382969F804DCD19E001344535CF0CFDB8AD7CFE",
                    new DeviceInfo(DEVICE_SM_M205F, 1, 2, false, false, OS_STOCK))
            .put("2A7E4954C9F703F3AC805AC660EA1727B981DB39B1E0F41E4013FA2586D3DF7F",
                    new DeviceInfo(DEVICE_SM_N960F, 1, 2, false, false, OS_STOCK))
            .put("173ACFA8AE9EDE7BBD998F45A49231F3A4BDDF0779345732E309446B46B5641B",
                    new DeviceInfo(DEVICE_SM_N960U, 1, 2, false, false, OS_STOCK))
            .put("E94BC43B97F98CD10C22CD9D8469DBE621116ECFA624FE291A1D53CF3CD685D1",
                    new DeviceInfo(DEVICE_SM_N970F, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("466011C44BBF883DB38CF96617ED35C796CE2552C5357F9230258329E943DB70",
                    new DeviceInfo(DEVICE_SM_N970U, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("52946676088007755EB586B3E3F3E8D3821BE5DF73513E6C13640507976420E6",
                    new DeviceInfo(DEVICE_SM_N975U, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("F3688C02D9676DEDB6909CADE364C271901FD66EA4F691AEB8B8921195E469C5",
                    new DeviceInfo(DEVICE_SM_S367VL, 1, 2, false, false, OS_STOCK))
            .put("106592D051E54388C6E601DFD61D59EB1674A8B93216C65C5B3E1830B73D3B82",
                    new DeviceInfo(DEVICE_SM_T510, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("87790149AED63553B768456AAB6DAAD5678CD87BDEB2BF3649467085349C34E0",
                    new DeviceInfo(DEVICE_SM_T835, 1, 2, false, false, OS_STOCK))
            .put("4285AD64745CC79B4499817F264DC16BF2AF5163AF6C328964F39E61EC84693E",
                    new DeviceInfo(DEVICE_SONY_XPERIA_XA2, 2, 3, true, true, OS_STOCK))
            .put("54A9F21E9CFAD3A2D028517EF333A658302417DB7FB75E0A109A019646CC5F39",
                    new DeviceInfo(DEVICE_SONY_XPERIA_XZ1, 2, 3, true, true, OS_STOCK))
            .put("BC3B5E121974113939B8A2FE758F9B923F1D195F038D2FD1C04929F886E83BB5",
                    new DeviceInfo(DEVICE_SONY_XPERIA_XZ2, 2, 3, false, true, OS_STOCK))
            .put("94B8B4E3260B4BF8211A02CF2F3DE257A127CFFB2E4047D5580A752A5E253DE0",
                    new DeviceInfo(DEVICE_SONY_XPERIA_XZ2_COMPACT, 2, 3, true, true, OS_STOCK))
            .put("728800FEBB119ADD74519618AFEDB715E1C39FE08A4DE37D249BF54ACF1CE00F",
                    new DeviceInfo(DEVICE_BLACKBERRY_KEY2, 2, 3, true, true, OS_STOCK))
            .put("1194659B40EA291245E54A3C4EC4AA5B7077BD244D65C7DD8C0A2DBB9DB1FB35",
                    new DeviceInfo(DEVICE_BQ_AQUARIS_X2_PRO, 2, 3, true, false, OS_STOCK))
            .put("A9C6758D509600D0EB94FA8D2BF6EE7A6A6097F0CCEF94A755DDE065AA1AA1B0",
                    new DeviceInfo(DEVICE_XIAOMI_MI_A2, 2, 3, true, false, OS_STOCK))
            .put("6FA710B639848C9D47378937A1AFB1B6A52DDA738BEB6657E2AE70A15B40541A",
                    new DeviceInfo(DEVICE_XIAOMI_MI_A2_LITE, 2, 3, true, false, OS_STOCK))
            .put("84BC8445A29B5444A2D1629C9774C8626DAFF3574D865EC5067A78FAEC96B013",
                    new DeviceInfo(DEVICE_XIAOMI_MI_9, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("1CC39488D2F85DEE0A8E0903CDC4124CFDF2BE2531ED6060B678057ED2CB89B4",
                    new DeviceInfo(DEVICE_HTC, 2, 3, true, false, OS_STOCK))
            .put("80BAB060807CFFA45D4747DF1AD706FEE3AE3F645F80CF14871DDBE27E14C30B",
                    new DeviceInfo(DEVICE_MOTO_G7, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("C2224571C9CD5C89200A7311B1E37AA9CF751E2E19753E8D3702BCA00BE1D42C",
                    new DeviceInfo(DEVICE_MOTOROLA_ONE_VISION, 2, 3, false, true, OS_STOCK))
            .put("1F6D98D1B0E1F1CE1C872BD36C668F9DFDBE0D47594789E1540DF4E6198F657D",
                    new DeviceInfo(DEVICE_VIVO_1807, 2, 3, true, false, OS_STOCK))
            .put("C55635636999E9D0A0588D24402256B7F9F3AEE07B4F7E4E003F09FF0190AFAE",
                    new DeviceInfo(DEVICE_REVVL_2, 2, 3, false, false, OS_STOCK))
            .put("341C50D577DC5F3D5B46E8BFA22C22D1E5FC7D86D4D860E70B89222A7CBFC893",
                    new DeviceInfo(DEVICE_OPPO_CPH1831, 2, 3, true, false, OS_STOCK))
            .put("41BF0A26BB3AFDCCCC40F7B685083522EB5BF1C492F0EC4847F351265313CB07",
                    new DeviceInfo(DEVICE_OPPO_CPH1903, 2, 3, true, false, OS_STOCK))
            .put("7E19E217072BE6CB7A4C6F673FD3FB62DC51B3E204E7475838747947A3920DD8",
                    new DeviceInfo(DEVICE_OPPO_CPH1909, 2, 3, false, false, OS_STOCK))
            .put("0D5F986943D0CE0D4F9783C27EEBE175BE359927DB8B6546B667279A81133C3C",
                    new DeviceInfo(DEVICE_LG_Q710AL, 2, 3, false, false, OS_STOCK))
            .put("D20078F2AF2A7D3ECA3064018CB8BD47FBCA6EE61ABB41BA909D3C529CB802F4",
                    new DeviceInfo(DEVICE_LM_Q720, 3, 4, false /* uses new API */, false, OS_STOCK))
            .put("54EC644C21FD8229E3B0066513337A8E2C8EF3098A3F974B6A1CFE456A683DAE",
                    new DeviceInfo(DEVICE_RMX1941, 2, 3, false, true, OS_STOCK))
            .build();

    static final ImmutableMap<String, DeviceInfo> fingerprintsStrongBoxCustomOS = ImmutableMap
            .<String, DeviceInfo>builder()
            // GrapheneOS
            .put("0F9A9CC8ADE73064A54A35C5509E77994E3AA37B6FB889DD53AF82C3C570C5CF",
                    new DeviceInfo(DEVICE_PIXEL_3, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("06DD526EE9B1CB92AA19D9835B68B4FF1A48A3AD31D813F27C9A7D6C271E9451",
                    new DeviceInfo(DEVICE_PIXEL_3_XL, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("73D6C63A07610404FE16A4E07DD24E41A70D331E9D3EF7BBA2D087E4761EB63A",
                    new DeviceInfo(DEVICE_PIXEL_3A, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("3F36E3482E1FF82986576552CB4FD08AF09F8B09D3832314341E04C42D2919A4",
                    new DeviceInfo(DEVICE_PIXEL_3A_XL, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("80EF268700EE42686F779A47B4A155FE1FFC2EEDF836B4803CAAB8FA61439746",
                    new DeviceInfo(DEVICE_PIXEL_4, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            .put("3F15FDCB82847FED97427CE00563B8F9FF34627070DE5FDB17ACA7849AB98CC8",
                    new DeviceInfo(DEVICE_PIXEL_4_XL, 3, 4, false /* uses new API */, true, OS_GRAPHENE))
            // CalyxOS
            .put("B4DE537A5F4B8FDAB6789EB2C06EC6E065E48A79EDD493A91F635004DD89F3E2",
                    new DeviceInfo(DEVICE_PIXEL_3_GENERIC, 3, 4, false /* uses new API */, true, OS_CALYX))
            .build();
    static final ImmutableMap<String, DeviceInfo> fingerprintsStrongBoxStock = ImmutableMap
            .<String, DeviceInfo>builder()
            .put("61FDA12B32ED84214A9CF13D1AFFB7AA80BD8A268A861ED4BB7A15170F1AB00C",
                    new DeviceInfo(DEVICE_PIXEL_3_GENERIC, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("8CA89AF1A6DAA74B00810849356DE929CFC4498EF36AF964757BDE8A113BF46D",
                    new DeviceInfo(DEVICE_PIXEL_3A_GENERIC, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("AE6316B4753C61F5855B95B9B98484AF784F2E83648D0FCC8107FCA752CAEA34",
                    new DeviceInfo(DEVICE_PIXEL_4_GENERIC, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("3D3DEB132A89551D0A700D230BABAE4E3E80E3C7926ACDD7BAEDF9B57AD316D0",
                    new DeviceInfo(DEVICE_SM_N970U, 3, 4, false /* uses new API */, true, OS_STOCK))
            .put("9AC63842137D92C119A1B1BE2C9270B9EBB6083BBE6350B7823571942B5869F0",
                    new DeviceInfo(DEVICE_SM_N975U, 3, 4, false /* uses new API */, true, OS_STOCK))
            .build();

    private static final String GOOGLE_ROOT_CERTIFICATE =
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV" +
            "BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy" +
            "ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B" +
            "AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS" +
            "Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7" +
            "tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj" +
            "nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq" +
            "C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ" +
            "oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O" +
            "JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg" +
            "sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi" +
            "igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M" +
            "RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E" +
            "aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um" +
            "AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD" +
            "VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO" +
            "BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk" +
            "Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD" +
            "ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB" +
            "Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m" +
            "qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY" +
            "DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm" +
            "QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u" +
            "JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD" +
            "CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy" +
            "ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD" +
            "qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic" +
            "MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1" +
            "wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk\n" +
            "-----END CERTIFICATE-----";

    private static final byte[] DEFLATE_DICTIONARY_1 = BaseEncoding.base64().decode(
            "MIICOzCCAeKgAwIBAgIBATAKBggqhkjOPQQDAjAbMRkwFwYDVQQFExBkNzc1MjM0ODY2ZjM3ZjUz" +
            "MCAXDTcwMDEwMTAwMDAwMFoYDzIxMDYwMjA3MDYyODE1WjAfMR0wGwYDVQQDDBRBbmRyb2lkIEtl" +
            "eXN0b3JlIEtleTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL//uGYRGvZkKMBn+OD141iz+Roo" +
            "KosorGyVa0lqW/BK40odihF55g1elhnVqjclcGE/H4b4jIKuC5vq+r+vF16jggEPMIIBCzAOBgNV" +
            "HQ8BAf8EBAMCB4AwgfgGCisGAQQB1nkCAREEgekwgeYCAQIKAQECAQMKAQEEBnNhbXBsZQQAMFi/" +
            "hT0IAgYBZwC3xBC/hUVIBEYwRDEeMBwEF2FwcC5hdHRlc3RhdGlvbi5hdWRpdG9yAgEEMSIEIJkO" +
            "BPCGSxnxT4Tg5DL3o5Pyl6sQWiLB4bELRCpKYsQsMHShCDEGAgECAgEDogMCAQOjBAICAQClBTED" +
            "AgEEqgMCAQG/g3cCBQC/hT4DAgEAv4U/AgUAv4VAKjAoBCAXFhbq7yYAn8RtxtifPSQhfpJsgaZ8" +
            "5l0uOp3CcEDHqwEB/woBAL+FQQUCAwFfkL+FQgUCAwMUUzAKBggqhkjOPQQDAgNHADBEAiBXQsil" +
            "fWfag1ubiMjD4AQrawdvNmQj5Qi62uAIfv+ZzgIgfZTibp2AVtBWSAezlalyOaZIMo+z7wS8ZFq3" +
            "eHicabgwggIpMIIBr6ADAgECAgloORJGdChHOWEwCgYIKoZIzj0EAwIwGzEZMBcGA1UEBRMQODdm" +
            "NDUxNDQ3NWJhMGEyYjAeFw0xNjA1MjYxNzA3MzNaFw0yNjA1MjQxNzA3MzNaMBsxGTAXBgNVBAUT" +
            "EGQ3NzUyMzQ4NjZmMzdmNTMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASqtc7KxE2tv4aOkx2u" +
            "axeuqr6PItpqGRyAKjqsBE7JhY4poRG9oWjC0azGRJ8xJuqimPeqn/8kE5bmJGsdFyngo4HbMIHY" +
            "MB0GA1UdDgQWBBQvvxzSWU7SsNOWC3CeF0TcGhBQSzAfBgNVHSMEGDAWgBQwRCPlovYG4VCrd18W" +
            "FruRzGPGWTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAkBgNVHR4EHTAboBkwF4IVaW52" +
            "YWxpZDtlbWFpbDppbnZhbGlkMFIGA1UdHwRLMEkwR6BFoEOGQWh0dHBzOi8vYW5kcm9pZC5nb29n" +
            "bGVhcGlzLmNvbS9hdHRlc3RhdGlvbi9jcmwvNjgzOTEyNDY3NDI4NDczOTYxMAoGCCqGSM49BAMC" +
            "A2gAMGUCMD2sDgFbg20egPedfKwfOIqUugCEK6nxXh02Za7qG0i3jcjcA2ZWbsLN602qASM08AIx" +
            "AIG4MorAs4cED1r90dv3LwCkZn4BTpK13Ef+sc9hEWrzj/lY6ZRXu894W5Ggw97TzTCCA8MwggGr" +
            "oAMCAQICCgOIJmdgZYmWhXUwDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEBRMQZjkyMDA5ZTg1M2I2" +
            "YjA0NTAeFw0xNjA1MjYxNzAxNTFaFw0yNjA1MjQxNzAxNTFaMBsxGTAXBgNVBAUTEDg3ZjQ1MTQ0" +
            "NzViYTBhMmIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARkO1ZoHSf6xILPbCBN687wKVw1DAGqjDJ/" +
            "PtX0Sa70KWZCXzk9hHY7Xa1WsQ0EWcYsak+TJVaokpPD0U6i8d/vDjy6InKjIjnhsrR9rFULq7xc" +
            "p1XiqkhiitY8dv9n3HKjgbYwgbMwHQYDVR0OBBYEFDBEI+Wi9gbhUKt3XxYWu5HMY8ZZMB8GA1Ud" +
            "IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD" +
            "AgGGMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHBzOi8vYW5kcm9pZC5nb29nbGVhcGlzLmNvbS9hdHRl" +
            "c3RhdGlvbi9jcmwvRThGQTE5NjMxNEQyRkExODANBgkqhkiG9w0BAQsFAAOCAgEAQDmKiza8k5mz" +
            "pZ645yLw6F8c2izoAl7tvZeyqWxR2nFS8YGLhkYiBEKs2QalGErfT4Mfj9ir6B7c/fbBGa9sU3tA" +
            "8Ogkx0wJsVl1BXa5bxDJthskn7yUXzDQrl9aCEvzOT30RGNmGR8i6v7J9bCa9xY2yjHVDAaOQHGp" +
            "nA9x/MJfTv+qn4V/ue4l86CusjMyURvzJZJufP7BZNKY2slAXIZlCS9BUOiISmIMvEShNlA8lyjh" +
            "O/mTEjFFJAHc8p9jxb+070LQ5NIHtppFaza3z0oJTMwTb60gxzwZk5LV88LHlWoCEU+ViPdTq1Dm" +
            "DxYKLLbHx+NobOcCbQBRj8A4P9ZjWvFMh/ZNWzK0at0q3s9VxtCbu0hjf7jV+XZHML3wJmnbZjOz" +
            "6fpdlR0WEUetNIwHDNvu25fHoKnzPwO78gy2ajyMQ8JC149njffZyWSVWhnwfXR8hTqgagTOkncl" +
            "ejEiK9wB6KOrvgViF/fqvJ9aoOSOiVnhHviufEm0Q1aDUILCj/f4j67lzFOa5tssD8SAlRsrJdRo" +
            "AD/hSd1+qD9LVxVkJWKFnUpaElRzWUKo9nr/+vbDJxrE3icNVeyNIEfnmTRH8Xvr2Q+Cj/PCsboj" +
            "wnveK6fgKrOf2MLgqOq0PhxouTmTQbEF2CLnwpUgY3vjqY0G+xyYIoSXc6NvyAc=");

    static byte[] getChallenge() {
        final SecureRandom random = new SecureRandom();
        final byte[] challenge = new byte[CHALLENGE_LENGTH];
        random.nextBytes(challenge);
        return challenge;
    }

    private static byte[] getFingerprint(final Certificate certificate)
            throws CertificateEncodingException {
        return FINGERPRINT_HASH_FUNCTION.hashBytes(certificate.getEncoded()).asBytes();
    }

    private static class Verified {
        final String device;
        final String verifiedBootKey;
        final byte[] verifiedBootHash;
        final String osName;
        final int osVersion;
        final int osPatchLevel;
        final int vendorPatchLevel;
        final int bootPatchLevel;
        final int appVersion;
        final int securityLevel;

        Verified(final String device, final String verifiedBootKey, final byte[] verifiedBootHash,
                final String osName, final int osVersion, final int osPatchLevel,
                final int vendorPatchLevel, final int bootPatchLevel, final int appVersion,
                final int securityLevel) {
            this.device = device;
            this.verifiedBootKey = verifiedBootKey;
            this.verifiedBootHash = verifiedBootHash;
            this.osName = osName;
            this.osVersion = osVersion;
            this.osPatchLevel = osPatchLevel;
            this.vendorPatchLevel = vendorPatchLevel;
            this.bootPatchLevel = bootPatchLevel;
            this.appVersion = appVersion;
            this.securityLevel = securityLevel;
        }
    }

    private static X509Certificate generateCertificate(final InputStream in)
            throws CertificateException {
        return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(in);
    }

    private static Verified verifyStateless(final Certificate[] certificates,
            final Cache<ByteBuffer, Boolean> pendingChallenges, final Certificate root) throws GeneralSecurityException {

        verifyCertificateSignatures(certificates);

        // check that the root certificate is the Google key attestation root
        if (!Arrays.equals(root.getEncoded(), certificates[certificates.length - 1].getEncoded())) {
            throw new GeneralSecurityException("root certificate is not the Google key attestation root");
        }

        final Attestation attestation = new Attestation((X509Certificate) certificates[0]);

        final int attestationSecurityLevel = attestation.getAttestationSecurityLevel();

        // enforce hardware-based attestation
        if (attestationSecurityLevel != Attestation.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT &&
                attestationSecurityLevel != SECURITY_LEVEL_STRONGBOX) {
            throw new GeneralSecurityException("attestation security level is not valid");
        }
        if (attestation.getKeymasterSecurityLevel() != attestationSecurityLevel) {
            throw new GeneralSecurityException("keymaster security level is not valid");
        }

        // prevent replay attacks
        final byte[] challenge = attestation.getAttestationChallenge();
        if (pendingChallenges.asMap().remove(ByteBuffer.wrap(challenge)) == null) {
            throw new GeneralSecurityException("challenge not pending");
        }

        // enforce communicating with the attestation app via OS level security
        final AuthorizationList softwareEnforced = attestation.getSoftwareEnforced();
        final AttestationApplicationId attestationApplicationId = softwareEnforced.getAttestationApplicationId();
        final List<AttestationPackageInfo> infos = attestationApplicationId.getAttestationPackageInfos();
        if (infos.size() != 1) {
            throw new GeneralSecurityException("wrong number of attestation packages");
        }
        final AttestationPackageInfo info = infos.get(0);
        if (!ATTESTATION_APP_PACKAGE_NAME.equals(info.getPackageName())) {
            throw new GeneralSecurityException("wrong attestation app package name");
        }
        final int appVersion = Math.toIntExact(info.getVersion()); // int for compatibility
        if (appVersion < ATTESTATION_APP_MINIMUM_VERSION) {
            throw new GeneralSecurityException("attestation app is too old");
        }
        final List<byte[]> signatureDigests = attestationApplicationId.getSignatureDigests();
        if (signatureDigests.size() != 1) {
            throw new GeneralSecurityException("wrong number of attestation app signature digests");
        }
        final String signatureDigest = BaseEncoding.base16().encode(signatureDigests.get(0));
        if (!ATTESTATION_APP_SIGNATURE_DIGEST_RELEASE.equals(signatureDigest)) {
            if (!BuildConfig.DEBUG || !ATTESTATION_APP_SIGNATURE_DIGEST_DEBUG.equals(signatureDigest)) {
                throw new GeneralSecurityException("wrong attestation app signature digest");
            }
        }

        final AuthorizationList teeEnforced = attestation.getTeeEnforced();

        // verified boot security checks
        final RootOfTrust rootOfTrust = teeEnforced.getRootOfTrust();
        if (rootOfTrust == null) {
            throw new GeneralSecurityException("missing root of trust");
        }
        if (!rootOfTrust.isDeviceLocked()) {
            throw new GeneralSecurityException("device is not locked");
        }

        final int verifiedBootState = rootOfTrust.getVerifiedBootState();
        final String verifiedBootKey = BaseEncoding.base16().encode(rootOfTrust.getVerifiedBootKey());
        final DeviceInfo device;
        if (verifiedBootState == RootOfTrust.KM_VERIFIED_BOOT_SELF_SIGNED) {
            if (attestationSecurityLevel == SECURITY_LEVEL_STRONGBOX) {
                device = fingerprintsStrongBoxCustomOS.get(verifiedBootKey);
            } else {
                device = fingerprintsCustomOS.get(verifiedBootKey);
            }
        } else if (verifiedBootState == RootOfTrust.KM_VERIFIED_BOOT_VERIFIED) {
            if (attestationSecurityLevel == SECURITY_LEVEL_STRONGBOX) {
                device = fingerprintsStrongBoxStock.get(verifiedBootKey);
            } else {
                device = fingerprintsStock.get(verifiedBootKey);
            }
        } else {
            throw new GeneralSecurityException("verified boot state is not verified or self signed");
        }

        if (device == null) {
            throw new GeneralSecurityException("invalid verified boot key fingerprint: " + verifiedBootKey);
        }

        // OS version sanity checks
        final int osVersion = teeEnforced.getOsVersion();
        if (osVersion == DEVELOPER_PREVIEW_OS_VERSION) {
            throw new GeneralSecurityException("OS version is not a production release");
        } else if (osVersion < OS_VERSION_MINIMUM) {
            throw new GeneralSecurityException("OS version too old: " + osVersion);
        }
        final int osPatchLevel = teeEnforced.getOsPatchLevel();
        if (osPatchLevel < OS_PATCH_LEVEL_MINIMUM) {
            throw new GeneralSecurityException("OS patch level too old: " + osPatchLevel);
        }
        final int vendorPatchLevel;
        if (teeEnforced.getVendorPatchLevel() == null) {
            vendorPatchLevel = 0;
        } else {
            vendorPatchLevel = teeEnforced.getVendorPatchLevel();
            if (vendorPatchLevel < VENDOR_PATCH_LEVEL_MINIMUM && !extraPatchLevelMissing.contains(device.name)) {
                throw new GeneralSecurityException("Vendor patch level too old: " + vendorPatchLevel);
            }
        }
        final int bootPatchLevel;
        if (teeEnforced.getBootPatchLevel() == null) {
            bootPatchLevel = 0;
        } else {
            bootPatchLevel = teeEnforced.getBootPatchLevel();
            if (bootPatchLevel < BOOT_PATCH_LEVEL_MINIMUM && !extraPatchLevelMissing.contains(device.name)) {
                throw new GeneralSecurityException("Boot patch level too old: " + bootPatchLevel);
            }
        }

        // key sanity checks
        if (teeEnforced.getOrigin() != AuthorizationList.KM_ORIGIN_GENERATED) {
            throw new GeneralSecurityException("not a generated key");
        }
        if (teeEnforced.isAllApplications()) {
            throw new GeneralSecurityException("expected key only usable by attestation app");
        }
        if (device.rollbackResistant && !teeEnforced.isRollbackResistant()) {
            throw new GeneralSecurityException("expected rollback resistant key");
        }

        // version sanity checks
        final int attestationVersion = attestation.getAttestationVersion();
        if (attestationVersion < device.attestationVersion) {
            throw new GeneralSecurityException("attestation version " + attestationVersion + " below " + device.attestationVersion);
        }
        final int keymasterVersion = attestation.getKeymasterVersion();
        if (keymasterVersion < device.keymasterVersion) {
            throw new GeneralSecurityException("keymaster version " + keymasterVersion + " below " + device.keymasterVersion);
        }

        final byte[] verifiedBootHash = rootOfTrust.getVerifiedBootHash();
        if (attestationVersion >= 3 && verifiedBootHash == null) {
            throw new GeneralSecurityException("verifiedBootHash expected for attestation version >= 3");
        }

        return new Verified(device.name, verifiedBootKey, verifiedBootHash, device.osName,
                osVersion, osPatchLevel, vendorPatchLevel, bootPatchLevel, appVersion,
                attestationSecurityLevel);
    }

    private static void verifyCertificateSignatures(Certificate[] certChain)
            throws GeneralSecurityException {
        for (int i = 1; i < certChain.length; ++i) {
            final PublicKey pubKey = certChain[i].getPublicKey();
            try {
                ((X509Certificate) certChain[i - 1]).checkValidity();
                certChain[i - 1].verify(pubKey);
            } catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException
                    | NoSuchProviderException | SignatureException e) {
                throw new GeneralSecurityException("Failed to verify certificate "
                        + certChain[i - 1] + " with public key " + certChain[i].getPublicKey(), e);
            }
            if (i == certChain.length - 1) {
                // Last cert is self-signed.
                try {
                    ((X509Certificate) certChain[i]).checkValidity();
                    certChain[i].verify(pubKey);
                } catch (CertificateException e) {
                    throw new GeneralSecurityException(
                            "Root cert " + certChain[i] + " is not correctly self-signed", e);
                }
            }
        }
    }

    private static void appendVerifiedInformation(final StringBuilder builder,
            final Verified verified) {
        final String osVersion = String.format(Locale.US, "%06d", verified.osVersion);
        builder.append(String.format("OS version: %s\n",
                Integer.parseInt(osVersion.substring(0, 2)) + "." +
                Integer.parseInt(osVersion.substring(2, 4)) + "." +
                Integer.parseInt(osVersion.substring(4, 6))));

        final String osPatchLevel = Integer.toString(verified.osPatchLevel);
        builder.append(String.format("OS patch level: %s\n",
                osPatchLevel.substring(0, 4) + "-" + osPatchLevel.substring(4, 6)));

        final String vendorPatchLevel = Integer.toString(verified.vendorPatchLevel);
        if (verified.vendorPatchLevel != 0) {
            builder.append(String.format("Vendor patch level: %s\n",
                    vendorPatchLevel.substring(0, 4) + "-" + vendorPatchLevel.substring(4, 6)));
        }

        final String bootPatchLevel = Integer.toString(verified.bootPatchLevel);
        if (verified.bootPatchLevel != 0) {
            builder.append(String.format("Boot patch level: %s\n",
                    bootPatchLevel.substring(0, 4) + "-" + bootPatchLevel.substring(4, 6)));
        }

        if (verified.verifiedBootHash != null) {
            builder.append(String.format("Verified boot hash: %s\n",
                    BaseEncoding.base16().encode(verified.verifiedBootHash)));
        }
    }

    private static void verifySignature(final PublicKey key, final ByteBuffer message,
            final byte[] signature) throws GeneralSecurityException {
        final Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
        sig.initVerify(key);
        sig.update(message);
        if (!sig.verify(signature)) {
            throw new GeneralSecurityException("signature verification failed");
        }
    }

    static class VerificationResult {
        final boolean strong;
        final String teeEnforced;
        final String osEnforced;

        VerificationResult(final boolean strong, final String teeEnforced,
                final String osEnforced) {
            this.strong = strong;
            this.teeEnforced = teeEnforced;
            this.osEnforced = osEnforced;
        }
    }

    private static String toYesNoString(final boolean value) {
        return value ? "yes" : "no";
    }

    private static void verify(final byte[] fingerprint,
            final Cache<ByteBuffer, Boolean> pendingChallenges, final long userId,
            final boolean paired, final ByteBuffer signedMessage, final byte[] signature,
            final Certificate[] attestationCertificates, final boolean userProfileSecure,
            final boolean accessibility, final boolean deviceAdmin,
            final boolean deviceAdminNonSystem, final boolean adbEnabled,
            final boolean addUsersWhenLocked, final boolean enrolledFingerprints,
            final boolean denyNewUsb, final boolean oemUnlockAllowed, final boolean systemUser)
            throws GeneralSecurityException, IOException {
        final String fingerprintHex = BaseEncoding.base16().encode(fingerprint);
        final byte[] currentFingerprint = getFingerprint(attestationCertificates[0]);
        final boolean hasPersistentKey = !Arrays.equals(currentFingerprint, fingerprint);
        if (paired && !hasPersistentKey) {
            throw new GeneralSecurityException("must be authenticated with subscribeKey for initial verification");
        }

        final SQLiteConnection conn = new SQLiteConnection(ATTESTATION_DATABASE);
        try {
            AttestationServer.open(conn, false);

            final byte[][] pinnedCertificates = new byte[3][];
            byte[] pinnedVerifiedBootKey = null;
            int pinnedOsVersion = Integer.MAX_VALUE;
            int pinnedOsPatchLevel = Integer.MAX_VALUE;
            int pinnedVendorPatchLevel = 0;
            int pinnedBootPatchLevel = 0;
            int pinnedAppVersion = Integer.MAX_VALUE;
            int pinnedSecurityLevel = 1;
            if (hasPersistentKey) {
                final SQLiteStatement st = conn.prepare("SELECT pinnedCertificate0, " +
                        "pinnedCertificate1, pinnedCertificate2, pinnedVerifiedBootKey, " +
                        "pinnedOsVersion, pinnedOsPatchLevel, pinnedVendorPatchLevel, " +
                        "pinnedBootPatchLevel, pinnedAppVersion, pinnedSecurityLevel, userId " +
                        "FROM Devices WHERE fingerprint = ?");
                st.bind(1, fingerprint);
                if (st.step()) {
                    pinnedCertificates[0] = st.columnBlob(0);
                    pinnedCertificates[1] = st.columnBlob(1);
                    pinnedCertificates[2] = st.columnBlob(2);
                    pinnedVerifiedBootKey = st.columnBlob(3);
                    pinnedOsVersion = st.columnInt(4);
                    pinnedOsPatchLevel = st.columnInt(5);
                    pinnedVendorPatchLevel = st.columnInt(6);
                    pinnedBootPatchLevel = st.columnInt(7);
                    pinnedAppVersion = st.columnInt(8);
                    pinnedSecurityLevel = st.columnInt(9);
                    if (userId != st.columnLong(10)) {
                        throw new GeneralSecurityException("wrong userId");
                    }
                    st.dispose();
                } else {
                    st.dispose();
                    throw new GeneralSecurityException(
                            "Pairing data for this Auditee is missing. Cannot perform paired attestation.\n" +
                            "\nEither the initial pairing was incomplete or the device is compromised.\n" +
                            "\nIf the initial pairing was simply not completed, clear the pairing data on either the Auditee or the Auditor via the menu and try again.\n");
                }
            }

            final Verified verified = verifyStateless(attestationCertificates, pendingChallenges,
                    generateCertificate(new ByteArrayInputStream(GOOGLE_ROOT_CERTIFICATE.getBytes())));
            final byte[] verifiedBootKey = BaseEncoding.base16().decode(verified.verifiedBootKey);

            final StringBuilder teeEnforced = new StringBuilder();
            final long now = new Date().getTime();

            if (attestationCertificates.length != 4) {
                throw new GeneralSecurityException("currently only support certificate chains with length 4");
            }

            if (hasPersistentKey) {
                for (int i = 1; i < attestationCertificates.length - 1; i++) {
                    if (!Arrays.equals(attestationCertificates[i].getEncoded(), pinnedCertificates[i])) {
                        throw new GeneralSecurityException("certificate chain mismatch");
                    }
                }

                final Certificate persistentCertificate = generateCertificate(
                        new ByteArrayInputStream(pinnedCertificates[0]));
                if (!Arrays.equals(fingerprint, getFingerprint(persistentCertificate))) {
                    throw new GeneralSecurityException("corrupt Auditor pinning data");
                }
                verifySignature(persistentCertificate.getPublicKey(), signedMessage, signature);

                if (!Arrays.equals(verifiedBootKey, pinnedVerifiedBootKey)) {
                    final String legacyFingerprint = fingerprintsMigration.get(verified.verifiedBootKey);
                    if (legacyFingerprint != null && legacyFingerprint.equals(BaseEncoding.base16().encode(pinnedVerifiedBootKey))) {
                        //Log.d(TAG, "migration from legacy fingerprint " + legacyFingerprint + " to " + verified.verifiedBootKey);
                    } else {
                        throw new GeneralSecurityException("pinned verified boot key mismatch");
                    }
                }
                if (verified.osVersion < pinnedOsVersion) {
                    throw new GeneralSecurityException("OS version downgrade detected");
                }
                if (verified.osPatchLevel < pinnedOsPatchLevel) {
                    throw new GeneralSecurityException("OS patch level downgrade detected");
                }
                if (verified.vendorPatchLevel < pinnedVendorPatchLevel) {
                    throw new GeneralSecurityException("Vendor patch level downgrade detected");
                }
                if (verified.bootPatchLevel < pinnedBootPatchLevel) {
                    throw new GeneralSecurityException("Boot patch level downgrade detected");
                }
                if (verified.appVersion < pinnedAppVersion) {
                    throw new GeneralSecurityException("App version downgraded");
                }
                if (verified.securityLevel != pinnedSecurityLevel) {
                    throw new GeneralSecurityException("Security level mismatch");
                }

                appendVerifiedInformation(teeEnforced, verified);

                final SQLiteStatement update = conn.prepare("UPDATE Devices SET " +
                        "pinnedVerifiedBootKey = ?, verifiedBootHash = ?, " +
                        "pinnedOsVersion = ?, pinnedOsPatchLevel = ?, " +
                        "pinnedVendorPatchLevel = ?, pinnedBootPatchLevel = ?, " +
                        "pinnedAppVersion = ?, pinnedSecurityLevel = ?, userProfileSecure = ?, enrolledFingerprints = ?, " +
                        "accessibility = ?, deviceAdmin = ?, adbEnabled = ?, " +
                        "addUsersWhenLocked = ?, denyNewUsb = ?, oemUnlockAllowed = ?, " +
                        "systemUser = ?, verifiedTimeLast = ? " +
                        "WHERE fingerprint = ?");
                // handle migration to v2 verified boot key fingerprint
                update.bind(1, verifiedBootKey);
                update.bind(2, verified.verifiedBootHash);
                update.bind(3, verified.osVersion);
                update.bind(4, verified.osPatchLevel);
                if (verified.vendorPatchLevel != 0) {
                    update.bind(5, verified.vendorPatchLevel);
                }
                if (verified.bootPatchLevel != 0) {
                    update.bind(6, verified.bootPatchLevel);
                }
                update.bind(7, verified.appVersion);
                update.bind(8, verified.securityLevel); // new field
                update.bind(9, userProfileSecure ? 1 : 0);
                update.bind(10, enrolledFingerprints ? 1 : 0);
                update.bind(11, accessibility ? 1 : 0);
                update.bind(12, deviceAdmin ? (deviceAdminNonSystem ? 2 : 1) : 0);
                update.bind(13, adbEnabled ? 1 : 0);
                update.bind(14, addUsersWhenLocked ? 1 : 0);
                update.bind(15, denyNewUsb ? 1 : 0);
                update.bind(16, oemUnlockAllowed ? 1 : 0);
                if (verified.appVersion >= 14) {
                    update.bind(17, systemUser ? 1 : 0);
                }
                update.bind(18, now);
                update.bind(19, fingerprint);
                update.step();
                update.dispose();
            } else {
                verifySignature(attestationCertificates[0].getPublicKey(), signedMessage, signature);

                final SQLiteStatement insert = conn.prepare("INSERT INTO Devices " +
                        "(fingerprint, pinnedCertificate0, pinnedCertificate1, pinnedCertificate2, " +
                        "pinnedVerifiedBootKey, verifiedBootHash, pinnedOsVersion, pinnedOsPatchLevel, " +
                        "pinnedVendorPatchLevel, pinnedBootPatchLevel, pinnedAppVersion, pinnedSecurityLevel, " +
                        "userProfileSecure, enrolledFingerprints, accessibility, deviceAdmin, " +
                        "adbEnabled, addUsersWhenLocked, denyNewUsb, oemUnlockAllowed, systemUser, " +
                        "verifiedTimeFirst, verifiedTimeLast, userId) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
                insert.bind(1, fingerprint);
                insert.bind(2, attestationCertificates[0].getEncoded());
                insert.bind(3, attestationCertificates[1].getEncoded());
                insert.bind(4, attestationCertificates[2].getEncoded());
                insert.bind(5, verifiedBootKey);
                insert.bind(6, verified.verifiedBootHash);
                insert.bind(7, verified.osVersion);
                insert.bind(8, verified.osPatchLevel);
                if (verified.vendorPatchLevel != 0) {
                    insert.bind(9, verified.vendorPatchLevel);
                }
                if (verified.bootPatchLevel != 0) {
                    insert.bind(10, verified.bootPatchLevel);
                }
                insert.bind(11, verified.appVersion);
                insert.bind(12, verified.securityLevel);
                insert.bind(13, userProfileSecure ? 1 : 0);
                insert.bind(14, enrolledFingerprints ? 1 : 0);
                insert.bind(15, accessibility ? 1 : 0);
                insert.bind(16, deviceAdmin ? (deviceAdminNonSystem ? 2 : 1) : 0);
                insert.bind(17, adbEnabled ? 1 : 0);
                insert.bind(18, addUsersWhenLocked ? 1 : 0);
                insert.bind(19, denyNewUsb ? 1 : 0);
                insert.bind(20, oemUnlockAllowed ? 1 : 0);
                if (verified.appVersion >= 14) {
                    insert.bind(21, systemUser ? 1 : 0);
                }
                insert.bind(22, now);
                insert.bind(23, now);
                insert.bind(24, userId);
                insert.step();
                insert.dispose();

                appendVerifiedInformation(teeEnforced, verified);
            }

            final StringBuilder osEnforced = new StringBuilder();
            osEnforced.append(String.format("Auditor app version: %s\n", verified.appVersion));
            osEnforced.append(String.format("User profile secure: %s\n",
                    toYesNoString(userProfileSecure)));
            osEnforced.append(String.format("Enrolled fingerprints: %s\n",
                    toYesNoString(enrolledFingerprints)));
            osEnforced.append(String.format("Accessibility service(s) enabled: %s\n",
                    toYesNoString(accessibility)));

            final String deviceAdminState;
            if (deviceAdminNonSystem) {
                deviceAdminState = "yes, but only system apps";
            } else if (deviceAdmin) {
                deviceAdminState = "yes, with non-system apps";
            } else {
                deviceAdminState = "no";
            }
            osEnforced.append(String.format("Device administrator(s) enabled: %s\n", deviceAdminState));

            osEnforced.append(String.format("Android Debug Bridge enabled: %s\n",
                    toYesNoString(adbEnabled)));
            osEnforced.append(String.format("Add users from lock screen: %s\n",
                    toYesNoString(addUsersWhenLocked)));
            osEnforced.append(String.format("Disallow new USB peripherals when locked: %s\n",
                    toYesNoString(denyNewUsb)));
            osEnforced.append(String.format("OEM unlocking allowed: %s\n",
                    toYesNoString(oemUnlockAllowed)));
            if (verified.appVersion >= 14) {
                osEnforced.append(String.format("Main user account: %s\n",
                        toYesNoString(systemUser)));
            }

            final String teeEnforcedString = teeEnforced.toString();
            final String osEnforcedString = osEnforced.toString();

            final SQLiteStatement insert = conn.prepare("INSERT INTO Attestations " +
                    "(fingerprint, time, strong, teeEnforced, osEnforced)" +
                    "VALUES (?, ?, ?, ?, ?)");
            insert.bind(1, fingerprint);
            insert.bind(2, now);
            insert.bind(3, hasPersistentKey ? 1 : 0);
            insert.bind(4, teeEnforcedString);
            insert.bind(5, osEnforcedString);
            insert.step();
            insert.dispose();
        } catch (final SQLiteException e) {
            throw new IOException(e);
        } finally {
            conn.dispose();
        }
    }

    static void verifySerialized(final byte[] attestationResult,
            final Cache<ByteBuffer, Boolean> pendingChallenges, final long userId, final boolean paired)
            throws DataFormatException, GeneralSecurityException, IOException {
        final ByteBuffer deserializer = ByteBuffer.wrap(attestationResult);
        final byte version = deserializer.get();
        if (version > PROTOCOL_VERSION) {
            throw new GeneralSecurityException("invalid protocol version: " + version);
        } else if (version < PROTOCOL_VERSION_MINIMUM) {
            throw new GeneralSecurityException("Auditee protocol version too old: " + version);
        }

        final short compressedChainLength = deserializer.getShort();
        final byte[] compressedChain = new byte[compressedChainLength];
        deserializer.get(compressedChain);

        final byte[] chain = new byte[MAX_ENCODED_CHAIN_LENGTH];
        final Inflater inflater = new Inflater(true);
        inflater.setInput(compressedChain);
        inflater.setDictionary(DEFLATE_DICTIONARY_1);
        final int chainLength = inflater.inflate(chain);
        if (!inflater.finished()) {
            throw new GeneralSecurityException("certificate chain is too large");
        }
        inflater.end();
        //Log.d(TAG, "encoded length: " + chainLength + ", compressed length: " + compressedChain.length);

        final ByteBuffer chainDeserializer = ByteBuffer.wrap(chain, 0, chainLength);
        final List<Certificate> certs = new ArrayList<>();
        while (chainDeserializer.hasRemaining()) {
            final short encodedLength = chainDeserializer.getShort();
            final byte[] encoded = new byte[encodedLength];
            chainDeserializer.get(encoded);
            certs.add(generateCertificate(new ByteArrayInputStream(encoded)));
        }
        final Certificate[] certificates = certs.toArray(new Certificate[certs.size() + 1]);

        final byte[] fingerprint = new byte[FINGERPRINT_LENGTH];
        deserializer.get(fingerprint);

        final int osEnforcedFlags = deserializer.getInt();
        if ((osEnforcedFlags & ~OS_ENFORCED_FLAGS_ALL) != 0) {
            //Log.w(TAG, "unknown OS enforced flag set (flags: " + Integer.toBinaryString(osEnforcedFlags) + ")");
        }
        final boolean userProfileSecure = (osEnforcedFlags & OS_ENFORCED_FLAGS_USER_PROFILE_SECURE) != 0;
        final boolean accessibility = (osEnforcedFlags & OS_ENFORCED_FLAGS_ACCESSIBILITY) != 0;
        final boolean deviceAdmin = (osEnforcedFlags & OS_ENFORCED_FLAGS_DEVICE_ADMIN) != 0;
        final boolean deviceAdminNonSystem = (osEnforcedFlags & OS_ENFORCED_FLAGS_DEVICE_ADMIN_NON_SYSTEM) != 0;
        final boolean adbEnabled = (osEnforcedFlags & OS_ENFORCED_FLAGS_ADB_ENABLED) != 0;
        final boolean addUsersWhenLocked = (osEnforcedFlags & OS_ENFORCED_FLAGS_ADD_USERS_WHEN_LOCKED) != 0;
        final boolean enrolledFingerprints = (osEnforcedFlags & OS_ENFORCED_FLAGS_ENROLLED_FINGERPRINTS) != 0;
        final boolean denyNewUsb = (osEnforcedFlags & OS_ENFORCED_FLAGS_DENY_NEW_USB) != 0;
        final boolean oemUnlockAllowed = (osEnforcedFlags & OS_ENFORCED_FLAGS_OEM_UNLOCK_ALLOWED) != 0;
        final boolean systemUser = (osEnforcedFlags & OS_ENFORCED_FLAGS_SYSTEM_USER) != 0;

        if (deviceAdminNonSystem && !deviceAdmin) {
            throw new GeneralSecurityException("invalid device administrator state");
        }

        final int signatureLength = deserializer.remaining();
        final byte[] signature = new byte[signatureLength];
        deserializer.get(signature);

        certificates[certificates.length - 1] =
                generateCertificate(new ByteArrayInputStream(GOOGLE_ROOT_CERTIFICATE.getBytes()));

        deserializer.rewind();
        deserializer.limit(deserializer.capacity() - signature.length);

        verify(fingerprint, pendingChallenges, userId, paired, deserializer.asReadOnlyBuffer(), signature,
                certificates, userProfileSecure, accessibility, deviceAdmin, deviceAdminNonSystem,
                adbEnabled, addUsersWhenLocked, enrolledFingerprints, denyNewUsb, oemUnlockAllowed,
                systemUser);
    }
}