Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
.externalNativeBuild
.cxx
/*.jks
/.kotlin/sessions/kotlin-compiler-*.salive
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ interface IAndroidKeyStore {
String getRkpHostname();
boolean canRemoteProvisioning(boolean useStrongBox);
RpcHardwareInfo getHardwareInfo(boolean useStrongBox, out DeviceInfo deviceInfo);
byte[] getDiceChain(boolean useStrongBox);
byte[] checkRemoteProvisioning(boolean useStrongBox);
String getVbmetaDigest();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.google.common.base.Functions.forMap;
import static com.google.common.collect.Collections2.transform;

import android.os.Build;
import android.security.keystore.KeyProperties;
import android.util.Log;

Expand All @@ -32,7 +33,6 @@
import org.bouncycastle.asn1.ASN1TaggedObject;

import java.security.cert.CertificateParsingException;
import java.text.DateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -591,6 +591,22 @@ public static String ecCurveAsString(Integer ecCurve) {
};
}

public static String osVersionToString(int osVersion) {
int major = osVersion / 10000;
int minor = (osVersion / 100) % 100;
int subMinor = osVersion % 100;
return "Android " + major + "." + minor + "." + subMinor + " (" + osVersion + ")";
}

public static String patchLevelToString(int patchLevel) {
var s = Integer.toString(patchLevel);
return switch (s.length()) {
case 6 -> s.substring(0, 4) + "-" + s.substring(4, 6);
case 8 -> s.substring(0, 4) + "-" + s.substring(4, 6) + "-" + s.substring(6, 8);
default -> s;
};
}

public Integer getSecurityLevel() {
return securityLevel;
}
Expand Down Expand Up @@ -711,6 +727,39 @@ public Integer getOsPatchLevel() {
return osPatchLevel;
}

public boolean isPatchLevelOutdated() {
try {
int device = Integer.parseInt(
Build.VERSION.SECURITY_PATCH.substring(0, 7).replace("-", ""));
return isOlder(osPatchLevel, device)
|| isOlder(vendorPatchLevel, device)
|| isOlder(bootPatchLevel, device);
} catch (RuntimeException e) {
return false;
}
}

// Normalize YYYYMMDD to YYYYMM
private static int toMonth(int patchLevel) {
return patchLevel > 999999 ? patchLevel / 100 : patchLevel;
}

private static boolean isOlder(Integer patchLevel, int device) {
return patchLevel != null && toMonth(patchLevel) < device;
}

public String getOldestPatchLevel() {
Integer oldest = null;
for (var patchLevel : new Integer[]{osPatchLevel, vendorPatchLevel, bootPatchLevel}) {
if (patchLevel == null) continue;
int month = toMonth(patchLevel);
if (oldest == null || month < oldest) oldest = month;
}
if (oldest == null) return null;
var s = oldest.toString();
return s.length() == 6 ? s.substring(0, 4) + "-" + s.substring(4) : s;
}

public AttestationApplicationId getAttestationApplicationId() {
return attestationApplicationId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@

package io.github.vvb2060.keyattestation.attestation;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborEncoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.Array;
import co.nstant.in.cbor.model.ByteString;
Expand All @@ -30,15 +36,6 @@
import co.nstant.in.cbor.model.UnicodeString;
import co.nstant.in.cbor.model.UnsignedInteger;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


class CborUtils {
public static Number toNumber(long l) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,19 @@

import android.util.Log;

import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.Number;
import co.nstant.in.cbor.model.UnicodeString;

import org.bouncycastle.asn1.ASN1Encodable;

import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;

import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.Number;
import co.nstant.in.cbor.model.UnicodeString;

public class EatAttestation extends Attestation {
static final String TAG = "EatAttestation";
final Map extension;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
package io.github.vvb2060.keyattestation.attestation;

import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.*;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KEYMASTER_TAG_TYPE_MASK;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ACTIVE_DATETIME;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ALGORITHM;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ALLOW_WHILE_ON_BODY;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_APPLICATION_ID;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ATTESTATION_APPLICATION_ID;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ATTESTATION_ID_BRAND;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ATTESTATION_ID_DEVICE;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ATTESTATION_ID_MANUFACTURER;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ATTESTATION_ID_MEID;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ATTESTATION_ID_MODEL;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ATTESTATION_ID_PRODUCT;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ATTESTATION_ID_SERIAL;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_AUTH_TIMEOUT;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_BLOCK_MODE;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_BOOT_PATCHLEVEL;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_CALLER_NONCE;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_DEVICE_UNIQUE_ATTESTATION;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_DIGEST;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_EARLY_BOOT_ONLY;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_EC_CURVE;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_IDENTITY_CREDENTIAL_KEY;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_KDF;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_KEY_SIZE;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_MIN_MAC_LENGTH;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_NO_AUTH_REQUIRED;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ORIGIN;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ORIGINATION_EXPIRE_DATETIME;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_OS_PATCHLEVEL;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_OS_VERSION;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_PADDING;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_PURPOSE;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ROLLBACK_RESISTANCE;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_ROLLBACK_RESISTANT;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_RSA_OAEP_MGF_DIGEST;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_RSA_PUBLIC_EXPONENT;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_UNLOCKED_DEVICE_REQUIRED;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_USAGE_EXPIRE_DATETIME;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_USER_AUTH_TYPE;
import static io.github.vvb2060.keyattestation.attestation.AuthorizationList.KM_TAG_VENDOR_PATCHLEVEL;

class EatClaim {
public static final int IAT = 6;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ private static NetworkResult fetchFromNetwork(String statusUrl, long cachedTime)
URL url = new URL(statusUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(3000);
connection.setReadTimeout(3000);
connection.setConnectTimeout(10_000);
connection.setReadTimeout(20_000);
connection.setRequestProperty("User-Agent", "KeyAttestation");

double rand = Math.round(Math.random() * 1000.0) / 1000.0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ public RootOfTrust(ASN1Encodable asn1Encodable) throws CertificateParsingExcepti
deviceLocked = Asn1Utils.getBooleanFromAsn1(sequence.getObjectAt(DEVICE_LOCKED_INDEX));
verifiedBootState =
Asn1Utils.getIntegerFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_STATE_INDEX));
if (sequence.size() == 3) verifiedBootHash = null;
else verifiedBootHash =
Asn1Utils.getByteArrayFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_HASH_INDEX));
if (sequence.size() == 3) {
verifiedBootHash = null;
} else {
var hash = Asn1Utils.getByteArrayFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_HASH_INDEX));
verifiedBootHash = hash.length == 0 ? null : hash;
}
}

RootOfTrust(byte[] verifiedBootKey, boolean deviceLocked,
Expand Down Expand Up @@ -94,6 +97,20 @@ public byte[] getVerifiedBootHash() {
return verifiedBootHash;
}

private static boolean isAllZero(byte[] data) {
for (var b : data) {
if (b != 0) return false;
}
return true;
}

public boolean isVerifiedBootKeySuspicious() {
if (verifiedBootKey == null || !isAllZero(verifiedBootKey)) {
return false;
}
return deviceLocked || verifiedBootState == KM_VERIFIED_BOOT_VERIFIED;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public enum Status {
ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+\
NpUFgNPN9PvQi8WEg5UmAGMCAwEAAQ==""";

// New P-384 KeyMint attestation root (CN=Key Attestation CA1)
// RKP devices use it exclusively
// https://developer.android.com/privacy-and-security/security-key-attestation#root_certificate_rotation
private static final String GOOGLE_RKP_ROOT_PUBLIC_KEY = """
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEI9ojcU7fPlsFCjxy6IRqzgeOoK0b+YsV\
9FPQywiyw8EQRTkJ9u3qwfnI4DGoSLlBqClTXJfgfCcZvs60FikNMHnu4fkRzObf\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import android.content.res.ColorStateList
import android.view.View
import androidx.core.view.isVisible
import io.github.vvb2060.keyattestation.R
import io.github.vvb2060.keyattestation.repository.AttestationData
import io.github.vvb2060.keyattestation.attestation.RootOfTrust
import io.github.vvb2060.keyattestation.databinding.HomeHeaderBinding
import io.github.vvb2060.keyattestation.repository.AttestationData
import rikka.core.res.resolveColor

class BootStateViewHolder(itemView: View, binding: HomeHeaderBinding) :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,25 @@ import io.github.vvb2060.keyattestation.attestation.CertificateInfo
import io.github.vvb2060.keyattestation.attestation.RootPublicKey
import io.github.vvb2060.keyattestation.databinding.HomeCommonItemBinding
import rikka.core.res.resolveColorStateList
import rikka.recyclerview.BaseViewHolder.Creator
import java.util.Date
import javax.security.auth.x500.X500Principal

open class CommonItemViewHolder<T>(itemView: View, binding: HomeCommonItemBinding) :
HomeViewHolder<T, HomeCommonItemBinding>(itemView, binding) {

companion object {
private fun copyRootOfTrustHex(context: Context, rootOfTrust: String): Boolean {
if (!rootOfTrust.contains("verifiedBootKey:")) return false
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.setPrimaryClip(ClipData.newPlainText("rootOfTrust", rootOfTrust))
Toast.makeText(
context,
context.getString(R.string.copied_to_clipboard),
Toast.LENGTH_SHORT
).show()
return true
}

val SIMPLE_CREATOR = Creator<Pair<String, String>> { inflater, parent ->
val binding = HomeCommonItemBinding.inflate(inflater, parent, false)
object : CommonItemViewHolder<Pair<String, String>>(binding.root, binding) {
Expand All @@ -37,7 +48,11 @@ open class CommonItemViewHolder<T>(itemView: View, binding: HomeCommonItemBindin

override fun onBind() {
binding.title.text = data.first
binding.summary.text = data.second
if (data.second.isEmpty()) {
binding.summary.setText(R.string.empty)
} else {
binding.summary.text = data.second
}
}
}
}
Expand Down Expand Up @@ -97,29 +112,11 @@ open class CommonItemViewHolder<T>(itemView: View, binding: HomeCommonItemBindin
init {
this.binding.apply {
root.setOnClickListener {
if (data.data.contains("verifiedBootHash:")) {
val lines = data.data.lines()
for (line in lines) {
if (line.startsWith("verifiedBootHash:")) {
val verifiedBootHash = line.substringAfter(":").trim()
val clipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.setPrimaryClip(
ClipData.newPlainText(
"verifiedBootHash",
verifiedBootHash
)
)
Toast.makeText(
context,
context.getString(R.string.copied_verifiedBootHash_to_clipboard),
Toast.LENGTH_SHORT
).show()
}
}
}
listener.onCommonDataClick(data)
}
root.setOnLongClickListener {
copyRootOfTrustHex(context, data.data)
}
icon.isVisible = false
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class HeaderData(
override val title: Int,
override val description: Int,
val icon: Int,
val color: Int
val color: Int,
vararg val formatArgs: Any
) : Data()

class AuthorizationItemData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.view.View
import androidx.core.view.isVisible
import io.github.vvb2060.keyattestation.databinding.HomeHeaderBinding
import rikka.core.res.resolveColorStateList
import rikka.recyclerview.BaseViewHolder.Creator

class HeaderViewHolder(itemView: View, binding: HomeHeaderBinding) : HomeViewHolder<HeaderData, HomeHeaderBinding>(itemView, binding) {

Expand All @@ -23,7 +22,7 @@ class HeaderViewHolder(itemView: View, binding: HomeHeaderBinding) : HomeViewHol
icon.setImageDrawable(context.getDrawable(data.icon))
title.setText(data.title)
if (data.description != 0) {
summary.setText(data.description)
summary.text = context.getString(data.description, *data.formatArgs)
summary.isVisible = true
} else {
summary.isVisible = false
Expand Down
Loading