diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 624211c10..fa6114bb3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -102,6 +102,9 @@ android { androidResources { noCompress += listOf("dex") } + defaultConfig { + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } } dependencies { @@ -121,4 +124,10 @@ dependencies { implementation("androidx.camera:camera-camera2:$cameraVersion") implementation("androidx.camera:camera-lifecycle:$cameraVersion") implementation("androidx.camera:camera-view:$cameraVersion") + + // unit tests + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test:runner:1.5.2") + androidTestImplementation("androidx.test:rules:1.5.0") + } diff --git a/app/src/androidTest/java/app/attestation/auditor/ImmutableMapParserTest.java b/app/src/androidTest/java/app/attestation/auditor/ImmutableMapParserTest.java new file mode 100644 index 000000000..e333055cd --- /dev/null +++ b/app/src/androidTest/java/app/attestation/auditor/ImmutableMapParserTest.java @@ -0,0 +1,60 @@ +package app.attestation.auditor; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import org.xmlpull.v1.XmlPullParserException; + +import android.app.Application; +import android.content.Context; +import androidx.test.platform.app.InstrumentationRegistry; + +public class ImmutableMapParserTest { + Context context; + + @Before + public void setUp() { + context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + } + + @Test + public void parseGrapheneResourceMap() throws IOException, XmlPullParserException { + var map = ImmutableMapParser.getImmutableMapResource(context, R.xml.fingerprints_graphene, "fingerprint", + "deviceInfo", + new AttestationProtocol.DeviceInfoParser()); + assertTrue( + map.get("B094E48B27C6E15661223CEFF539CF35E481DEB4E3250331E973AC2C15CAD6CD").name == R.string.device_pixel_2); + } + + @Test + public void parseGrapheneStrongBoxResourceMap() throws IOException, XmlPullParserException { + var map = ImmutableMapParser.getImmutableMapResource(context, R.xml.fingerprints_graphene_strongbox, + "fingerprint", + "deviceInfo", + new AttestationProtocol.DeviceInfoParser()); + assertTrue( + map.get("0F9A9CC8ADE73064A54A35C5509E77994E3AA37B6FB889DD53AF82C3C570C5CF").name == R.string.device_pixel_3); + } + + @Test + public void parseStockResourceMap() throws IOException, XmlPullParserException { + var map = ImmutableMapParser.getImmutableMapResource(context, R.xml.fingerprints_stock, "fingerprint", + "deviceInfo", + new AttestationProtocol.DeviceInfoParser()); + assertTrue( + map.get("5341E6B2646979A70E57653007A1F310169421EC9BDD9F1A5648F75ADE005AF1").name == R.string.device_huawei); + } + + @Test + public void parseStockStrongBoxResourceMap() throws IOException, XmlPullParserException { + var map = ImmutableMapParser.getImmutableMapResource(context, R.xml.fingerprints_stock_strongbox, + "fingerprint", + "deviceInfo", + new AttestationProtocol.DeviceInfoParser()); + assertTrue( + map.get("61FDA12B32ED84214A9CF13D1AFFB7AA80BD8A268A861ED4BB7A15170F1AB00C").name == R.string.device_pixel_3_generic); + } +} diff --git a/app/src/main/java/app/attestation/auditor/AttestationActivity.java b/app/src/main/java/app/attestation/auditor/AttestationActivity.java index c84cec43a..31e81083f 100644 --- a/app/src/main/java/app/attestation/auditor/AttestationActivity.java +++ b/app/src/main/java/app/attestation/auditor/AttestationActivity.java @@ -52,8 +52,6 @@ public class AttestationActivity extends AppCompatActivity { private static final String TAG = "AttestationActivity"; - private static final String TUTORIAL_URL = "https://" + RemoteVerifyJob.DOMAIN + "/tutorial"; - private static final String STATE_AUDITEE_PAIRING = "auditee_pairing"; private static final String STATE_AUDITEE_SERIALIZED_ATTESTATION = "auditee_serialized_attestation"; private static final String STATE_AUDITOR_CHALLENGE = "auditor_challenge"; @@ -114,7 +112,8 @@ private enum Stage { stage = Stage.None; Log.d(TAG, "account: " + contents); final String[] values = contents.split(" "); - if (values.length < 4 || !RemoteVerifyJob.DOMAIN.equals(values[0])) { + final String domain = getString(R.string.base_domain); + if (values.length < 4 || !domain.equals(values[0])) { snackbar.setText(R.string.scanned_invalid_account_qr_code).show(); return; } @@ -507,6 +506,10 @@ public boolean onPrepareOptionsMenu(final Menu menu) { return true; } + private String tutorialUrl() { + return getString(R.string.base_domain) + "/tutorial"; + } + @Override @SuppressLint("InlinedApi") public boolean onOptionsItemSelected(final MenuItem item) { @@ -584,7 +587,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { } return true; } else if (itemId == R.id.action_help) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(TUTORIAL_URL))); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(tutorialUrl()))); return true; } return super.onOptionsItemSelected(item); diff --git a/app/src/main/java/app/attestation/auditor/AttestationProtocol.java b/app/src/main/java/app/attestation/auditor/AttestationProtocol.java index 27ebad3f4..67f98f163 100644 --- a/app/src/main/java/app/attestation/auditor/AttestationProtocol.java +++ b/app/src/main/java/app/attestation/auditor/AttestationProtocol.java @@ -10,6 +10,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.content.res.XmlResourceParser; import android.os.Build; import android.os.UserManager; import android.provider.Settings; @@ -30,6 +31,9 @@ import com.google.common.io.ByteStreams; import com.google.common.primitives.Bytes; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -132,29 +136,41 @@ class AttestationProtocol { // 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 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. + // 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: // - // For backwards compatibility the Auditor device sends its maximum supported version, and + // For backwards compatibility the Auditor device sends its maximum supported + // version, and // the Auditee uses the highest version it supports. // - // Compression is done with raw DEFLATE (no zlib wrapper) with a preset dictionary generated from + // 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[] compressedChain { [short encodedCertificateLength, byte[] + // encodedCertificate] } // byte[] fingerprint (length: FINGERPRINT_LENGTH) // int osEnforcedFlags // } @@ -164,41 +180,65 @@ class AttestationProtocol { // // n/a // - // 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 an 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 + // 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 an 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 + // 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 + // 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 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 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 + // 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. private static final byte PROTOCOL_VERSION = 4; private static final byte PROTOCOL_VERSION_MINIMUM = 4; @@ -218,8 +258,7 @@ class AttestationProtocol { 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 | + 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 | @@ -233,12 +272,9 @@ class AttestationProtocol { private static final String AUDITOR_APP_PACKAGE_NAME_RELEASE = "app.attestation.auditor"; private static final String AUDITOR_APP_PACKAGE_NAME_PLAY = "app.attestation.auditor.play"; private static final String AUDITOR_APP_PACKAGE_NAME_DEBUG = "app.attestation.auditor.debug"; - private static final String AUDITOR_APP_SIGNATURE_DIGEST_RELEASE = - "990E04F0864B19F14F84E0E432F7A393F297AB105A22C1E1B10B442A4A62C42C"; - private static final String AUDITOR_APP_SIGNATURE_DIGEST_PLAY = - "075335BD7B54C965222B5284D2A1FDEF1198AE45EC7B09A4934287A0E3A243C7"; - private static final String AUDITOR_APP_SIGNATURE_DIGEST_DEBUG = - "17727D8B61D55A864936B1A7B4A2554A15151F32EBCF44CDAA6E6C3258231890"; + private static final String AUDITOR_APP_SIGNATURE_DIGEST_RELEASE = "990E04F0864B19F14F84E0E432F7A393F297AB105A22C1E1B10B442A4A62C42C"; + private static final String AUDITOR_APP_SIGNATURE_DIGEST_PLAY = "075335BD7B54C965222B5284D2A1FDEF1198AE45EC7B09A4934287A0E3A243C7"; + private static final String AUDITOR_APP_SIGNATURE_DIGEST_DEBUG = "17727D8B61D55A864936B1A7B4A2554A15151F32EBCF44CDAA6E6C3258231890"; private static final byte AUDITOR_APP_VARIANT_RELEASE = 0; private static final byte AUDITOR_APP_VARIANT_PLAY = 1; private static final byte AUDITOR_APP_VARIANT_DEBUG = 2; @@ -251,7 +287,100 @@ class AttestationProtocol { // Split displayed fingerprint into groups of 4 characters private static final int FINGERPRINT_SPLIT_INTERVAL = 4; - private static class DeviceInfo { + static class DeviceInfoParser implements XmlMapElemParser { + + private static ImmutableMap getDeviceMap(Context context, int resMap) + throws IOException, XmlPullParserException { + return ImmutableMapParser.getImmutableMapResource(context, resMap, "fingerprint", "deviceInfo", + new DeviceInfoParser()); + } + + @Override + public String parseKey(Context context, XmlResourceParser parser) { + if (parser.getName().equals("fingerprint")) { + try { + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.TEXT) { + eventType = parser.next(); + } + + var key = parser.getText(); + Log.d("AttestationProtocol", "key: " + key); + return key; + } catch (IOException | XmlPullParserException e) { + throw new IllegalArgumentException("Invalid data in xml resource map: ", e); + } + } else { + throw new IllegalStateException("Trying to parse a key from an invalid node: " + parser.getName()); + } + } + + @Override + public DeviceInfo parseValue(Context context, XmlResourceParser parser) { + int name = 0, osName = 0, attestationVersion = 0, keymasterVersion = 0; + boolean rollbackResistant = false, perUserEncryption = false, enforceStrongBox = false; + + if (parser.getName().equals("deviceInfo")) { + try { + int eventType; + String tagName = "deviceInfo"; + + do { + eventType = parser.next(); + if (eventType == XmlPullParser.TEXT) { + Log.d("AttestationProtocol", "found tag text: " + parser.getText()); + switch (tagName) { + case "name": + name = context.getResources().getIdentifier(parser.getText(), "string", + context.getPackageName()); + break; + case "attestationVersion": + attestationVersion = Integer.parseInt(parser.getText()); + break; + case "keymasterVersion": + keymasterVersion = Integer.parseInt(parser.getText()); + break; + case "rollbackResistant": + rollbackResistant = Boolean.parseBoolean(parser.getText()); + break; + case "perUserEncryption": + perUserEncryption = Boolean.parseBoolean(parser.getText()); + break; + case "enforceStrongBox": + enforceStrongBox = Boolean.parseBoolean(parser.getText()); + break; + case "osName": + Log.d("AttestationProtocol", "osName: " + parser.getText()); + osName = context.getResources().getIdentifier(parser.getText(), "string", + context.getPackageName()); + break; + } + } else if (eventType == XmlPullParser.START_TAG || eventType == XmlPullParser.END_TAG) { + tagName = parser.getName(); + } + } while (eventType != XmlPullParser.END_TAG || !tagName.equals("deviceInfo")); + Log.d("AttestationProtocol", "finished parsing DeviceInfo"); + } catch (IOException | XmlPullParserException e) { + throw new IllegalArgumentException("Invalid data in xml resource map: ", e); + } + } else { + throw new IllegalStateException("Trying to parse DeviceInfo from an invalid node: " + parser.getName()); + } + + var deviceInfo = new DeviceInfo(name, attestationVersion, keymasterVersion, rollbackResistant, + perUserEncryption, + enforceStrongBox, osName); + Log.d("AttestationProtocol", + "DeviceInfo { name: " + deviceInfo.name + ", attestationVersion: " + attestationVersion + + ", keymasterVersion: " + keymasterVersion + ", rollbackResistant: " + rollbackResistant + + ", perUserEncryption: " + perUserEncryption + ", enforceStrongBox: " + enforceStrongBox + + ", osName: " + osName + " }"); + return deviceInfo; + } + + } + + static class DeviceInfo { final int name; final int attestationVersion; final int keymasterVersion; @@ -293,7 +422,8 @@ private static class DeviceInfo { "SM-N970U", "SM-N975U").contains(Build.MODEL); - // Pixel 6, Pixel 6 Pro and Pixel 6a forgot to declare the attest key feature when it shipped in Android 12 + // Pixel 6, Pixel 6 Pro and Pixel 6a forgot to declare the attest key feature + // when it shipped in Android 12 private static final boolean alwaysHasAttestKey = ImmutableSet.of( "Pixel 6", "Pixel 6 Pro", @@ -308,242 +438,6 @@ private static class DeviceInfo { R.string.device_sm_n975u, R.string.device_sm_t510); - private static final ImmutableMap fingerprintsCustomOS = ImmutableMap - .builder() - // GrapheneOS - .put("B094E48B27C6E15661223CEFF539CF35E481DEB4E3250331E973AC2C15CAD6CD", - new DeviceInfo(R.string.device_pixel_2, 2, 3, true, true, false, R.string.os_graphene)) - .put("B6851E9B9C0EBB7185420BD0E79D20A84CB15AB0B018505EFFAA4A72B9D9DAC7", - new DeviceInfo(R.string.device_pixel_2_xl, 2, 3, true, true, false, R.string.os_graphene)) - .put("0F9A9CC8ADE73064A54A35C5509E77994E3AA37B6FB889DD53AF82C3C570C5CF", - new DeviceInfo(R.string.device_pixel_3, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("06DD526EE9B1CB92AA19D9835B68B4FF1A48A3AD31D813F27C9A7D6C271E9451", - new DeviceInfo(R.string.device_pixel_3_xl, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("8FF8B9B4F831114963669E04EA4F849F33F3744686A0B33B833682746645ABC8", - new DeviceInfo(R.string.device_pixel_3a, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("91943FAA75DCB6392AE87DA18CA57D072BFFB80BC30F8FAFC7FFE13D76C5736E", - new DeviceInfo(R.string.device_pixel_3a_xl, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("80EF268700EE42686F779A47B4A155FE1FFC2EEDF836B4803CAAB8FA61439746", - new DeviceInfo(R.string.device_pixel_4, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("3F15FDCB82847FED97427CE00563B8F9FF34627070DE5FDB17ACA7849AB98CC8", - new DeviceInfo(R.string.device_pixel_4_xl, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("9F2454A1657B1B5AD7F2336B39A2611F7A40B2E0DDFD0D6553A359605928DF29", - new DeviceInfo(R.string.device_pixel_4a, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("DCEC2D053D3EC4F1C9BE414AA07E4D7D7CBD12040AD2F8831C994A83A0536866", - new DeviceInfo(R.string.device_pixel_4a_5g, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("36A99EAB7907E4FB12A70E3C41C456BCBE46C13413FBFE2436ADEE2B2B61120F", - new DeviceInfo(R.string.device_pixel_5, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("0ABDDEDA03B6CE10548C95E0BEA196FAA539866F929BCDF7ECA84B4203952514", - new DeviceInfo(R.string.device_pixel_5a, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("F0A890375D1405E62EBFD87E8D3F475F948EF031BBF9DDD516D5F600A23677E8", - new DeviceInfo(R.string.device_pixel_6, 100, 100, false /* uses new API */, true, true, R.string.os_graphene)) - .put("439B76524D94C40652CE1BF0D8243773C634D2F99BA3160D8D02AA5E29FF925C", - new DeviceInfo(R.string.device_pixel_6_pro, 100, 100, false /* uses new API */, true, true, R.string.os_graphene)) - .put("08C860350A9600692D10C8512F7B8E80707757468E8FBFEEA2A870C0A83D6031", - new DeviceInfo(R.string.device_pixel_6a, 100, 100, false /* uses new API */, true, true, R.string.os_graphene)) - .put("3EFE5392BE3AC38AFB894D13DE639E521675E62571A8A9B3EF9FC8C44FD17FA1", - new DeviceInfo(R.string.device_pixel_7, 200, 200, false /* uses new API */, true, false, R.string.os_graphene)) - .put("BC1C0DD95664604382BB888412026422742EB333071EA0B2D19036217D49182F", - new DeviceInfo(R.string.device_pixel_7_pro, 200, 200, false /* uses new API */, true, false, R.string.os_graphene)) - .build(); - private static final ImmutableMap fingerprintsStock = ImmutableMap - .builder() - .put("5341E6B2646979A70E57653007A1F310169421EC9BDD9F1A5648F75ADE005AF1", - new DeviceInfo(R.string.device_huawei, 2, 3, false, true, false, R.string.os_stock)) - .put("7E2E8CC82A77CA74554457E5DF3A3ED82E7032B3182D17FE17919BC6E989FF09", - new DeviceInfo(R.string.device_huawei_honor_7a_pro, 2, 3, false, true, false, R.string.os_stock)) - .put("DFC2920C81E136FDD2A510478FDA137B262DC51D449EDD7D0BDB554745725CFE", - new DeviceInfo(R.string.device_nokia, 2, 3, true, true, false, R.string.os_stock)) - .put("4D790FA0A5FE81D6B352B90AFE430684D9BC817518CD24C50E6343395F7C51F2", - new DeviceInfo(R.string.device_nokia_3_1, 2, 3, false, false, false, R.string.os_stock)) - .put("893A17FD918235DB2865F7F6439EB0134A45B766AA452E0675BAC6CFB5A773AA", - new DeviceInfo(R.string.device_nokia_7_1, 2, 3, true, true, false, R.string.os_stock)) - .put("6101853DFF451FAE5B137DF914D5E6C15C659337F2C405AC50B513A159071958", - new DeviceInfo(R.string.device_oneplus_6_a6003, 2, 3, true, true, false, R.string.os_stock)) - .put("1B90B7D1449D697FB2732A7D2DFA405D587254593F5137F7B6E64F7A0CE03BFD", - new DeviceInfo(R.string.device_oneplus_6t_a6013, 3, 4, false /* uses new API */, true, false, R.string.os_stock)) - .put("4B9201B11685BE6710E2B2BA8482F444E237E0C8A3D1F7F447FE29C37CECC559", - new DeviceInfo(R.string.device_oneplus_7_pro_gm1913, 3, 4, false /* uses new API */, true, false, R.string.os_stock)) - .put("1962B0538579FFCE9AC9F507C46AFE3B92055BAC7146462283C85C500BE78D82", - new DeviceInfo(R.string.device_pixel_2, 2, 3, true, true, false, R.string.os_stock)) - .put("171616EAEF26009FC46DC6D89F3D24217E926C81A67CE65D2E3A9DC27040C7AB", - new DeviceInfo(R.string.device_pixel_2_xl, 2, 3, true, true, false, R.string.os_stock)) - .put("61FDA12B32ED84214A9CF13D1AFFB7AA80BD8A268A861ED4BB7A15170F1AB00C", - new DeviceInfo(R.string.device_pixel_3_generic, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("E75B86C52C7496255A95FB1E2B1C044BFA9D5FE34DD1E4EEBD752EEF0EA89875", - new DeviceInfo(R.string.device_pixel_3a_generic, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("AE6316B4753C61F5855B95B9B98484AF784F2E83648D0FCC8107FCA752CAEA34", - new DeviceInfo(R.string.device_pixel_4_generic, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("879CD3F18EA76E244D4D4AC3BCB9C337C13B4667190B19035AFE2536550050F1", - new DeviceInfo(R.string.device_pixel_4a, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("88265D85BA9E1E2F6036A259D880D2741031ACA445840137395B6D541C0FC7FC", - new DeviceInfo(R.string.device_pixel_5_generic, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("1DD694CE00BF131AD61CEB576B7DCC41CF7F9B2C418F4C12B2B8F3E9A1EA911D", - new DeviceInfo(R.string.device_pixel_5a, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("0F6E75C80183B5DEC074B0054D4271E99389EBE4B136B0819DE1F150BA0FF9D7", - new DeviceInfo(R.string.device_pixel_6, 100, 100, false /* uses new API */, true, true, R.string.os_stock)) - .put("42ED1BCA352FABD428F34E8FCEE62776F4CB2C66E06F82E5A59FF4495267BFC2", - new DeviceInfo(R.string.device_pixel_6_pro, 100, 100, false /* uses new API */, true, true, R.string.os_stock)) - .put("9AC4174153D45E4545B0F49E22FE63273999B6AC1CB6949C3A9F03EC8807EEE9", - new DeviceInfo(R.string.device_pixel_6a, 100, 100, false /* uses new API */, true, true, R.string.os_stock)) - .put("8B2C4CD539F5075E8E7CF212ADB3DB0413FBD77D321199C73D5A473C51F2E10D", - new DeviceInfo(R.string.device_pixel_7, 200, 200, false /* uses new API */, true, false, R.string.os_stock)) - .put("26AC4C60BEB1E378357CAD0C3061347AF8DF6FBABBB0D8CEA2445855EE01E368", - new DeviceInfo(R.string.device_pixel_7_pro, 200, 200, false /* uses new API */, true, false, R.string.os_stock)) - .put("72376CAACF11726D4922585732429FB97D0D1DD69F0D2E0770B9E61D14ADDE65", - new DeviceInfo(R.string.device_sm_a705fn, 3, 4, false /* uses new API */, true, false, R.string.os_stock)) - .put("33D9484FD512E610BCF00C502827F3D55A415088F276C6506657215E622FA770", - new DeviceInfo(R.string.device_sm_g960f, 1, 2, false, false, false, R.string.os_stock)) - .put("266869F7CF2FB56008EFC4BE8946C8F84190577F9CA688F59C72DD585E696488", - new DeviceInfo(R.string.device_sm_g960_na, 1, 2, false, false, false, R.string.os_stock)) - .put("12E8460A7BAF709F3B6CF41C7E5A37C6EB4D11CB36CF7F61F7793C8DCDC3C2E4", - new DeviceInfo(R.string.device_sm_g9600, 1, 2, false, false, false, R.string.os_stock)) - .put("D1C53B7A931909EC37F1939B14621C6E4FD19BF9079D195F86B3CEA47CD1F92D", - new DeviceInfo(R.string.device_sm_g965f, 1, 2, false, false, false, R.string.os_stock)) - .put("A4A544C2CFBAEAA88C12360C2E4B44C29722FC8DBB81392A6C1FAEDB7BF63010", - new DeviceInfo(R.string.device_sm_g965_msm, 1, 2, false, false, false, R.string.os_stock)) - .put("9D77474FA4FEA6F0B28636222FBCEE2BB1E6FF9856C736C85B8EA6E3467F2BBA", - new DeviceInfo(R.string.device_sm_g970f, 3, 4, false /* uses new API */, true, false, R.string.os_stock)) - .put("08B2B5C6EC8F54C00C505756E1EF516BB4537B2F02D640410D287A43FCF92E3F", - new DeviceInfo(R.string.device_sm_g975f, 3, 4, false /* uses new API */, true, false, R.string.os_stock)) - .put("F0FC0AF47D3FE4F27D79CF629AD6AC42AA1EEDE0A29C0AE109A91BBD1E7CD76D", - new DeviceInfo(R.string.device_sm_j260a, 1, 2, false, false, false, R.string.os_stock)) - .put("410102030405060708090001020304050607080900010203040506070809005A", - new DeviceInfo(R.string.device_sm_j260f, 1, 2, false, false, false, R.string.os_stock)) - .put("D6B902D9E77DFC0FB3627FFEFA6D05405932EBB3A6ED077874B5E2A0CCBDB632", - new DeviceInfo(R.string.device_sm_j260t1, 1, 2, false, false, false, R.string.os_stock)) - .put("4558C1AFB30D1B46CB93F85462BC7D7FCF70B0103B9DBB0FE96DD828F43F29FC", - new DeviceInfo(R.string.device_sm_j337a, 1, 2, false, false, false, R.string.os_stock)) - .put("45E3AB5D61A03915AE10BF0465B186CB5D9A2FB6A46BEFAA76E4483BBA5A358D", - new DeviceInfo(R.string.device_sm_j337t, 1, 2, false, false, false, R.string.os_stock)) - .put("D95279A8F2E832FD68D919DBF33CFE159D5A1179686DB0BD2D7BBBF2382C4DD3", - new DeviceInfo(R.string.device_sm_j720f, 1, 2, false, false, false, R.string.os_stock)) - .put("BB053A5F64D3E3F17C4611340FF2BBE2F605B832A9FA412B2C87F2A163ECE2FB", - new DeviceInfo(R.string.device_sm_j737t1, 1, 2, false, false, false, R.string.os_stock)) - .put("4E0570011025D01386D057B2B382969F804DCD19E001344535CF0CFDB8AD7CFE", - new DeviceInfo(R.string.device_sm_m205f, 1, 2, false, false, false, R.string.os_stock)) - .put("2A7E4954C9F703F3AC805AC660EA1727B981DB39B1E0F41E4013FA2586D3DF7F", - new DeviceInfo(R.string.device_sm_n960f, 1, 2, false, false, false, R.string.os_stock)) - .put("173ACFA8AE9EDE7BBD998F45A49231F3A4BDDF0779345732E309446B46B5641B", - new DeviceInfo(R.string.device_sm_n960u, 1, 2, false, false, false, R.string.os_stock)) - .put("E94BC43B97F98CD10C22CD9D8469DBE621116ECFA624FE291A1D53CF3CD685D1", - new DeviceInfo(R.string.device_sm_n970f, 3, 4, false /* uses new API */, true, false, R.string.os_stock)) - .put("466011C44BBF883DB38CF96617ED35C796CE2552C5357F9230258329E943DB70", - new DeviceInfo(R.string.device_sm_n970u, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("52946676088007755EB586B3E3F3E8D3821BE5DF73513E6C13640507976420E6", - new DeviceInfo(R.string.device_sm_n975u, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("F3688C02D9676DEDB6909CADE364C271901FD66EA4F691AEB8B8921195E469C5", - new DeviceInfo(R.string.device_sm_s367vl, 1, 2, false, false, false, R.string.os_stock)) - .put("106592D051E54388C6E601DFD61D59EB1674A8B93216C65C5B3E1830B73D3B82", - new DeviceInfo(R.string.device_sm_t510, 3, 4, false /* uses new API */, true, false, R.string.os_stock)) - .put("87790149AED63553B768456AAB6DAAD5678CD87BDEB2BF3649467085349C34E0", - new DeviceInfo(R.string.device_sm_t835, 1, 2, false, false, false, R.string.os_stock)) - .put("4285AD64745CC79B4499817F264DC16BF2AF5163AF6C328964F39E61EC84693E", - new DeviceInfo(R.string.device_sony_xperia_xa2, 2, 3, true, true, false, R.string.os_stock)) - .put("54A9F21E9CFAD3A2D028517EF333A658302417DB7FB75E0A109A019646CC5F39", - new DeviceInfo(R.string.device_sony_xperia_xz1, 2, 3, true, true, false, R.string.os_stock)) - .put("BC3B5E121974113939B8A2FE758F9B923F1D195F038D2FD1C04929F886E83BB5", - new DeviceInfo(R.string.device_sony_xperia_xz2, 2, 3, false, true, false, R.string.os_stock)) - .put("94B8B4E3260B4BF8211A02CF2F3DE257A127CFFB2E4047D5580A752A5E253DE0", - new DeviceInfo(R.string.device_sony_xperia_xz2_compact, 2, 3, true, true, false, R.string.os_stock)) - .put("728800FEBB119ADD74519618AFEDB715E1C39FE08A4DE37D249BF54ACF1CE00F", - new DeviceInfo(R.string.device_blackberry_key2, 2, 3, true, true, false, R.string.os_stock)) - .put("1194659B40EA291245E54A3C4EC4AA5B7077BD244D65C7DD8C0A2DBB9DB1FB35", - new DeviceInfo(R.string.device_bq_aquaris_x2_pro, 2, 3, true, false, false, R.string.os_stock)) - .put("A9C6758D509600D0EB94FA8D2BF6EE7A6A6097F0CCEF94A755DDE065AA1AA1B0", - new DeviceInfo(R.string.device_xiaomi_mi_a2, 2, 3, true, false, false, R.string.os_stock)) - .put("6FA710B639848C9D47378937A1AFB1B6A52DDA738BEB6657E2AE70A15B40541A", - new DeviceInfo(R.string.device_xiaomi_mi_a2_lite, 2, 3, true, false, false, R.string.os_stock)) - .put("84BC8445A29B5444A2D1629C9774C8626DAFF3574D865EC5067A78FAEC96B013", - new DeviceInfo(R.string.device_xiaomi_mi_9, 3, 4, false /* uses new API */, true, false, R.string.os_stock)) - .put("1CC39488D2F85DEE0A8E0903CDC4124CFDF2BE2531ED6060B678057ED2CB89B4", - new DeviceInfo(R.string.device_htc, 2, 3, true, false, false, R.string.os_stock)) - .put("80BAB060807CFFA45D4747DF1AD706FEE3AE3F645F80CF14871DDBE27E14C30B", - new DeviceInfo(R.string.device_moto_g7, 3, 4, false /* uses new API */, true, false, R.string.os_stock)) - .put("C2224571C9CD5C89200A7311B1E37AA9CF751E2E19753E8D3702BCA00BE1D42C", - new DeviceInfo(R.string.device_motorola_one_vision, 2, 3, false, true, false, R.string.os_stock)) - .put("1F6D98D1B0E1F1CE1C872BD36C668F9DFDBE0D47594789E1540DF4E6198F657D", - new DeviceInfo(R.string.device_vivo_1807, 2, 3, true, false, false, R.string.os_stock)) - .put("C55635636999E9D0A0588D24402256B7F9F3AEE07B4F7E4E003F09FF0190AFAE", - new DeviceInfo(R.string.device_revvl_2, 2, 3, false, false, false, R.string.os_stock)) - .put("341C50D577DC5F3D5B46E8BFA22C22D1E5FC7D86D4D860E70B89222A7CBFC893", - new DeviceInfo(R.string.device_oppo_cph1831, 2, 3, true, false, false, R.string.os_stock)) - .put("41BF0A26BB3AFDCCCC40F7B685083522EB5BF1C492F0EC4847F351265313CB07", - new DeviceInfo(R.string.device_oppo_cph1903, 2, 3, true, false, false, R.string.os_stock)) - .put("7E19E217072BE6CB7A4C6F673FD3FB62DC51B3E204E7475838747947A3920DD8", - new DeviceInfo(R.string.device_oppo_cph1909, 2, 3, false, false, false, R.string.os_stock)) - .put("0D5F986943D0CE0D4F9783C27EEBE175BE359927DB8B6546B667279A81133C3C", - new DeviceInfo(R.string.device_lg_q710al, 2, 3, false, false, false, R.string.os_stock)) - .put("D20078F2AF2A7D3ECA3064018CB8BD47FBCA6EE61ABB41BA909D3C529CB802F4", - new DeviceInfo(R.string.device_lm_q720, 3, 4, false /* uses new API */, false, false, R.string.os_stock)) - .put("54EC644C21FD8229E3B0066513337A8E2C8EF3098A3F974B6A1CFE456A683DAE", - new DeviceInfo(R.string.device_rmx1941, 2, 3, false, true, false, R.string.os_stock)) - .build(); - - private static final ImmutableMap fingerprintsStrongBoxCustomOS = ImmutableMap - .builder() - // GrapheneOS - .put("0F9A9CC8ADE73064A54A35C5509E77994E3AA37B6FB889DD53AF82C3C570C5CF", - new DeviceInfo(R.string.device_pixel_3, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("06DD526EE9B1CB92AA19D9835B68B4FF1A48A3AD31D813F27C9A7D6C271E9451", - new DeviceInfo(R.string.device_pixel_3_xl, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("73D6C63A07610404FE16A4E07DD24E41A70D331E9D3EF7BBA2D087E4761EB63A", - new DeviceInfo(R.string.device_pixel_3a, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("3F36E3482E1FF82986576552CB4FD08AF09F8B09D3832314341E04C42D2919A4", - new DeviceInfo(R.string.device_pixel_3a_xl, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("80EF268700EE42686F779A47B4A155FE1FFC2EEDF836B4803CAAB8FA61439746", - new DeviceInfo(R.string.device_pixel_4, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("3F15FDCB82847FED97427CE00563B8F9FF34627070DE5FDB17ACA7849AB98CC8", - new DeviceInfo(R.string.device_pixel_4_xl, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("9F2454A1657B1B5AD7F2336B39A2611F7A40B2E0DDFD0D6553A359605928DF29", - new DeviceInfo(R.string.device_pixel_4a, 3, 4, false /* uses new API */, true, true, R.string.os_graphene)) - .put("DCEC2D053D3EC4F1C9BE414AA07E4D7D7CBD12040AD2F8831C994A83A0536866", - new DeviceInfo(R.string.device_pixel_4a_5g, 4, 41, false /* uses new API */, true, true, R.string.os_graphene)) - .put("36A99EAB7907E4FB12A70E3C41C456BCBE46C13413FBFE2436ADEE2B2B61120F", - new DeviceInfo(R.string.device_pixel_5, 4, 41, false /* uses new API */, true, true, R.string.os_graphene)) - .put("0ABDDEDA03B6CE10548C95E0BEA196FAA539866F929BCDF7ECA84B4203952514", - new DeviceInfo(R.string.device_pixel_5a, 4, 41, false /* uses new API */, true, true, R.string.os_graphene)) - .put("F0A890375D1405E62EBFD87E8D3F475F948EF031BBF9DDD516D5F600A23677E8", - new DeviceInfo(R.string.device_pixel_6, 100, 100, false /* uses new API */, true, true, R.string.os_graphene)) - .put("439B76524D94C40652CE1BF0D8243773C634D2F99BA3160D8D02AA5E29FF925C", - new DeviceInfo(R.string.device_pixel_6_pro, 100, 100, false /* uses new API */, true, true, R.string.os_graphene)) - .put("08C860350A9600692D10C8512F7B8E80707757468E8FBFEEA2A870C0A83D6031", - new DeviceInfo(R.string.device_pixel_6a, 100, 100, false /* uses new API */, true, true, R.string.os_graphene)) - .put("3EFE5392BE3AC38AFB894D13DE639E521675E62571A8A9B3EF9FC8C44FD17FA1", - new DeviceInfo(R.string.device_pixel_7, 100, 100, false /* uses new API */, true, false, R.string.os_graphene)) - .put("BC1C0DD95664604382BB888412026422742EB333071EA0B2D19036217D49182F", - new DeviceInfo(R.string.device_pixel_7_pro, 100, 100, false /* uses new API */, true, false, R.string.os_graphene)) - .build(); - private static final ImmutableMap fingerprintsStrongBoxStock = ImmutableMap - .builder() - .put("61FDA12B32ED84214A9CF13D1AFFB7AA80BD8A268A861ED4BB7A15170F1AB00C", - new DeviceInfo(R.string.device_pixel_3_generic, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("8CA89AF1A6DAA74B00810849356DE929CFC4498EF36AF964757BDE8A113BF46D", - new DeviceInfo(R.string.device_pixel_3a_generic, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("AE6316B4753C61F5855B95B9B98484AF784F2E83648D0FCC8107FCA752CAEA34", - new DeviceInfo(R.string.device_pixel_4_generic, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("879CD3F18EA76E244D4D4AC3BCB9C337C13B4667190B19035AFE2536550050F1", - new DeviceInfo(R.string.device_pixel_4a, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("88265D85BA9E1E2F6036A259D880D2741031ACA445840137395B6D541C0FC7FC", - new DeviceInfo(R.string.device_pixel_5_generic, 4, 41, false /* uses new API */, true, true, R.string.os_stock)) - .put("1DD694CE00BF131AD61CEB576B7DCC41CF7F9B2C418F4C12B2B8F3E9A1EA911D", - new DeviceInfo(R.string.device_pixel_5a, 4, 41, false /* uses new API */, true, true, R.string.os_stock)) - .put("0F6E75C80183B5DEC074B0054D4271E99389EBE4B136B0819DE1F150BA0FF9D7", - new DeviceInfo(R.string.device_pixel_6, 100, 100, false /* uses new API */, true, true, R.string.os_stock)) - .put("42ED1BCA352FABD428F34E8FCEE62776F4CB2C66E06F82E5A59FF4495267BFC2", - new DeviceInfo(R.string.device_pixel_6_pro, 100, 100, false /* uses new API */, true, true, R.string.os_stock)) - .put("9AC4174153D45E4545B0F49E22FE63273999B6AC1CB6949C3A9F03EC8807EEE9", - new DeviceInfo(R.string.device_pixel_6a, 100, 100, false /* uses new API */, true, true, R.string.os_stock)) - .put("8B2C4CD539F5075E8E7CF212ADB3DB0413FBD77D321199C73D5A473C51F2E10D", - new DeviceInfo(R.string.device_pixel_7, 100, 100, false /* uses new API */, true, false, R.string.os_stock)) - .put("26AC4C60BEB1E378357CAD0C3061347AF8DF6FBABBB0D8CEA2445855EE01E368", - new DeviceInfo(R.string.device_pixel_7_pro, 100, 100, false /* uses new API */, true, false, R.string.os_stock)) - .put("3D3DEB132A89551D0A700D230BABAE4E3E80E3C7926ACDD7BAEDF9B57AD316D0", - new DeviceInfo(R.string.device_sm_n970u, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .put("9AC63842137D92C119A1B1BE2C9270B9EBB6083BBE6350B7823571942B5869F0", - new DeviceInfo(R.string.device_sm_n975u, 3, 4, false /* uses new API */, true, true, R.string.os_stock)) - .build(); - private static byte[] getChallengeIndex(final Context context) { final SharedPreferences global = PreferenceManager.getDefaultSharedPreferences(context); final String challengeIndexSerialized = global.getString(KEY_CHALLENGE_INDEX, null); @@ -566,7 +460,7 @@ private static byte[] getChallenge() { } static byte[] getChallengeMessage(final Context context) { - return Bytes.concat(new byte[]{PROTOCOL_VERSION}, getChallengeIndex(context), getChallenge()); + return Bytes.concat(new byte[] { PROTOCOL_VERSION }, getChallengeIndex(context), getChallenge()); } private static byte[] getFingerprint(final Certificate certificate) @@ -624,10 +518,70 @@ private static X509Certificate generateCertificate(final Resources resources, fi } } - private static Verified verifyStateless(final Certificate[] certificates, + private static Verified verifyStateless(Context context, + final Certificate[] certificates, final byte[] challenge, final boolean hasPersistentKey, final Certificate root0, final Certificate root1, final Certificate root2) throws GeneralSecurityException { + ImmutableMap fingerprintsGraphene, fingerprintsGrapheneStrongBox, fingerprintsStock, + fingerprintsStockStrongBox, fingerprintsCustomOs, fingerprintsCustomOsStrongBox; + try { + fingerprintsGraphene = DeviceInfoParser.getDeviceMap(context, + R.xml.fingerprints_graphene); + } catch (IOException | XmlPullParserException e) { + throw new GeneralSecurityException("failed to parse GrapheneOS fingerprints"); + } + + try { + fingerprintsGrapheneStrongBox = DeviceInfoParser.getDeviceMap(context, + R.xml.fingerprints_graphene_strongbox); + } catch (IOException | XmlPullParserException e) { + throw new GeneralSecurityException("failed to parse GrapheneOS StrongBox fingerprints"); + } + + try { + fingerprintsStock = DeviceInfoParser.getDeviceMap(context, + R.xml.fingerprints_stock); + } catch (IOException | XmlPullParserException e) { + throw new GeneralSecurityException("failed to parse Stock fingerprints"); + } + + try { + fingerprintsStockStrongBox = DeviceInfoParser.getDeviceMap(context, + R.xml.fingerprints_stock_strongbox); + } catch (IOException | XmlPullParserException e) { + throw new GeneralSecurityException("failed to parse Stock StrongBox fingerprints"); + } + + Resources res = context.getResources(); + + int custom_os_fingerprints_res = res.getIdentifier("os_custom_fingerprints_res", "string", + context.getPackageName()); + if (custom_os_fingerprints_res == 0) { + fingerprintsCustomOs = ImmutableMap.builder().build(); + } else { + try { + fingerprintsCustomOs = DeviceInfoParser.getDeviceMap(context, custom_os_fingerprints_res); + } catch (IOException | XmlPullParserException e) { + Log.e("AttestationProtocol", "failed to parse provided fingerprints for custom OS", e); + fingerprintsCustomOs = ImmutableMap.builder().build(); + } + } + + int custom_os_fingerprints_strongbox_res = res.getIdentifier("os_custom_fingerprints_strongbox_res", "string", + context.getPackageName()); + if (custom_os_fingerprints_res == 0) { + fingerprintsCustomOsStrongBox = ImmutableMap.builder().build(); + } else { + try { + fingerprintsCustomOsStrongBox = DeviceInfoParser.getDeviceMap(context, + custom_os_fingerprints_strongbox_res); + } catch (IOException | XmlPullParserException e) { + Log.e("AttestationProtocol", "failed to parse provided StrongBox fingerprints for custom OS", e); + fingerprintsCustomOsStrongBox = ImmutableMap.builder().build(); + } + } + verifyCertificateSignatures(certificates, hasPersistentKey); // check that the root certificate is a valid key attestation root @@ -681,7 +635,8 @@ private static Verified verifyStateless(final Certificate[] certificates, appVariant = AUDITOR_APP_VARIANT_PLAY; } else if (AUDITOR_APP_PACKAGE_NAME_DEBUG.equals(info.getPackageName())) { if (!BuildConfig.DEBUG) { - throw new GeneralSecurityException("Auditor debug builds are only trusted by other Auditor debug builds"); + throw new GeneralSecurityException( + "Auditor debug builds are only trusted by other Auditor debug builds"); } if (!AUDITOR_APP_SIGNATURE_DIGEST_DEBUG.equals(signatureDigest)) { throw new GeneralSecurityException("invalid Auditor app signing key"); @@ -710,13 +665,21 @@ private static Verified verifyStateless(final Certificate[] certificates, final DeviceInfo device; if (verifiedBootState == RootOfTrust.KM_VERIFIED_BOOT_SELF_SIGNED) { if (attestationSecurityLevel == Attestation.KM_SECURITY_LEVEL_STRONG_BOX) { - device = fingerprintsStrongBoxCustomOS.get(verifiedBootKey); + if (fingerprintsGrapheneStrongBox.containsKey(verifiedBootKey)) { + device = fingerprintsGrapheneStrongBox.get(verifiedBootKey); + } else { + device = fingerprintsCustomOsStrongBox.get(verifiedBootKey); + } } else { - device = fingerprintsCustomOS.get(verifiedBootKey); + if (fingerprintsGraphene.containsKey(verifiedBootKey)) { + device = fingerprintsGraphene.get(verifiedBootKey); + } else { + device = fingerprintsCustomOs.get(verifiedBootKey); + } } } else if (verifiedBootState == RootOfTrust.KM_VERIFIED_BOOT_VERIFIED) { if (attestationSecurityLevel == Attestation.KM_SECURITY_LEVEL_STRONG_BOX) { - device = fingerprintsStrongBoxStock.get(verifiedBootKey); + device = fingerprintsStockStrongBox.get(verifiedBootKey); } else { device = fingerprintsStock.get(verifiedBootKey); } @@ -779,12 +742,14 @@ private static Verified verifyStateless(final Certificate[] certificates, final int attestationVersion = attestation.getAttestationVersion(); Log.d(TAG, "attestationVersion: " + attestationVersion); if (attestationVersion < device.attestationVersion) { - throw new GeneralSecurityException("attestation version " + attestationVersion + " below " + device.attestationVersion); + throw new GeneralSecurityException( + "attestation version " + attestationVersion + " below " + device.attestationVersion); } final int keymasterVersion = attestation.getKeymasterVersion(); Log.d(TAG, "keymasterVersion: " + keymasterVersion); if (keymasterVersion < device.keymasterVersion) { - throw new GeneralSecurityException("keymaster version " + keymasterVersion + " below " + device.keymasterVersion); + throw new GeneralSecurityException( + "keymaster version " + keymasterVersion + " below " + device.keymasterVersion); } final byte[] verifiedBootHash = rootOfTrust.getVerifiedBootHash(); @@ -840,7 +805,8 @@ private static Verified verifyStateless(final Certificate[] certificates, throw new GeneralSecurityException("attest key challenge does not match"); } - if (!attestation1.getSoftwareEnforced().getAttestationApplicationId().equals(attestationApplicationId)) { + if (!attestation1.getSoftwareEnforced().getAttestationApplicationId() + .equals(attestationApplicationId)) { throw new GeneralSecurityException("attest key application does not match"); } @@ -872,7 +838,8 @@ private static Verified verifyStateless(final Certificate[] certificates, } attestKey = true; - } catch (final Attestation.KeyDescriptionMissingException e) {} + } catch (final Attestation.KeyDescriptionMissingException e) { + } for (int i = 2; i < certificates.length; i++) { try { @@ -889,9 +856,12 @@ private static Verified verifyStateless(final Certificate[] certificates, device.enforceStrongBox); } - // Only checks expiry beyond the initial certificate for the initial pairing since the - // certificates are short lived when remote provisioning is in use and we prevent rotation by - // using the attest key feature to provide permanent pairing-specific certificate chains in + // Only checks expiry beyond the initial certificate for the initial pairing + // since the + // certificates are short lived when remote provisioning is in use and we + // prevent rotation by + // using the attest key feature to provide permanent pairing-specific + // certificate chains in // order to pin them. private static void verifyCertificateSignatures(final Certificate[] certChain, final boolean hasPersistentKey) throws GeneralSecurityException { @@ -965,9 +935,9 @@ private static void appendVerifiedInformation(final Context context, } else { final String osVersion = String.format(Locale.US, "%06d", verified.osVersion); builder.append(context.getString(R.string.os_version, - Integer.parseInt(osVersion.substring(0, 2)) + "." + - Integer.parseInt(osVersion.substring(2, 4)) + "." + - Integer.parseInt(osVersion.substring(4, 6)))); + Integer.parseInt(osVersion.substring(0, 2)) + "." + + Integer.parseInt(osVersion.substring(2, 4)) + "." + + Integer.parseInt(osVersion.substring(4, 6)))); } builder.append(context.getString(R.string.os_patch_level, formatPatchLevel(verified.osPatchLevel))); @@ -981,7 +951,7 @@ private static void appendVerifiedInformation(final Context context, } builder.append(context.getString(R.string.verified_boot_key_hash, - verified.verifiedBootKey)); + verified.verifiedBootKey)); if (verified.verifiedBootHash != null) { builder.append(context.getString(R.string.verified_boot_hash, @@ -1030,17 +1000,16 @@ private static VerificationResult verify(final Context context, final byte[] fin final byte[] currentFingerprint = getFingerprint(attestationCertificates[0]); final boolean hasPersistentKey = !Arrays.equals(currentFingerprint, fingerprint); - final SharedPreferences preferences = - context.getSharedPreferences(PREFERENCES_DEVICE_PREFIX + fingerprintHex, - Context.MODE_PRIVATE); + final SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_DEVICE_PREFIX + fingerprintHex, + Context.MODE_PRIVATE); if (hasPersistentKey && !preferences.contains(KEY_PINNED_CERTIFICATE_LENGTH)) { 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"); + "\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, challenge, hasPersistentKey, + final Verified verified = verifyStateless(context, attestationCertificates, challenge, hasPersistentKey, generateCertificate(context.getResources(), R.raw.google_root_0), generateCertificate(context.getResources(), R.raw.google_root_1), generateCertificate(context.getResources(), R.raw.google_root_2)); @@ -1059,7 +1028,8 @@ private static VerificationResult verify(final Context context, final byte[] fin chainOffset = 1; pinOffset = 0; attestKeyMigration = true; - } else if (ALLOW_ATTEST_KEY_DOWNGRADE && attestationCertificates.length == 4 && preferences.getInt(KEY_PINNED_CERTIFICATE_LENGTH, 0) == 5) { + } else if (ALLOW_ATTEST_KEY_DOWNGRADE && attestationCertificates.length == 4 + && preferences.getInt(KEY_PINNED_CERTIFICATE_LENGTH, 0) == 5) { // temporarily work around attest key breakage by allowing not using it chainOffset = 0; pinOffset = 1; @@ -1071,13 +1041,15 @@ private static VerificationResult verify(final Context context, final byte[] fin pinOffset = 0; } for (int i = 1 + chainOffset; i < attestationCertificates.length; i++) { - final byte[] b = BaseEncoding.base64().decode(preferences.getString(KEY_PINNED_CERTIFICATE + (i - chainOffset + pinOffset), "")); + final byte[] b = BaseEncoding.base64() + .decode(preferences.getString(KEY_PINNED_CERTIFICATE + (i - chainOffset + pinOffset), "")); if (!Arrays.equals(attestationCertificates[i].getEncoded(), b)) { throw new GeneralSecurityException("certificate chain mismatch"); } } - final byte[] persistentCertificateEncoded = BaseEncoding.base64().decode(preferences.getString(KEY_PINNED_CERTIFICATE + "0", "")); + final byte[] persistentCertificateEncoded = BaseEncoding.base64() + .decode(preferences.getString(KEY_PINNED_CERTIFICATE + "0", "")); final Certificate persistentCertificate = generateCertificate( new ByteArrayInputStream(persistentCertificateEncoded)); if (!Arrays.equals(fingerprint, getFingerprint(persistentCertificate))) { @@ -1110,7 +1082,8 @@ private static VerificationResult verify(final Context context, final byte[] fin if (verified.appVariant < pinnedAppVariant) { throw new GeneralSecurityException("App version downgraded"); } - if (verified.securityLevel != preferences.getInt(KEY_PINNED_SECURITY_LEVEL, Attestation.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT)) { + if (verified.securityLevel != preferences.getInt(KEY_PINNED_SECURITY_LEVEL, + Attestation.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT)) { throw new GeneralSecurityException("Security level mismatch"); } @@ -1136,8 +1109,10 @@ private static VerificationResult verify(final Context context, final byte[] fin } else { verifySignature(attestationCertificates[0].getPublicKey(), signedMessage, signature); - if (PREFER_STRONGBOX && verified.enforceStrongBox && verified.securityLevel != Attestation.KM_SECURITY_LEVEL_STRONG_BOX) { - throw new GeneralSecurityException("non-StrongBox security level for initial pairing with StrongBox device"); + if (PREFER_STRONGBOX && verified.enforceStrongBox + && verified.securityLevel != Attestation.KM_SECURITY_LEVEL_STRONG_BOX) { + throw new GeneralSecurityException( + "non-StrongBox security level for initial pairing with StrongBox device"); } final SharedPreferences.Editor editor = preferences.edit(); @@ -1212,7 +1187,8 @@ private static VerificationResult verify(final Context context, final byte[] fin osEnforced.append(context.getString(R.string.system_user, toYesNoString(context, systemUser))); - return new VerificationResult(hasPersistentKey, teeEnforced.toString(), osEnforced.toString(), history.toString()); + return new VerificationResult(hasPersistentKey, teeEnforced.toString(), osEnforced.toString(), + history.toString()); } private static Certificate[] decodeChain(final byte[] dictionary, final byte[] compressedChain) @@ -1359,8 +1335,8 @@ static KeyGenParameterSpec.Builder getKeyBuilder(final String alias, final int p } @TargetApi(31) - static void generateAttestKey(final String alias, final byte[] challenge, final boolean useStrongBox) throws - GeneralSecurityException, IOException { + static void generateAttestKey(final String alias, final byte[] challenge, final boolean useStrongBox) + throws GeneralSecurityException, IOException { generateKeyPair(getKeyBuilder(alias, KeyProperties.PURPOSE_ATTEST_KEY, useStrongBox, challenge, false).build()); } @@ -1416,19 +1392,19 @@ static AttestationResult generateSerialized(final Context context, final byte[] index = BaseEncoding.base16().encode(challengeIndex); } - final String attestKeystoreAlias = - statePrefix + KEYSTORE_ALIAS_ATTEST_PREFIX + index; - final String persistentKeystoreAlias = - statePrefix + KEYSTORE_ALIAS_PERSISTENT_PREFIX + index; + final String attestKeystoreAlias = statePrefix + KEYSTORE_ALIAS_ATTEST_PREFIX + index; + final String persistentKeystoreAlias = statePrefix + KEYSTORE_ALIAS_PERSISTENT_PREFIX + index; final PackageManager pm = context.getPackageManager(); - // generate a new key for fresh attestation results unless the persistent key is not yet created + // generate a new key for fresh attestation results unless the persistent key is + // not yet created final boolean hasPersistentKey = keyStore.containsAlias(persistentKeystoreAlias); final String attestationKeystoreAlias; final boolean useStrongBox; @SuppressLint("InlinedApi") - final boolean canUseAttestKey = (alwaysHasAttestKey || pm.hasSystemFeature(PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY)) + final boolean canUseAttestKey = (alwaysHasAttestKey + || pm.hasSystemFeature(PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY)) && USE_ATTEST_KEY; boolean useAttestKey; if (hasPersistentKey) { @@ -1442,8 +1418,7 @@ static AttestationResult generateSerialized(final Context context, final byte[] final KeyInfo keyinfo = factory.getKeySpec(key, KeyInfo.class); useStrongBox = keyinfo.getSecurityLevel() == KeyProperties.SECURITY_LEVEL_STRONGBOX; } else { - final X509Certificate persistent = - (X509Certificate) getCertificate(keyStore, persistentKeystoreAlias); + final X509Certificate persistent = (X509Certificate) getCertificate(keyStore, persistentKeystoreAlias); final String dn = persistent.getIssuerX500Principal().getName(X500Principal.RFC1779); useStrongBox = dn.contains("StrongBox"); } @@ -1478,7 +1453,8 @@ static AttestationResult generateSerialized(final Context context, final byte[] } generateKeyPair(builder.build()); } catch (final IOException e) { - // try without using attest key when already paired due to Pixel 6 / Pixel 6 Pro / Pixel 6a upgrade bug + // try without using attest key when already paired due to Pixel 6 / Pixel 6 Pro + // / Pixel 6a upgrade bug if (hasPersistentKey) { useAttestKey = false; final KeyGenParameterSpec.Builder builder = getKeyBuilder(attestationKeystoreAlias, @@ -1491,8 +1467,7 @@ static AttestationResult generateSerialized(final Context context, final byte[] } try { - final byte[] fingerprint = - getFingerprint(getCertificate(keyStore, persistentKeystoreAlias)); + final byte[] fingerprint = getFingerprint(getCertificate(keyStore, persistentKeystoreAlias)); final Certificate[] attestationCertificates; @@ -1505,8 +1480,9 @@ static AttestationResult generateSerialized(final Context context, final byte[] attestationCertificates = getCertificateChain(keyStore, attestationKeystoreAlias); } - // sanity check on the device being verified before sending it off to the verifying device - final Verified verified = verifyStateless(attestationCertificates, challenge, hasPersistentKey, + // sanity check on the device being verified before sending it off to the + // verifying device + final Verified verified = verifyStateless(context, attestationCertificates, challenge, hasPersistentKey, generateCertificate(context.getResources(), R.raw.google_root_0), generateCertificate(context.getResources(), R.raw.google_root_1), generateCertificate(context.getResources(), R.raw.google_root_2)); @@ -1558,8 +1534,7 @@ static AttestationResult generateSerialized(final Context context, final byte[] final boolean addUsersWhenLocked = Settings.Global.getInt(context.getContentResolver(), ADD_USERS_WHEN_LOCKED, 0) != 0; - final String denyNewUsbValue = - SystemProperties.get("persist.security.deny_new_usb", "disabled"); + final String denyNewUsbValue = SystemProperties.get("persist.security.deny_new_usb", "disabled"); final boolean denyNewUsb = !denyNewUsbValue.equals("disabled"); final String oemUnlockAllowedValue = SystemProperties.get("sys.oem_unlock_allowed", "0"); @@ -1651,12 +1626,18 @@ static AttestationResult generateSerialized(final Context context, final byte[] static void generateKeyPair(final KeyGenParameterSpec spec) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, IOException { - // Handle RuntimeExceptions caused by a broken keystore. A common issue involves users - // unlocking the device and wiping the encrypted TEE attestation keys from the persist - // partition. Additionally, some non-CTS compliant devices or operating systems have a - // non-existent or broken implementation. No one has reported these uncaught exceptions, - // presumably because they know their device or OS is broken, but the crash reports are - // being spammed to the Google Play error collection and causing it to think the app is + // Handle RuntimeExceptions caused by a broken keystore. A common issue involves + // users + // unlocking the device and wiping the encrypted TEE attestation keys from the + // persist + // partition. Additionally, some non-CTS compliant devices or operating systems + // have a + // non-existent or broken implementation. No one has reported these uncaught + // exceptions, + // presumably because they know their device or OS is broken, but the crash + // reports are + // being spammed to the Google Play error collection and causing it to think the + // app is // unreliable. try { final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, diff --git a/app/src/main/java/app/attestation/auditor/ImmutableMapParser.java b/app/src/main/java/app/attestation/auditor/ImmutableMapParser.java new file mode 100644 index 000000000..31e077f98 --- /dev/null +++ b/app/src/main/java/app/attestation/auditor/ImmutableMapParser.java @@ -0,0 +1,58 @@ +package app.attestation.auditor; + +import java.io.IOException; + +import com.google.common.collect.ImmutableMap; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.content.res.XmlResourceParser; +import android.util.Log; + +interface XmlMapElemParser { + public K parseKey(Context context, XmlResourceParser parser); + + public V parseValue(Context context, XmlResourceParser parser); +} + +class ImmutableMapParser { + public static ImmutableMap getImmutableMapResource(Context context, int hashMapResId, + String keyTagName, String valueTagName, XmlMapElemParser mapElemParser) + throws XmlPullParserException, IOException, IllegalArgumentException { + ImmutableMap.Builder map = null; + XmlResourceParser parser = context.getResources().getXml(hashMapResId); + + K key = null; + V value = null; + + int eventType = parser.getEventType(); + + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_DOCUMENT) { + Log.d("ImmutableMapParser", "Start document"); + } else if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("map")) { + Log.d("ImmutableMapParser", "parsing map"); + map = ImmutableMap.builder(); + } else if (parser.getName().equals("entry")) { + Log.d("ImmutableMapParser", "parsing entry"); + } else if (parser.getName().equals(keyTagName)) { + key = mapElemParser.parseKey(context, parser); + } else if (parser.getName().equals(valueTagName)) { + value = mapElemParser.parseValue(context, parser); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("entry")) { + map.put(key, value); + key = null; + value = null; + } + } + eventType = parser.next(); + } + + return map.build(); + } +} diff --git a/app/src/main/java/app/attestation/auditor/RemoteVerifyJob.java b/app/src/main/java/app/attestation/auditor/RemoteVerifyJob.java index fc4a66e99..f1f629a1d 100644 --- a/app/src/main/java/app/attestation/auditor/RemoteVerifyJob.java +++ b/app/src/main/java/app/attestation/auditor/RemoteVerifyJob.java @@ -39,9 +39,6 @@ public class RemoteVerifyJob extends JobService { private static final String TAG = "RemoteVerifyJob"; private static final int PERIODIC_JOB_ID = 0; private static final int FIRST_RUN_JOB_ID = 1; - static final String DOMAIN = "attestation.app"; - private static final String CHALLENGE_URL = "https://" + DOMAIN + "/challenge"; - private static final String VERIFY_URL = "https://" + DOMAIN + "/verify"; private static final int CONNECT_TIMEOUT = 60000; private static final int READ_TIMEOUT = 60000; private static final int DEFAULT_INTERVAL = 4 * 60 * 60; @@ -127,6 +124,18 @@ static void cancel(final Context context) { scheduler.cancel(FIRST_RUN_JOB_ID); } + private String domain() { + return getString(R.string.base_domain); + } + + private String challengeUrl() { + return domain() + "/challenge"; + } + + private String verifyUrl() { + return domain() + "/verify"; + } + @Override public boolean onStartJob(final JobParameters params) { task = executor.submit(() -> { @@ -135,7 +144,7 @@ public boolean onStartJob(final JobParameters params) { HttpURLConnection connection = null; String exceptionMessage = null; try { - connection = (HttpURLConnection) new URL(CHALLENGE_URL).openConnection(); + connection = (HttpURLConnection) new URL(challengeUrl()).openConnection(); connection.setConnectTimeout(CONNECT_TIMEOUT); connection.setReadTimeout(READ_TIMEOUT); connection.setRequestMethod("POST"); @@ -158,7 +167,7 @@ public boolean onStartJob(final JobParameters params) { final AttestationResult result = AttestationProtocol.generateSerialized( context, challengeMessage, Long.toString(userId), STATE_PREFIX); - connection = (HttpURLConnection) new URL(VERIFY_URL).openConnection(); + connection = (HttpURLConnection) new URL(verifyUrl()).openConnection(); connection.setConnectTimeout(CONNECT_TIMEOUT); connection.setReadTimeout(READ_TIMEOUT); connection.setDoOutput(true); diff --git a/app/src/main/java/app/attestation/auditor/SubmitSampleJob.java b/app/src/main/java/app/attestation/auditor/SubmitSampleJob.java index f2a7bb696..985cef07c 100644 --- a/app/src/main/java/app/attestation/auditor/SubmitSampleJob.java +++ b/app/src/main/java/app/attestation/auditor/SubmitSampleJob.java @@ -41,7 +41,6 @@ public class SubmitSampleJob extends JobService { private static final String TAG = "SubmitSampleJob"; private static final int JOB_ID = 2; - private static final String SUBMIT_URL = "https://" + RemoteVerifyJob.DOMAIN + "/submit"; private static final int CONNECT_TIMEOUT = 60000; private static final int READ_TIMEOUT = 60000; private static final int ESTIMATED_DOWNLOAD_BYTES = 4 * 1024; @@ -70,12 +69,16 @@ static void schedule(final Context context) { } } + private String submitUrl() { + return getString(R.string.base_domain) + "/submit"; + } + @Override public boolean onStartJob(final JobParameters params) { task = executor.submit(() -> { HttpURLConnection connection = null; try { - connection = (HttpURLConnection) new URL(SUBMIT_URL).openConnection(); + connection = (HttpURLConnection) new URL(submitUrl()).openConnection(); connection.setConnectTimeout(CONNECT_TIMEOUT); connection.setReadTimeout(READ_TIMEOUT); connection.setDoOutput(true); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 30b3a932e..b5948c3f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ Auditor + attestation.app Two devices with Android 10 or higher are needed to perform verification:\n\n- The device to be verified (Auditee), which needs to be one of the supported devices.\n\n- An Android device to perform the verification (Auditor).\n\nThe verification process requires sending data between the devices by scanning QR codes. Device is not one of the supported models. Camera permission is required to scan QR codes. diff --git a/app/src/main/res/xml/fingerprints_graphene.xml b/app/src/main/res/xml/fingerprints_graphene.xml new file mode 100644 index 000000000..cec6a668a --- /dev/null +++ b/app/src/main/res/xml/fingerprints_graphene.xml @@ -0,0 +1,207 @@ + + + + B094E48B27C6E15661223CEFF539CF35E481DEB4E3250331E973AC2C15CAD6CD + + device_pixel_2 + 2 + 3 + true + true + false + os_graphene + + + + B6851E9B9C0EBB7185420BD0E79D20A84CB15AB0B018505EFFAA4A72B9D9DAC7 + + device_pixel_2_xl + 2 + 3 + true + true + false + os_graphene + + + + 0F9A9CC8ADE73064A54A35C5509E77994E3AA37B6FB889DD53AF82C3C570C5CF + + device_pixel_3 + 3 + 4 + false + true + true + os_graphene + + + + 06DD526EE9B1CB92AA19D9835B68B4FF1A48A3AD31D813F27C9A7D6C271E9451 + + device_pixel_3_xl + 3 + 4 + false + true + true + os_graphene + + + + 8FF8B9B4F831114963669E04EA4F849F33F3744686A0B33B833682746645ABC8 + + device_pixel_3a + 3 + 4 + false + true + true + os_graphene + + + + 91943FAA75DCB6392AE87DA18CA57D072BFFB80BC30F8FAFC7FFE13D76C5736E + + device_pixel_3a_xl + 3 + 4 + false + true + true + os_graphene + + + + 80EF268700EE42686F779A47B4A155FE1FFC2EEDF836B4803CAAB8FA61439746 + + device_pixel_4 + 3 + 4 + false + true + true + os_graphene + + + + 3F15FDCB82847FED97427CE00563B8F9FF34627070DE5FDB17ACA7849AB98CC8 + + device_pixel_4_xl + 3 + 4 + false + true + true + os_graphene + + + + 9F2454A1657B1B5AD7F2336B39A2611F7A40B2E0DDFD0D6553A359605928DF29 + + device_pixel_4a + 3 + 4 + false + true + true + os_graphene + + + + DCEC2D053D3EC4F1C9BE414AA07E4D7D7CBD12040AD2F8831C994A83A0536866 + + device_pixel_4a_5g + 3 + 4 + false + true + true + os_graphene + + + + 36A99EAB7907E4FB12A70E3C41C456BCBE46C13413FBFE2436ADEE2B2B61120F + + device_pixel_5 + 3 + 4 + false + true + true + os_graphene + + + + 0ABDDEDA03B6CE10548C95E0BEA196FAA539866F929BCDF7ECA84B4203952514 + + device_pixel_5a + 3 + 4 + false + true + true + os_graphene + + + + F0A890375D1405E62EBFD87E8D3F475F948EF031BBF9DDD516D5F600A23677E8 + + device_pixel_6 + 100 + 100 + false + true + true + os_graphene + + + + 439B76524D94C40652CE1BF0D8243773C634D2F99BA3160D8D02AA5E29FF925C + + device_pixel_6_pro + 100 + 100 + false + true + true + os_graphene + + + + 08C860350A9600692D10C8512F7B8E80707757468E8FBFEEA2A870C0A83D6031 + + device_pixel_6a + 100 + 100 + false + true + true + os_graphene + + + + 3EFE5392BE3AC38AFB894D13DE639E521675E62571A8A9B3EF9FC8C44FD17FA1 + + device_pixel_7 + 200 + 200 + false + true + false + os_graphene + + + + BC1C0DD95664604382BB888412026422742EB333071EA0B2D19036217D49182F + + device_pixel_7_pro + 200 + 200 + false + true + false + os_graphene + + + diff --git a/app/src/main/res/xml/fingerprints_graphene_strongbox.xml b/app/src/main/res/xml/fingerprints_graphene_strongbox.xml new file mode 100644 index 000000000..43f3fdfe5 --- /dev/null +++ b/app/src/main/res/xml/fingerprints_graphene_strongbox.xml @@ -0,0 +1,183 @@ + + + + 0F9A9CC8ADE73064A54A35C5509E77994E3AA37B6FB889DD53AF82C3C570C5CF + + device_pixel_3 + 3 + 4 + false + true + true + os_graphene + + + + 06DD526EE9B1CB92AA19D9835B68B4FF1A48A3AD31D813F27C9A7D6C271E9451 + + device_pixel_3_xl + 3 + 4 + false + true + true + os_graphene + + + + 73D6C63A07610404FE16A4E07DD24E41A70D331E9D3EF7BBA2D087E4761EB63A + + device_pixel_3a + 3 + 4 + false + true + true + os_graphene + + + + 3F36E3482E1FF82986576552CB4FD08AF09F8B09D3832314341E04C42D2919A4 + + device_pixel_3a_xl + 3 + 4 + false + true + true + os_graphene + + + + 80EF268700EE42686F779A47B4A155FE1FFC2EEDF836B4803CAAB8FA61439746 + + device_pixel_4 + 3 + 4 + false + true + true + os_graphene + + + + 3F15FDCB82847FED97427CE00563B8F9FF34627070DE5FDB17ACA7849AB98CC8 + + device_pixel_4_xl + 3 + 4 + false + true + true + os_graphene + + + + 9F2454A1657B1B5AD7F2336B39A2611F7A40B2E0DDFD0D6553A359605928DF29 + + device_pixel_4a + 3 + 4 + false + true + true + os_graphene + + + + DCEC2D053D3EC4F1C9BE414AA07E4D7D7CBD12040AD2F8831C994A83A0536866 + + device_pixel_4a_5g + 4 + 41 + false + true + true + os_graphene + + + + 36A99EAB7907E4FB12A70E3C41C456BCBE46C13413FBFE2436ADEE2B2B61120F + + device_pixel_5 + 4 + 41 + false + true + true + os_graphene + + + + 0ABDDEDA03B6CE10548C95E0BEA196FAA539866F929BCDF7ECA84B4203952514 + + device_pixel_5a + 4 + 41 + false + true + true + os_graphene + + + + F0A890375D1405E62EBFD87E8D3F475F948EF031BBF9DDD516D5F600A23677E8 + + device_pixel_6 + 100 + 100 + false + true + true + os_graphene + + + + 439B76524D94C40652CE1BF0D8243773C634D2F99BA3160D8D02AA5E29FF925C + + device_pixel_6_pro + 100 + 100 + false + true + true + os_graphene + + + + 08C860350A9600692D10C8512F7B8E80707757468E8FBFEEA2A870C0A83D6031 + + device_pixel_6a + 100 + 100 + false + true + true + os_graphene + + + + 3EFE5392BE3AC38AFB894D13DE639E521675E62571A8A9B3EF9FC8C44FD17FA1 + + device_pixel_7 + 100 + 100 + false + true + false + os_graphene + + + + BC1C0DD95664604382BB888412026422742EB333071EA0B2D19036217D49182F + + device_pixel_7_pro + 100 + 100 + false + true + false + os_graphene + + + diff --git a/app/src/main/res/xml/fingerprints_stock.xml b/app/src/main/res/xml/fingerprints_stock.xml new file mode 100644 index 000000000..047abfeb2 --- /dev/null +++ b/app/src/main/res/xml/fingerprints_stock.xml @@ -0,0 +1,783 @@ + + + + 5341E6B2646979A70E57653007A1F310169421EC9BDD9F1A5648F75ADE005AF1 + + device_huawei + 2 + 3 + false + true + false + os_stock + + + + 7E2E8CC82A77CA74554457E5DF3A3ED82E7032B3182D17FE17919BC6E989FF09 + + device_huawei_honor_7a_pro + 2 + 3 + false + true + false + os_stock + + + + DFC2920C81E136FDD2A510478FDA137B262DC51D449EDD7D0BDB554745725CFE + + device_nokia + 2 + 3 + true + true + false + os_stock + + + + 4D790FA0A5FE81D6B352B90AFE430684D9BC817518CD24C50E6343395F7C51F2 + + device_nokia_3_1 + 2 + 3 + false + false + false + os_stock + + + + 893A17FD918235DB2865F7F6439EB0134A45B766AA452E0675BAC6CFB5A773AA + + device_nokia_7_1 + 2 + 3 + true + true + false + os_stock + + + + 6101853DFF451FAE5B137DF914D5E6C15C659337F2C405AC50B513A159071958 + + device_oneplus_6_a6003 + 2 + 3 + true + true + false + os_stock + + + + 1B90B7D1449D697FB2732A7D2DFA405D587254593F5137F7B6E64F7A0CE03BFD + + device_oneplus_6t_a6013 + 3 + 4 + false + true + false + os_stock + + + + 4B9201B11685BE6710E2B2BA8482F444E237E0C8A3D1F7F447FE29C37CECC559 + + device_oneplus_7_pro_gm1913 + 3 + 4 + false + true + false + os_stock + + + + 1962B0538579FFCE9AC9F507C46AFE3B92055BAC7146462283C85C500BE78D82 + + device_pixel_2 + 2 + 3 + true + true + false + os_stock + + + + 171616EAEF26009FC46DC6D89F3D24217E926C81A67CE65D2E3A9DC27040C7AB + + device_pixel_2_xl + 2 + 3 + true + true + false + os_stock + + + + 61FDA12B32ED84214A9CF13D1AFFB7AA80BD8A268A861ED4BB7A15170F1AB00C + + device_pixel_3_generic + 3 + 4 + false + true + true + os_stock + + + + E75B86C52C7496255A95FB1E2B1C044BFA9D5FE34DD1E4EEBD752EEF0EA89875 + + device_pixel_3a_generic + 3 + 4 + false + true + true + os_stock + + + + AE6316B4753C61F5855B95B9B98484AF784F2E83648D0FCC8107FCA752CAEA34 + + device_pixel_4_generic + 3 + 4 + false + true + true + os_stock + + + + 879CD3F18EA76E244D4D4AC3BCB9C337C13B4667190B19035AFE2536550050F1 + + device_pixel_4a + 3 + 4 + false + true + true + os_stock + + + + 88265D85BA9E1E2F6036A259D880D2741031ACA445840137395B6D541C0FC7FC + + device_pixel_5_generic + 3 + 4 + false + true + true + os_stock + + + + 1DD694CE00BF131AD61CEB576B7DCC41CF7F9B2C418F4C12B2B8F3E9A1EA911D + + device_pixel_5a + 3 + 4 + false + true + true + os_stock + + + + 0F6E75C80183B5DEC074B0054D4271E99389EBE4B136B0819DE1F150BA0FF9D7 + + device_pixel_6 + 100 + 100 + false + true + true + os_stock + + + + 42ED1BCA352FABD428F34E8FCEE62776F4CB2C66E06F82E5A59FF4495267BFC2 + + device_pixel_6_pro + 100 + 100 + false + true + true + os_stock + + + + 9AC4174153D45E4545B0F49E22FE63273999B6AC1CB6949C3A9F03EC8807EEE9 + + device_pixel_6a + 100 + 100 + false + true + true + os_stock + + + + 8B2C4CD539F5075E8E7CF212ADB3DB0413FBD77D321199C73D5A473C51F2E10D + + device_pixel_7 + 200 + 200 + false + true + false + os_stock + + + + 26AC4C60BEB1E378357CAD0C3061347AF8DF6FBABBB0D8CEA2445855EE01E368 + + device_pixel_7_pro + 200 + 200 + false + true + false + os_stock + + + + 72376CAACF11726D4922585732429FB97D0D1DD69F0D2E0770B9E61D14ADDE65 + + device_sm_a705fn + 3 + 4 + false + true + false + os_stock + + + + 33D9484FD512E610BCF00C502827F3D55A415088F276C6506657215E622FA770 + + device_sm_g960f + 1 + 2 + false + false + false + os_stock + + + + 266869F7CF2FB56008EFC4BE8946C8F84190577F9CA688F59C72DD585E696488 + + device_sm_g960_na + 1 + 2 + false + false + false + os_stock + + + + 12E8460A7BAF709F3B6CF41C7E5A37C6EB4D11CB36CF7F61F7793C8DCDC3C2E4 + + device_sm_g9600 + 1 + 2 + false + false + false + os_stock + + + + D1C53B7A931909EC37F1939B14621C6E4FD19BF9079D195F86B3CEA47CD1F92D + + device_sm_g965f + 1 + 2 + false + false + false + os_stock + + + + A4A544C2CFBAEAA88C12360C2E4B44C29722FC8DBB81392A6C1FAEDB7BF63010 + + device_sm_g965_msm + 1 + 2 + false + false + false + os_stock + + + + 9D77474FA4FEA6F0B28636222FBCEE2BB1E6FF9856C736C85B8EA6E3467F2BBA + + device_sm_g970f + 3 + 4 + false + true + false + os_stock + + + + 08B2B5C6EC8F54C00C505756E1EF516BB4537B2F02D640410D287A43FCF92E3F + + device_sm_g975f + 3 + 4 + false + true + false + os_stock + + + + F0FC0AF47D3FE4F27D79CF629AD6AC42AA1EEDE0A29C0AE109A91BBD1E7CD76D + + device_sm_j260a + 1 + 2 + false + false + false + os_stock + + + + 410102030405060708090001020304050607080900010203040506070809005A + + device_sm_j260f + 1 + 2 + false + false + false + os_stock + + + + D6B902D9E77DFC0FB3627FFEFA6D05405932EBB3A6ED077874B5E2A0CCBDB632 + + device_sm_j260t1 + 1 + 2 + false + false + false + os_stock + + + + 4558C1AFB30D1B46CB93F85462BC7D7FCF70B0103B9DBB0FE96DD828F43F29FC + + device_sm_j337a + 1 + 2 + false + false + false + os_stock + + + + 45E3AB5D61A03915AE10BF0465B186CB5D9A2FB6A46BEFAA76E4483BBA5A358D + + device_sm_j337t + 1 + 2 + false + false + false + os_stock + + + + D95279A8F2E832FD68D919DBF33CFE159D5A1179686DB0BD2D7BBBF2382C4DD3 + + device_sm_j720f + 1 + 2 + false + false + false + os_stock + + + + BB053A5F64D3E3F17C4611340FF2BBE2F605B832A9FA412B2C87F2A163ECE2FB + + device_sm_j737t1 + 1 + 2 + false + false + false + os_stock + + + + 4E0570011025D01386D057B2B382969F804DCD19E001344535CF0CFDB8AD7CFE + + device_sm_m205f + 1 + 2 + false + false + false + os_stock + + + + 2A7E4954C9F703F3AC805AC660EA1727B981DB39B1E0F41E4013FA2586D3DF7F + + device_sm_n960f + 1 + 2 + false + false + false + os_stock + + + + 173ACFA8AE9EDE7BBD998F45A49231F3A4BDDF0779345732E309446B46B5641B + + device_sm_n960u + 1 + 2 + false + false + false + os_stock + + + + E94BC43B97F98CD10C22CD9D8469DBE621116ECFA624FE291A1D53CF3CD685D1 + + device_sm_n970f + 3 + 4 + false + true + false + os_stock + + + + 466011C44BBF883DB38CF96617ED35C796CE2552C5357F9230258329E943DB70 + + device_sm_n970u + 3 + 4 + false + true + true + os_stock + + + + 52946676088007755EB586B3E3F3E8D3821BE5DF73513E6C13640507976420E6 + + device_sm_n975u + 3 + 4 + false + true + true + os_stock + + + + F3688C02D9676DEDB6909CADE364C271901FD66EA4F691AEB8B8921195E469C5 + + device_sm_s367vl + 1 + 2 + false + false + false + os_stock + + + + 106592D051E54388C6E601DFD61D59EB1674A8B93216C65C5B3E1830B73D3B82 + + device_sm_t510 + 3 + 4 + false + true + false + os_stock + + + + 87790149AED63553B768456AAB6DAAD5678CD87BDEB2BF3649467085349C34E0 + + device_sm_t835 + 1 + 2 + false + false + false + os_stock + + + + 4285AD64745CC79B4499817F264DC16BF2AF5163AF6C328964F39E61EC84693E + + device_sony_xperia_xa2 + 2 + 3 + true + true + false + os_stock + + + + 54A9F21E9CFAD3A2D028517EF333A658302417DB7FB75E0A109A019646CC5F39 + + device_sony_xperia_xz1 + 2 + 3 + true + true + false + os_stock + + + + BC3B5E121974113939B8A2FE758F9B923F1D195F038D2FD1C04929F886E83BB5 + + device_sony_xperia_xz2 + 2 + 3 + false + true + false + os_stock + + + + 94B8B4E3260B4BF8211A02CF2F3DE257A127CFFB2E4047D5580A752A5E253DE0 + + device_sony_xperia_xz2_compact + 2 + 3 + true + true + false + os_stock + + + + 728800FEBB119ADD74519618AFEDB715E1C39FE08A4DE37D249BF54ACF1CE00F + + device_blackberry_key2 + 2 + 3 + true + true + false + os_stock + + + + 1194659B40EA291245E54A3C4EC4AA5B7077BD244D65C7DD8C0A2DBB9DB1FB35 + + device_bq_aquaris_x2_pro + 2 + 3 + true + false + false + os_stock + + + + A9C6758D509600D0EB94FA8D2BF6EE7A6A6097F0CCEF94A755DDE065AA1AA1B0 + + device_xiaomi_mi_a2 + 2 + 3 + true + false + false + os_stock + + + + 6FA710B639848C9D47378937A1AFB1B6A52DDA738BEB6657E2AE70A15B40541A + + device_xiaomi_mi_a2_lite + 2 + 3 + true + false + false + os_stock + + + + 84BC8445A29B5444A2D1629C9774C8626DAFF3574D865EC5067A78FAEC96B013 + + device_xiaomi_mi_9 + 3 + 4 + false + true + false + os_stock + + + + 1CC39488D2F85DEE0A8E0903CDC4124CFDF2BE2531ED6060B678057ED2CB89B4 + + device_htc + 2 + 3 + true + false + false + os_stock + + + + 80BAB060807CFFA45D4747DF1AD706FEE3AE3F645F80CF14871DDBE27E14C30B + + device_moto_g7 + 3 + 4 + false + true + false + os_stock + + + + C2224571C9CD5C89200A7311B1E37AA9CF751E2E19753E8D3702BCA00BE1D42C + + device_motorola_one_vision + 2 + 3 + false + true + false + os_stock + + + + 1F6D98D1B0E1F1CE1C872BD36C668F9DFDBE0D47594789E1540DF4E6198F657D + + device_vivo_1807 + 2 + 3 + true + false + false + os_stock + + + + C55635636999E9D0A0588D24402256B7F9F3AEE07B4F7E4E003F09FF0190AFAE + + device_revvl_2 + 2 + 3 + false + false + false + os_stock + + + + 341C50D577DC5F3D5B46E8BFA22C22D1E5FC7D86D4D860E70B89222A7CBFC893 + + device_oppo_cph1831 + 2 + 3 + true + false + false + os_stock + + + + 41BF0A26BB3AFDCCCC40F7B685083522EB5BF1C492F0EC4847F351265313CB07 + + device_oppo_cph1903 + 2 + 3 + true + false + false + os_stock + + + + 7E19E217072BE6CB7A4C6F673FD3FB62DC51B3E204E7475838747947A3920DD8 + + device_oppo_cph1909 + 2 + 3 + false + false + false + os_stock + + + + 0D5F986943D0CE0D4F9783C27EEBE175BE359927DB8B6546B667279A81133C3C + + device_lg_q710al + 2 + 3 + false + false + false + os_stock + + + + D20078F2AF2A7D3ECA3064018CB8BD47FBCA6EE61ABB41BA909D3C529CB802F4 + + device_lm_q720 + 3 + 4 + false + false + false + os_stock + + + + 54EC644C21FD8229E3B0066513337A8E2C8EF3098A3F974B6A1CFE456A683DAE + + device_rmx1941 + 2 + 3 + false + true + false + os_stock + + + diff --git a/app/src/main/res/xml/fingerprints_stock_strongbox.xml b/app/src/main/res/xml/fingerprints_stock_strongbox.xml new file mode 100644 index 000000000..93d84aa14 --- /dev/null +++ b/app/src/main/res/xml/fingerprints_stock_strongbox.xml @@ -0,0 +1,159 @@ + + + + 61FDA12B32ED84214A9CF13D1AFFB7AA80BD8A268A861ED4BB7A15170F1AB00C + + device_pixel_3_generic + 3 + 4 + false + true + true + os_stock + + + + 8CA89AF1A6DAA74B00810849356DE929CFC4498EF36AF964757BDE8A113BF46D + + device_pixel_3a_generic + 3 + 4 + false + true + true + os_stock + + + + AE6316B4753C61F5855B95B9B98484AF784F2E83648D0FCC8107FCA752CAEA34 + + device_pixel_4_generic + 3 + 4 + false + true + true + os_stock + + + + 879CD3F18EA76E244D4D4AC3BCB9C337C13B4667190B19035AFE2536550050F1 + + device_pixel_4a + 3 + 4 + false + true + true + os_stock + + + + 88265D85BA9E1E2F6036A259D880D2741031ACA445840137395B6D541C0FC7FC + + device_pixel_5_generic + 4 + 41 + false + true + true + os_stock + + + + 1DD694CE00BF131AD61CEB576B7DCC41CF7F9B2C418F4C12B2B8F3E9A1EA911D + + device_pixel_5a + 4 + 41 + false + true + true + os_stock + + + + 0F6E75C80183B5DEC074B0054D4271E99389EBE4B136B0819DE1F150BA0FF9D7 + + device_pixel_6 + 100 + 100 + false + true + true + os_stock + + + + 42ED1BCA352FABD428F34E8FCEE62776F4CB2C66E06F82E5A59FF4495267BFC2 + + device_pixel_6_pro + 100 + 100 + false + true + true + os_stock + + + + 9AC4174153D45E4545B0F49E22FE63273999B6AC1CB6949C3A9F03EC8807EEE9 + + device_pixel_6a + 100 + 100 + false + true + true + os_stock + + + + 8B2C4CD539F5075E8E7CF212ADB3DB0413FBD77D321199C73D5A473C51F2E10D + + device_pixel_7 + 100 + 100 + false + true + false + os_stock + + + + 26AC4C60BEB1E378357CAD0C3061347AF8DF6FBABBB0D8CEA2445855EE01E368 + + device_pixel_7_pro + 100 + 100 + false + true + false + os_stock + + + + 3D3DEB132A89551D0A700D230BABAE4E3E80E3C7926ACDD7BAEDF9B57AD316D0 + + device_sm_n970u + 3 + 4 + false + true + true + os_stock + + + + 9AC63842137D92C119A1B1BE2C9270B9EBB6083BBE6350B7823571942B5869F0 + + device_sm_n975u + 3 + 4 + false + true + true + os_stock + + + diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 5a97d0faa..3f19dbf4b 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -37,6 +37,22 @@ + + + + + + + + + + + + + + + + @@ -51,6 +67,9 @@ + + + @@ -80,6 +99,9 @@ + + + @@ -272,6 +294,14 @@ + + + + + + + + @@ -280,6 +310,14 @@ + + + + + + + + @@ -288,6 +326,22 @@ + + + + + + + + + + + + + + + + @@ -386,6 +440,14 @@ + + + + + + + + @@ -394,6 +456,14 @@ + + + + + + + + @@ -407,6 +477,14 @@ + + + + + + + + @@ -423,6 +501,14 @@ + + + + + + + + @@ -436,6 +522,14 @@ + + + + + + + + @@ -449,6 +543,14 @@ + + + + + + + + @@ -457,6 +559,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -582,6 +708,9 @@ + + + @@ -594,6 +723,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -682,6 +851,11 @@ + + + + + @@ -914,6 +1088,14 @@ + + + + + + + + @@ -994,6 +1176,14 @@ + + + + + + + + @@ -1002,6 +1192,14 @@ + + + + + + + + @@ -1010,6 +1208,14 @@ + + + + + + + + @@ -1018,6 +1224,14 @@ + + + + + + + + @@ -1026,6 +1240,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -1034,6 +1272,14 @@ + + + + + + + + @@ -1042,6 +1288,14 @@ + + + + + + + + @@ -1074,6 +1328,14 @@ + + + + + + + + @@ -1110,6 +1372,14 @@ + + + + + + + + @@ -1331,6 +1601,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1339,6 +1641,14 @@ + + + + + + + + @@ -1360,6 +1670,11 @@ + + + + + @@ -1472,6 +1787,14 @@ + + + + + + + + @@ -1671,6 +1994,14 @@ + + + + + + + + @@ -1930,6 +2261,19 @@ + + + + + + + + + + + + + @@ -2277,6 +2621,21 @@ + + + + + + + + + + + + + + + @@ -2285,6 +2644,19 @@ + + + + + + + + + + + + + @@ -2298,6 +2670,24 @@ + + + + + + + + + + + + + + + + + + @@ -2314,6 +2704,11 @@ + + + + +