Revert "KeyboxImitationHooks: Abandon generated certs"

This reverts commit 4da4b393a71c2f73fe49100bc94ffce60db7b2af.
This commit is contained in:
Joey
2025-11-28 11:08:02 +09:00
parent 49f57ced33
commit c3edd35b29
5 changed files with 738 additions and 199 deletions

View File

@@ -0,0 +1,475 @@
/*
* SPDX-FileCopyrightText: 2025 Neoteric OS
* SPDX-License-Identifier: Apache-2.0
*/
package com.android.internal.util.evolution;
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.hardware.security.keymint.Algorithm;
import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.KeyOrigin;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.Tag;
import android.os.Binder;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
import android.security.keystore.KeyProperties;
import android.system.keystore2.KeyDescriptor;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.internal.org.bouncycastle.asn1.ASN1Boolean;
import com.android.internal.org.bouncycastle.asn1.ASN1Encodable;
import com.android.internal.org.bouncycastle.asn1.ASN1Enumerated;
import com.android.internal.org.bouncycastle.asn1.ASN1Integer;
import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
import com.android.internal.org.bouncycastle.asn1.ASN1OctetString;
import com.android.internal.org.bouncycastle.asn1.ASN1Sequence;
import com.android.internal.org.bouncycastle.asn1.DERNull;
import com.android.internal.org.bouncycastle.asn1.DEROctetString;
import com.android.internal.org.bouncycastle.asn1.DERSequence;
import com.android.internal.org.bouncycastle.asn1.DERSet;
import com.android.internal.org.bouncycastle.asn1.DERTaggedObject;
import com.android.internal.org.bouncycastle.asn1.x500.X500Name;
import com.android.internal.org.bouncycastle.asn1.x509.Extension;
import com.android.internal.org.bouncycastle.asn1.x509.KeyUsage;
import com.android.internal.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import com.android.internal.org.bouncycastle.asn1.x509.Time;
import com.android.internal.org.bouncycastle.cert.X509CertificateHolder;
import com.android.internal.org.bouncycastle.cert.X509v3CertificateBuilder;
import com.android.internal.org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.android.internal.org.bouncycastle.operator.ContentSigner;
import com.android.internal.org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
/**
* @hide
*/
public final class KeyboxChainGenerator {
private static final String TAG = "KeyboxChainGenerator";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX = 0;
private static final int ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX = 1;
private static final int ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX = 0;
private static final int ATTESTATION_PACKAGE_INFO_VERSION_INDEX = 1;
public static List<Certificate> generateCertChain(int uid, KeyDescriptor descriptor, KeyGenParameters params) {
dlog("Requested KeyPair with alias: " + descriptor.alias);
int size = params.keySize;
KeyPair kp;
try {
if (Objects.equals(params.algorithm, Algorithm.EC)) {
dlog("Generating EC keypair of size " + size);
kp = buildECKeyPair(params);
} else if (Objects.equals(params.algorithm, Algorithm.RSA)) {
dlog("Generating RSA keypair of size " + size);
kp = buildRSAKeyPair(params);
} else {
dlog("Unsupported algorithm");
return null;
}
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
KeyboxUtils.getCertificateHolder(
Objects.equals(params.algorithm, Algorithm.EC)
? KeyProperties.KEY_ALGORITHM_EC
: KeyProperties.KEY_ALGORITHM_RSA
).getSubject(),
params.certificateSerial,
new Time(params.certificateNotBefore),
new Time(params.certificateNotAfter),
params.certificateSubject,
SubjectPublicKeyInfo.getInstance(
ASN1Sequence.getInstance(kp.getPublic().getEncoded())
)
);
KeyUsage keyUsage = new KeyUsage(KeyUsage.keyCertSign);
certBuilder.addExtension(Extension.keyUsage, true, keyUsage);
certBuilder.addExtension(createExtension(params, uid));
ContentSigner contentSigner;
if (Objects.equals(params.algorithm, Algorithm.EC)) {
contentSigner = new JcaContentSignerBuilder("SHA256withECDSA").build(KeyboxUtils.getPrivateKey(KeyProperties.KEY_ALGORITHM_EC));
} else {
contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(KeyboxUtils.getPrivateKey(KeyProperties.KEY_ALGORITHM_RSA));
}
X509CertificateHolder certHolder = certBuilder.build(contentSigner);
Certificate leaf = KeyboxUtils.getCertificateFromHolder(certHolder);
List<Certificate> chain = KeyboxUtils.getCertificateChain(leaf.getPublicKey().getAlgorithm());
chain.add(0, leaf);
dlog("Successfully generated X500 Cert for alias: " + descriptor.alias);
return chain;
} catch (Throwable t) {
Log.e(TAG, Log.getStackTraceString(t));
}
return null;
}
private static ASN1Encodable[] fromIntList(List<Integer> list) {
ASN1Encodable[] result = new ASN1Encodable[list.size()];
for (int i = 0; i < list.size(); i++) {
result[i] = new ASN1Integer(list.get(i));
}
return result;
}
private static Extension createExtension(KeyGenParameters params, int uid) {
try {
Context context = ActivityThread.currentApplication();
if (context == null) {
Log.e(TAG, "Context is null in createExtension");
return null;
}
SecureRandom secureRandom = new SecureRandom();
String key = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.VBOOT_KEY);
byte[] verifiedBootKey;
if (key == null) {
byte[] randomBytes = new byte[32];
secureRandom.nextBytes(randomBytes);
String encoded = Base64.encodeToString(randomBytes, Base64.NO_WRAP);
Settings.Secure.putString(context.getContentResolver(), Settings.Secure.VBOOT_KEY, encoded);
verifiedBootKey = randomBytes;
} else {
verifiedBootKey = Base64.decode(key, Base64.NO_WRAP);
}
String hash = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.VBOOT_HASH);
byte[] verifiedBootHash;
if (hash == null) {
byte[] randomBytes = new byte[32];
secureRandom.nextBytes(randomBytes);
String encoded = Base64.encodeToString(randomBytes, Base64.NO_WRAP);
Settings.Secure.putString(context.getContentResolver(), Settings.Secure.VBOOT_HASH, encoded);
verifiedBootHash = randomBytes;
} else {
verifiedBootHash = Base64.decode(hash, Base64.NO_WRAP);
}
ASN1Encodable[] rootOfTrustEncodables = {
new DEROctetString(verifiedBootKey),
ASN1Boolean.TRUE,
new ASN1Enumerated(0),
new DEROctetString(verifiedBootHash)
};
ASN1Sequence rootOfTrustSeq = new DERSequence(rootOfTrustEncodables);
var Apurpose = new DERSet(fromIntList(params.purpose));
var Aalgorithm = new ASN1Integer(params.algorithm);
var AkeySize = new ASN1Integer(params.keySize);
var Adigest = new DERSet(fromIntList(params.digest));
var AecCurve = new ASN1Integer(params.ecCurve);
var AnoAuthRequired = DERNull.INSTANCE;
var Aorigin = new ASN1Integer(0);
// To be loaded
var AosVersion = new ASN1Integer(getOsVersion());
var AosPatchLevel = new ASN1Integer(getPatchLevel());
var AbootPatchlevel = new ASN1Integer(getPatchLevelLong());
var AvendorPatchLevel = new ASN1Integer(getPatchLevelLong());
var purpose = new DERTaggedObject(true, 1, Apurpose);
var algorithm = new DERTaggedObject(true, 2, Aalgorithm);
var keySize = new DERTaggedObject(true, 3, AkeySize);
var digest = new DERTaggedObject(true, 5, Adigest);
var ecCurve = new DERTaggedObject(true, 10, AecCurve);
var noAuthRequired = new DERTaggedObject(true, 503, AnoAuthRequired);
var origin = new DERTaggedObject(true, 702, Aorigin);
var rootOfTrust = new DERTaggedObject(true, 704, rootOfTrustSeq);
var osVersion = new DERTaggedObject(true, 705, AosVersion);
var osPatchLevel = new DERTaggedObject(true, 706, AosPatchLevel);
var vendorPatchLevel = new DERTaggedObject(true, 718, AvendorPatchLevel);
var bootPatchLevel = new DERTaggedObject(true, 719, AbootPatchlevel);
ASN1Encodable[] teeEnforcedEncodables;
// Support device properties attestation
if (params.brand != null) {
var Abrand = new DEROctetString(params.brand);
var Adevice = new DEROctetString(params.device);
var Aproduct = new DEROctetString(params.product);
var Amanufacturer = new DEROctetString(params.manufacturer);
var Amodel = new DEROctetString(params.model);
var brand = new DERTaggedObject(true, 710, Abrand);
var device = new DERTaggedObject(true, 711, Adevice);
var product = new DERTaggedObject(true, 712, Aproduct);
var manufacturer = new DERTaggedObject(true, 716, Amanufacturer);
var model = new DERTaggedObject(true, 717, Amodel);
teeEnforcedEncodables = new ASN1Encodable[]{purpose, algorithm, keySize, digest, ecCurve,
noAuthRequired, origin, rootOfTrust, osVersion, osPatchLevel, vendorPatchLevel,
bootPatchLevel, brand, device, product, manufacturer, model};
} else {
teeEnforcedEncodables = new ASN1Encodable[]{purpose, algorithm, keySize, digest, ecCurve,
noAuthRequired, origin, rootOfTrust, osVersion, osPatchLevel, vendorPatchLevel,
bootPatchLevel};
}
var AcreationDateTime = new ASN1Integer(System.currentTimeMillis());
var AapplicationID = createApplicationId(uid);
var creationDateTime = new DERTaggedObject(true, 701, AcreationDateTime);
var applicationID = new DERTaggedObject(true, 709, AapplicationID);
ASN1Encodable[] softwareEnforced = {creationDateTime, applicationID};
ASN1OctetString keyDescriptionOctetStr = getAsn1OctetString(teeEnforcedEncodables, softwareEnforced, params);
return new Extension(new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17"), false, keyDescriptionOctetStr);
} catch (Throwable t) {
Log.e(TAG, Log.getStackTraceString(t));
}
return null;
}
private static int getOsVersion() {
String release = Build.VERSION.RELEASE;
int major = 0, minor = 0, patch = 0;
String[] parts = release.split("\\.");
if (parts.length > 0) major = Integer.parseInt(parts[0]);
if (parts.length > 1) minor = Integer.parseInt(parts[1]);
if (parts.length > 2) patch = Integer.parseInt(parts[2]);
return major * 10000 + minor * 100 + patch;
}
private static int getPatchLevel() {
return convertPatchLevel(Build.VERSION.SECURITY_PATCH, false);
}
private static int getPatchLevelLong() {
return convertPatchLevel(Build.VERSION.SECURITY_PATCH, true);
}
private static int convertPatchLevel(String patchLevel, boolean longFormat) {
try {
String[] parts = patchLevel.split("-");
int year = Integer.parseInt(parts[0]);
int month = Integer.parseInt(parts[1]);
if (longFormat) {
int day = Integer.parseInt(parts[2]);
return year * 10000 + month * 100 + day;
} else {
return year * 100 + month;
}
} catch (Exception e) {
Log.e(TAG, "Invalid patch level: " + patchLevel, e);
return 202404;
}
}
private static ASN1OctetString getAsn1OctetString(ASN1Encodable[] teeEnforcedEncodables, ASN1Encodable[] softwareEnforcedEncodables, KeyGenParameters params) throws IOException {
ASN1Integer attestationVersion = new ASN1Integer(100);
ASN1Enumerated attestationSecurityLevel = new ASN1Enumerated(1);
ASN1Integer keymasterVersion = new ASN1Integer(100);
ASN1Enumerated keymasterSecurityLevel = new ASN1Enumerated(1);
ASN1OctetString attestationChallenge = new DEROctetString(params.attestationChallenge);
ASN1OctetString uniqueId = new DEROctetString(new byte[0]);
ASN1Encodable softwareEnforced = new DERSequence(softwareEnforcedEncodables);
ASN1Sequence teeEnforced = new DERSequence(teeEnforcedEncodables);
ASN1Encodable[] keyDescriptionEncodables = {attestationVersion, attestationSecurityLevel, keymasterVersion,
keymasterSecurityLevel, attestationChallenge, uniqueId, softwareEnforced, teeEnforced};
ASN1Sequence keyDescriptionHackSeq = new DERSequence(keyDescriptionEncodables);
return new DEROctetString(keyDescriptionHackSeq);
}
private static DEROctetString createApplicationId(int uid) throws Throwable {
Context context = ActivityThread.currentApplication();
if (context == null) {
throw new IllegalStateException("createApplicationId: context not available from ActivityThread!");
}
PackageManager pm = context.getPackageManager();
if (pm == null) {
throw new IllegalStateException("createApplicationId: PackageManager not found!");
}
String[] packages = pm.getPackagesForUid(uid);
if (packages == null || packages.length == 0) {
throw new IllegalStateException("No packages found for UID: " + uid);
}
int size = packages.length;
ASN1Encodable[] packageInfoAA = new ASN1Encodable[size];
Set<Digest> signatures = new HashSet<>();
MessageDigest dg = MessageDigest.getInstance("SHA-256");
for (int i = 0; i < size; i++) {
String name = packages[i];
PackageInfo info = pm.getPackageInfo(name, PackageManager.GET_SIGNATURES);
ASN1Encodable[] arr = new ASN1Encodable[2];
arr[ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX] =
new DEROctetString(name.getBytes(StandardCharsets.UTF_8));
arr[ATTESTATION_PACKAGE_INFO_VERSION_INDEX] =
new ASN1Integer(info.getLongVersionCode());
packageInfoAA[i] = new DERSequence(arr);
if (info != null && info.signatures != null) {
for (Signature s : info.signatures) {
if (s != null) signatures.add(new Digest(dg.digest(s.toByteArray())));
}
}
}
ASN1Encodable[] signaturesAA = new ASN1Encodable[signatures.size()];
int i = 0;
for (Digest d : signatures) {
signaturesAA[i++] = new DEROctetString(d.digest);
}
ASN1Encodable[] applicationIdAA = new ASN1Encodable[2];
applicationIdAA[ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX] =
new DERSet(packageInfoAA);
applicationIdAA[ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX] =
new DERSet(signaturesAA);
return new DEROctetString(new DERSequence(applicationIdAA).getEncoded());
}
record Digest(byte[] digest) {
@Override
public boolean equals(@Nullable Object o) {
if (o instanceof Digest d)
return Arrays.equals(digest, d.digest);
return false;
}
@Override
public int hashCode() {
return Arrays.hashCode(digest);
}
}
private static KeyPair buildECKeyPair(KeyGenParameters params) throws Exception {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
Security.addProvider(new BouncyCastleProvider());
ECGenParameterSpec spec = new ECGenParameterSpec(params.ecCurveName);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
kpg.initialize(spec);
return kpg.generateKeyPair();
}
private static KeyPair buildRSAKeyPair(KeyGenParameters params) throws Exception {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
Security.addProvider(new BouncyCastleProvider());
RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(
params.keySize, params.rsaPublicExponent);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
kpg.initialize(spec);
return kpg.generateKeyPair();
}
private static void dlog(String msg) {
if (DEBUG) Log.d(TAG, msg);
}
public static class KeyGenParameters {
public int keySize;
public int algorithm;
public BigInteger certificateSerial;
public Date certificateNotBefore;
public Date certificateNotAfter;
public X500Name certificateSubject;
public BigInteger rsaPublicExponent;
public int ecCurve;
public String ecCurveName;
public List<Integer> purpose = new ArrayList<>();
public List<Integer> digest = new ArrayList<>();
public byte[] attestationChallenge;
public byte[] brand;
public byte[] device;
public byte[] product;
public byte[] manufacturer;
public byte[] model;
public int securityLevel;
public boolean noAuthRequired;
// Extra fields for response metadata
public int osVersion = KeyboxChainGenerator.getOsVersion();
public int osPatchLevel = KeyboxChainGenerator.getPatchLevel();
public int vendorPatchLevel = KeyboxChainGenerator.getPatchLevelLong();
public int bootPatchLevel = KeyboxChainGenerator.getPatchLevelLong();
public long creationDateTime = System.currentTimeMillis();
public int userId = UserHandle.myUserId();
public int origin = KeyOrigin.GENERATED;
public KeyGenParameters(KeyParameter[] params) {
for (KeyParameter kp : params) {
switch (kp.tag) {
case Tag.KEY_SIZE -> keySize = kp.value.getInteger();
case Tag.ALGORITHM -> algorithm = kp.value.getAlgorithm();
case Tag.CERTIFICATE_SERIAL -> certificateSerial = new BigInteger(kp.value.getBlob());
case Tag.CERTIFICATE_NOT_BEFORE -> certificateNotBefore = new Date(kp.value.getDateTime());
case Tag.CERTIFICATE_NOT_AFTER -> certificateNotAfter = new Date(kp.value.getDateTime());
case Tag.CERTIFICATE_SUBJECT ->
certificateSubject = new X500Name(new X500Principal(kp.value.getBlob()).getName());
case Tag.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = BigInteger.valueOf(kp.value.getLongInteger());
case Tag.EC_CURVE -> {
ecCurve = kp.value.getEcCurve();
ecCurveName = getEcCurveName(ecCurve);
}
case Tag.PURPOSE -> purpose.add(kp.value.getKeyPurpose());
case Tag.DIGEST -> digest.add(kp.value.getDigest());
case Tag.ATTESTATION_CHALLENGE -> attestationChallenge = kp.value.getBlob();
case Tag.ATTESTATION_ID_BRAND -> brand = kp.value.getBlob();
case Tag.ATTESTATION_ID_DEVICE -> device = kp.value.getBlob();
case Tag.ATTESTATION_ID_PRODUCT -> product = kp.value.getBlob();
case Tag.ATTESTATION_ID_MANUFACTURER -> manufacturer = kp.value.getBlob();
case Tag.ATTESTATION_ID_MODEL -> model = kp.value.getBlob();
case Tag.HARDWARE_TYPE -> securityLevel = kp.value.getSecurityLevel();
case Tag.NO_AUTH_REQUIRED -> noAuthRequired = kp.value.getBoolValue();
}
}
}
private static String getEcCurveName(int curve) {
return switch (curve) {
case EcCurve.CURVE_25519 -> "CURVE_25519";
case EcCurve.P_224 -> "secp224r1";
case EcCurve.P_256 -> "secp256r1";
case EcCurve.P_384 -> "secp384r1";
case EcCurve.P_521 -> "secp521r1";
default -> throw new IllegalArgumentException("unknown curve");
};
}
}
}

View File

@@ -5,37 +5,27 @@
*/
package com.android.internal.util.evolution;
import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import android.security.KeyChain;
import android.security.keystore.KeyProperties;
import android.hardware.security.keymint.Algorithm;
import android.hardware.security.keymint.KeyOrigin;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.KeyParameterValue;
import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.Tag;
import android.os.Binder;
import android.system.keystore2.Authorization;
import android.system.keystore2.IKeystoreSecurityLevel;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyEntryResponse;
import android.util.Base64;
import android.system.keystore2.KeyMetadata;
import android.util.Log;
import com.android.internal.org.bouncycastle.asn1.ASN1Boolean;
import com.android.internal.org.bouncycastle.asn1.ASN1Encodable;
import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
import com.android.internal.org.bouncycastle.asn1.ASN1Enumerated;
import com.android.internal.org.bouncycastle.asn1.ASN1Integer;
import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
import com.android.internal.org.bouncycastle.asn1.ASN1OctetString;
import com.android.internal.org.bouncycastle.asn1.ASN1Sequence;
import com.android.internal.org.bouncycastle.asn1.ASN1TaggedObject;
import com.android.internal.org.bouncycastle.asn1.DEROctetString;
import com.android.internal.org.bouncycastle.asn1.DERSequence;
import com.android.internal.org.bouncycastle.asn1.DERTaggedObject;
import com.android.internal.org.bouncycastle.asn1.x509.Extension;
import com.android.internal.org.bouncycastle.cert.X509CertificateHolder;
import com.android.internal.org.bouncycastle.cert.X509v3CertificateBuilder;
import com.android.internal.org.bouncycastle.operator.ContentSigner;
import com.android.internal.org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import com.android.internal.util.evolution.KeyboxChainGenerator.KeyGenParameters;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* @hide
@@ -45,177 +35,186 @@ public class KeyboxImitationHooks {
private static final String TAG = "KeyboxImitationHooks";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final ASN1ObjectIdentifier KEY_ATTESTATION_OID = new ASN1ObjectIdentifier(
"1.3.6.1.4.1.11129.2.1.17");
public static KeyEntryResponse onGetKeyEntry(KeyEntryResponse response) {
public static KeyEntryResponse onGetKeyEntry(KeyDescriptor descriptor) {
if (!KeyProviderManager.isKeyboxAvailable()) {
dlog("Key attestation spoofing is disabled because no keybox is defined to spoof");
return response;
}
if (response == null || response.metadata == null) return response;
try {
if (response.metadata.certificate == null) {
Log.e(TAG, "Certificate is null, skipping modification");
return response;
}
X509Certificate certificate = KeyChain.toCertificate(response.metadata.certificate);
if (certificate.getExtensionValue(KEY_ATTESTATION_OID.getId()) == null) {
Log.e(TAG, "Key attestation OID not found, skipping modification");
return response;
}
String keyAlgorithm = certificate.getPublicKey().getAlgorithm();
response.metadata.certificate = modifyLeafCertificate(certificate, keyAlgorithm);
response.metadata.certificateChain = KeyboxUtils.getCertificateChain(keyAlgorithm);
} catch (Exception e) {
Log.e(TAG, "Error in onGetKeyEntry", e);
}
return response;
}
private static byte[] modifyLeafCertificate(X509Certificate leafCertificate,
String keyAlgorithm) throws Exception {
X509CertificateHolder certificateHolder = new X509CertificateHolder(
leafCertificate.getEncoded());
Extension keyAttestationExtension = certificateHolder.getExtension(KEY_ATTESTATION_OID);
ASN1Sequence keyAttestationSequence = ASN1Sequence.getInstance(
keyAttestationExtension.getExtnValue().getOctets());
ASN1Encodable[] keyAttestationEncodables = keyAttestationSequence.toArray();
ASN1Sequence teeEnforcedSequence = (ASN1Sequence) keyAttestationEncodables[7];
ASN1EncodableVector teeEnforcedVector = new ASN1EncodableVector();
for (ASN1Encodable teeEnforcedEncodable : teeEnforcedSequence) {
ASN1TaggedObject taggedObject = (ASN1TaggedObject) teeEnforcedEncodable;
int tag = taggedObject.getTagNo();
if (tag == 704 || tag == 705 || tag == 706 || tag == 718 || tag == 719) {
continue;
}
teeEnforcedVector.add(teeEnforcedEncodable);
}
PrivateKey privateKey = KeyboxUtils.getPrivateKey(keyAlgorithm);
X509CertificateHolder providerCertHolder = KeyboxUtils.getCertificateHolder(keyAlgorithm);
X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(
providerCertHolder.getSubject(),
certificateHolder.getSerialNumber(),
certificateHolder.getNotBefore(),
certificateHolder.getNotAfter(),
certificateHolder.getSubject(),
certificateHolder.getSubjectPublicKeyInfo()
);
ContentSigner contentSigner = new JcaContentSignerBuilder(
leafCertificate.getSigAlgName()).build(privateKey);
Context context = ActivityThread.currentApplication();
if (context == null) {
Log.e(TAG, "Context is null in modifyLeafCertificate");
return null;
}
SecureRandom secureRandom = new SecureRandom();
String key = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.VBOOT_KEY);
byte[] verifiedBootKey;
if (key == null) {
byte[] randomBytes = new byte[32];
secureRandom.nextBytes(randomBytes);
String encoded = Base64.encodeToString(randomBytes, Base64.NO_WRAP);
Settings.Secure.putString(context.getContentResolver(), Settings.Secure.VBOOT_KEY, encoded);
verifiedBootKey = randomBytes;
} else {
verifiedBootKey = Base64.decode(key, Base64.NO_WRAP);
KeyEntryResponse spoofed = KeyboxUtils.retrieve(Binder.getCallingUid(), descriptor.alias);
if (spoofed != null) {
dlog("Key entry spoofed");
return spoofed;
}
String hash = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.VBOOT_HASH);
byte[] verifiedBootHash;
if (hash == null) {
byte[] randomBytes = new byte[32];
secureRandom.nextBytes(randomBytes);
String encoded = Base64.encodeToString(randomBytes, Base64.NO_WRAP);
Settings.Secure.putString(context.getContentResolver(), Settings.Secure.VBOOT_HASH, encoded);
verifiedBootHash = randomBytes;
} else {
verifiedBootHash = Base64.decode(hash, Base64.NO_WRAP);
return null;
}
public static KeyMetadata generateKey(IKeystoreSecurityLevel level, KeyDescriptor descriptor, Collection<KeyParameter> args) {
if (!KeyProviderManager.isKeyboxAvailable()) {
return null;
}
ASN1Encodable[] rootOfTrustEncodables = {
new DEROctetString(verifiedBootKey),
ASN1Boolean.TRUE,
new ASN1Enumerated(0),
new DEROctetString(verifiedBootHash)
};
KeyGenParameters params = new KeyGenParameters(args.toArray(new KeyParameter[args.size()]));
ASN1Sequence newRootOfTrustSequence = new DERSequence(rootOfTrustEncodables);
ASN1TaggedObject rootOfTrustTaggedObject = new DERTaggedObject(704, newRootOfTrustSequence);
teeEnforcedVector.add(rootOfTrustTaggedObject);
teeEnforcedVector.add(new DERTaggedObject(705,
new ASN1Integer(getOsVersion())));
teeEnforcedVector.add(new DERTaggedObject(706,
new ASN1Integer(getPatchLevel())));
teeEnforcedVector.add(new DERTaggedObject(718,
new ASN1Integer(getPatchLevelLong())));
teeEnforcedVector.add(new DERTaggedObject(719,
new ASN1Integer(getPatchLevelLong())));
ASN1Sequence newTeeEnforcedSequence = new DERSequence(teeEnforcedVector);
keyAttestationEncodables[7] = newTeeEnforcedSequence;
ASN1Sequence newKeyAttestationSequence = new DERSequence(keyAttestationEncodables);
ASN1OctetString newKeyAttestationOctetString = new DEROctetString(
newKeyAttestationSequence);
Extension newKeyAttestationExtension = new Extension(KEY_ATTESTATION_OID, false,
newKeyAttestationOctetString);
certificateBuilder.addExtension(newKeyAttestationExtension);
for (ASN1ObjectIdentifier extensionOID :
certificateHolder.getExtensions().getExtensionOIDs()) {
if (KEY_ATTESTATION_OID.getId().equals(extensionOID.getId())) continue;
certificateBuilder.addExtension(certificateHolder.getExtension(extensionOID));
if (params.attestationChallenge == null) {
return null;
}
return certificateBuilder.build(contentSigner).getEncoded();
}
if (params.purpose == null || !params.purpose.contains(KeyPurpose.SIGN)) {
return null;
}
private static int getOsVersion() {
String release = Build.VERSION.RELEASE;
int major = 0, minor = 0, patch = 0;
if (!params.noAuthRequired) {
return null;
}
String[] parts = release.split("\\.");
if (parts.length > 0) major = Integer.parseInt(parts[0]);
if (parts.length > 1) minor = Integer.parseInt(parts[1]);
if (parts.length > 2) patch = Integer.parseInt(parts[2]);
if (params.algorithm != Algorithm.EC && params.algorithm != Algorithm.RSA) {
Log.w(TAG, "Unsupported algorithm: " + params.algorithm);
return null;
}
return major * 10000 + minor * 100 + patch;
}
private static int getPatchLevel() {
return convertPatchLevel(Build.VERSION.SECURITY_PATCH, false);
}
private static int getPatchLevelLong() {
return convertPatchLevel(Build.VERSION.SECURITY_PATCH, true);
}
private static int convertPatchLevel(String patchLevel, boolean longFormat) {
int uid = Binder.getCallingUid();
try {
String[] parts = patchLevel.split("-");
int year = Integer.parseInt(parts[0]);
int month = Integer.parseInt(parts[1]);
if (longFormat) {
int day = Integer.parseInt(parts[2]);
return year * 10000 + month * 100 + day;
} else {
return year * 100 + month;
List<Certificate> chain = KeyboxChainGenerator.generateCertChain(uid, descriptor, params);
if (chain == null || chain.isEmpty()) {
return null;
}
KeyEntryResponse response = buildResponse(level, chain, params, descriptor);
if (response == null) {
return null;
}
KeyboxUtils.append(uid, descriptor.alias, response);
return response.metadata;
} catch (Exception e) {
Log.e(TAG, "Invalid patch level: " + patchLevel, e);
return 202404;
Log.e(TAG, "Failed to generate key", e);
return null;
}
}
private static KeyEntryResponse buildResponse(
IKeystoreSecurityLevel level,
List<Certificate> chain,
KeyGenParameters params,
KeyDescriptor descriptor
) {
try {
KeyEntryResponse response = new KeyEntryResponse();
KeyMetadata metadata = new KeyMetadata();
metadata.keySecurityLevel = params.securityLevel;
KeyboxUtils.putCertificateChain(metadata, chain.toArray(new Certificate[chain.size()]));
KeyDescriptor d = new KeyDescriptor();
d.domain = descriptor.domain;
d.nspace = descriptor.nspace;
metadata.key = d;
List<Authorization> authorizations = new ArrayList<>();
Authorization a;
for (Integer i : params.purpose) {
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.PURPOSE;
a.keyParameter.value = KeyParameterValue.keyPurpose(i);
a.securityLevel = params.securityLevel;
authorizations.add(a);
}
for (Integer i : params.digest) {
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.DIGEST;
a.keyParameter.value = KeyParameterValue.digest(i);
a.securityLevel = params.securityLevel;
authorizations.add(a);
}
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.ALGORITHM;
a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.KEY_SIZE;
a.keyParameter.value = KeyParameterValue.integer(params.keySize);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.EC_CURVE;
a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.NO_AUTH_REQUIRED;
a.keyParameter.value = KeyParameterValue.boolValue(true);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.ORIGIN;
a.keyParameter.value = KeyParameterValue.origin(params.origin);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.OS_VERSION;
a.keyParameter.value = KeyParameterValue.integer(params.osVersion);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.OS_PATCHLEVEL;
a.keyParameter.value = KeyParameterValue.integer(params.osPatchLevel);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.VENDOR_PATCHLEVEL;
a.keyParameter.value = KeyParameterValue.integer(params.vendorPatchLevel);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.BOOT_PATCHLEVEL;
a.keyParameter.value = KeyParameterValue.integer(params.bootPatchLevel);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.CREATION_DATETIME;
a.keyParameter.value = KeyParameterValue.longInteger(params.creationDateTime);
a.securityLevel = params.securityLevel;
authorizations.add(a);
a = new Authorization();
a.keyParameter = new KeyParameter();
a.keyParameter.tag = Tag.USER_ID;
a.keyParameter.value = KeyParameterValue.integer(params.userId);
a.securityLevel = params.securityLevel;
authorizations.add(a);
metadata.authorizations = authorizations.toArray(new Authorization[0]);
metadata.modificationTimeMs = System.currentTimeMillis();
response.metadata = metadata;
response.iSecurityLevel = level;
return response;
} catch (Exception e) {
Log.e(TAG, "Failed to build key entry response", e);
return null;
}
}

View File

@@ -38,6 +38,9 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class KeyboxUtils {
private static final ConcurrentHashMap<Key, KeyEntryResponse> response = new ConcurrentHashMap<>();
public static record Key(int uid, String alias) {}
public static byte[] decodePemOrBase64(String input) {
String base64 = input
.replaceAll("-----BEGIN [^-]+-----", "")
@@ -49,11 +52,11 @@ public class KeyboxUtils {
public static PrivateKey parsePrivateKey(String encodedKey, String algorithm) throws Exception {
byte[] keyBytes = decodePemOrBase64(encodedKey);
ASN1Primitive primitive = ASN1Primitive.fromByteArray(keyBytes);
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
if ("EC".equalsIgnoreCase(algorithm)) {
try {
// Try parsing as PKCS#8
PrivateKeyInfo info = PrivateKeyInfo.getInstance(primitive);
return KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_EC).generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded()));
return KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded()));
} catch (Exception e) {
// Possibly SEC1 / PKCS#1 EC
ASN1Sequence seq = ASN1Sequence.getInstance(primitive);
@@ -61,33 +64,65 @@ public class KeyboxUtils {
AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, ecPrivateKey.getParameters());
PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, ecPrivateKey);
PKCS8EncodedKeySpec pkcs8Spec = new PKCS8EncodedKeySpec(privInfo.getEncoded());
return KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_EC).generatePrivate(pkcs8Spec);
return KeyFactory.getInstance("EC").generatePrivate(pkcs8Spec);
}
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
} else if ("RSA".equalsIgnoreCase(algorithm)) {
try {
// Try parsing as PKCS#8
return KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_RSA).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
} catch (Exception e) {
// Parse as PKCS#1
RSAPrivateKey rsaKey = RSAPrivateKey.getInstance(primitive);
AlgorithmIdentifier algId = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, rsaKey);
PKCS8EncodedKeySpec pkcs8Spec = new PKCS8EncodedKeySpec(privInfo.getEncoded());
return KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_RSA).generatePrivate(pkcs8Spec);
return KeyFactory.getInstance("RSA").generatePrivate(pkcs8Spec);
}
} else {
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
}
}
public static byte[] getCertificateChain(String algorithm) throws Exception {
String[] chain = KeyProperties.KEY_ALGORITHM_EC.equals(algorithm)
? KeyProviderManager.getProvider().getEcCertificateChain()
: KeyProviderManager.getProvider().getRsaCertificateChain();
public static X509Certificate parseCertificate(String encodedCert) throws Exception {
byte[] certBytes = decodePemOrBase64(encodedCert);
return (X509Certificate) CertificateFactory
.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(certBytes));
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (String cert : chain) out.write(decodePemOrBase64(cert));
return out.toByteArray();
public static List<Certificate> getCertificateChain(String algorithm) throws Exception {
IKeyboxProvider provider = KeyProviderManager.getProvider();
String[] certChainPem = KeyProperties.KEY_ALGORITHM_EC.equals(algorithm)
? provider.getEcCertificateChain()
: provider.getRsaCertificateChain();
CertificateFactory factory = CertificateFactory.getInstance("X.509");
List<Certificate> certs = new ArrayList<>();
for (String certPem : certChainPem) {
certs.add(parseCertificate(certPem));
}
return certs;
}
public static void putCertificateChain(KeyEntryResponse response, Certificate[] chain) throws Exception {
putCertificateChain(response.metadata, chain);
}
public static void putCertificateChain(KeyMetadata metadata, Certificate[] chain) throws Exception {
metadata.certificate = chain[0].getEncoded();
var output = new ByteArrayOutputStream();
for (int i = 1; i < chain.length; i++) {
output.write(chain[i].getEncoded());
}
metadata.certificateChain = output.toByteArray();
}
public static X509Certificate getCertificateFromHolder(X509CertificateHolder holder) throws Exception {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
ByteArrayInputStream in = new ByteArrayInputStream(holder.getEncoded());
return (X509Certificate) certFactory.generateCertificate(in);
}
public static PrivateKey getPrivateKey(String algorithm) throws Exception {
@@ -101,12 +136,23 @@ public class KeyboxUtils {
public static X509CertificateHolder getCertificateHolder(String algorithm) throws Exception {
IKeyboxProvider provider = KeyProviderManager.getProvider();
String cert = KeyProperties.KEY_ALGORITHM_EC.equals(algorithm)
String certPem = KeyProperties.KEY_ALGORITHM_EC.equals(algorithm)
? provider.getEcCertificateChain()[0]
: provider.getRsaCertificateChain()[0];
byte[] certBytes = decodePemOrBase64(cert);
X509Certificate parsedCert = parseCertificate(certPem);
return new X509CertificateHolder(parsedCert.getEncoded());
}
return new X509CertificateHolder(certBytes);
public static void append(int uid, String a, KeyEntryResponse c) {
response.put(new Key(uid, a), c);
}
public static void remove(int uid, String a) {
response.remove(new Key(uid, a));
}
public static KeyEntryResponse retrieve(int uid, String a) {
return response.get(new Key(uid, a));
}
}

View File

@@ -33,6 +33,7 @@ import android.system.keystore2.ResponseCode;
import android.util.Log;
import com.android.internal.util.evolution.KeyboxImitationHooks;
import com.android.internal.util.evolution.KeyboxUtils;
import java.util.Calendar;
@@ -285,8 +286,12 @@ public class KeyStore2 {
throws KeyStoreException {
StrictMode.noteDiskRead();
return KeyboxImitationHooks.onGetKeyEntry(
handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor)));
KeyEntryResponse response = KeyboxImitationHooks.onGetKeyEntry(descriptor);
if (response != null) {
return response;
}
return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor));
}
/**
@@ -336,6 +341,8 @@ public class KeyStore2 {
throws KeyStoreException {
StrictMode.noteDiskWrite();
KeyboxUtils.remove(Binder.getCallingUid(), descriptor.alias);
handleRemoteExceptionWithRetry((service) -> {
service.deleteKey(descriptor);
return 0;

View File

@@ -33,6 +33,9 @@ import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
import android.util.Log;
import com.android.internal.util.evolution.KeyboxImitationHooks;
import com.android.internal.util.evolution.KeyboxUtils;
import java.util.Calendar;
import java.util.Collection;
@@ -146,6 +149,15 @@ public class KeyStoreSecurityLevel {
throws KeyStoreException {
StrictMode.noteDiskWrite();
KeyboxUtils.remove(Binder.getCallingUid(), descriptor.alias);
if (attestationKey == null) {
KeyMetadata metadata = KeyboxImitationHooks.generateKey(mSecurityLevel,
descriptor, args);
if (metadata != null) {
return metadata;
}
}
return handleExceptions(() -> mSecurityLevel.generateKey(
descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]),
flags, entropy));