Revert "KeyboxImitationHooks: Abandon generated certs"
This reverts commit 4da4b393a71c2f73fe49100bc94ffce60db7b2af.
This commit is contained in:
@@ -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");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user