From c3edd35b29eb16a49a4fb78cb8dea19e4cc03c10 Mon Sep 17 00:00:00 2001 From: Joey Date: Fri, 28 Nov 2025 11:08:02 +0900 Subject: [PATCH] Revert "KeyboxImitationHooks: Abandon generated certs" This reverts commit 4da4b393a71c2f73fe49100bc94ffce60db7b2af. --- .../util/evolution/KeyboxChainGenerator.java | 475 ++++++++++++++++++ .../util/evolution/KeyboxImitationHooks.java | 361 +++++++------ .../internal/util/evolution/KeyboxUtils.java | 78 ++- keystore/java/android/security/KeyStore2.java | 11 +- .../security/KeyStoreSecurityLevel.java | 12 + 5 files changed, 738 insertions(+), 199 deletions(-) create mode 100644 core/java/com/android/internal/util/evolution/KeyboxChainGenerator.java diff --git a/core/java/com/android/internal/util/evolution/KeyboxChainGenerator.java b/core/java/com/android/internal/util/evolution/KeyboxChainGenerator.java new file mode 100644 index 0000000000000..afdab0d44a379 --- /dev/null +++ b/core/java/com/android/internal/util/evolution/KeyboxChainGenerator.java @@ -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 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 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 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 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 purpose = new ArrayList<>(); + public List 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"); + }; + } + } +} diff --git a/core/java/com/android/internal/util/evolution/KeyboxImitationHooks.java b/core/java/com/android/internal/util/evolution/KeyboxImitationHooks.java index 8357ea9cd0234..39b569387ef0a 100644 --- a/core/java/com/android/internal/util/evolution/KeyboxImitationHooks.java +++ b/core/java/com/android/internal/util/evolution/KeyboxImitationHooks.java @@ -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 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 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 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 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; } } diff --git a/core/java/com/android/internal/util/evolution/KeyboxUtils.java b/core/java/com/android/internal/util/evolution/KeyboxUtils.java index 07954ccb63df9..42a147f72b485 100644 --- a/core/java/com/android/internal/util/evolution/KeyboxUtils.java +++ b/core/java/com/android/internal/util/evolution/KeyboxUtils.java @@ -38,6 +38,9 @@ import java.util.concurrent.ConcurrentHashMap; */ public class KeyboxUtils { + private static final ConcurrentHashMap 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 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 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)); } } diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java index 0b6d6caa57927..072084f4a6d57 100644 --- a/keystore/java/android/security/KeyStore2.java +++ b/keystore/java/android/security/KeyStore2.java @@ -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; diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java index 6ab148a8b4eab..60cbb4592e219 100644 --- a/keystore/java/android/security/KeyStoreSecurityLevel.java +++ b/keystore/java/android/security/KeyStoreSecurityLevel.java @@ -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));