blob: 287d0652b3360aab4cbd5e855023a24240da02b2 [file] [log] [blame]
package com.google.attestationexample;
import android.os.AsyncTask;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import android.util.Log;
import com.google.common.collect.ImmutableSet;
import java.io.ByteArrayInputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import static android.security.keystore.KeyProperties.DIGEST_SHA256;
import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
import static com.google.attestationexample.RootOfTrust.KM_VERIFIED_BOOT_VERIFIED;
/**
* AttestationTest generates an EC Key pair, with attestation, and displays the result in the
* TextView provided to its constructor.
*/
public class AttestationTest extends AsyncTask<Void, String, Void> {
private static final int ORIGINATION_TIME_OFFSET = 1000000;
private static final int CONSUMPTION_TIME_OFFSET = 2000000;
private static final int KEY_USAGE_BITSTRING_LENGTH = 9;
private static final int KEY_USAGE_DIGITAL_SIGNATURE_BIT_OFFSET = 0;
private static final int KEY_USAGE_KEY_ENCIPHERMENT_BIT_OFFSET = 2;
private static final int KEY_USAGE_DATA_ENCIPHERMENT_BIT_OFFSET = 3;
private static final int OS_MAJOR_VERSION_MATCH_GROUP_NAME = 1;
private static final int OS_MINOR_VERSION_MATCH_GROUP_NAME = 2;
private static final int OS_SUBMINOR_VERSION_MATCH_GROUP_NAME = 3;
private static final Pattern OS_VERSION_STRING_PATTERN = Pattern
.compile("([0-9]{1,2})(?:\\.([0-9]{1,2}))?(?:\\.([0-9]{1,2}))?(?:[^0-9.]+.*)?");
private static final int OS_PATCH_LEVEL_YEAR_GROUP_NAME = 1;
private static final int OS_PATCH_LEVEL_MONTH_GROUP_NAME = 2;
private static final Pattern OS_PATCH_LEVEL_STRING_PATTERN = Pattern
.compile("([0-9]{4})-([0-9]{2})-[0-9]{2}");
private static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
private static final String FINISHED = "AttestationFinished";
private static final String FAIL = "AttestationFail";
private static final String INFO = "AttestationInfo";
private static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
private static final String KEY_USAGE_OID = "2.5.29.15"; // Standard key usage extension.
private static final String GOOGLE_ROOT_CERTIFICATE =
"-----BEGIN CERTIFICATE-----\n"
+ "MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV"
+ "BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy"
+ "ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B"
+ "AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS"
+ "Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7"
+ "tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj"
+ "nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq"
+ "C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ"
+ "oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O"
+ "JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg"
+ "sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi"
+ "igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M"
+ "RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E"
+ "aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um"
+ "AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD"
+ "VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO"
+ "BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk"
+ "Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD"
+ "ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB"
+ "Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m"
+ "qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY"
+ "DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm"
+ "QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u"
+ "JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD"
+ "CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy"
+ "ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD"
+ "qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic"
+ "MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1"
+ "wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk\n"
+ "-----END CERTIFICATE-----";
@Override
protected Void doInBackground(Void... params) {
try {
testEcAttestation();
} catch (Exception e) {
Log.e(FAIL, "Something is wrong, check detailed logcat logs:\n", e);
}
return null;
}
private void testEcAttestation() throws Exception {
String ecCurve = "secp256r1";
int keySize = 256;
byte[] challenge = "challenge".getBytes();
String keystoreAlias = "test_key";
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.deleteEntry(keystoreAlias);
Log.d(INFO,"Generating key pair...");
Date startTime = new Date(new Date().getTime() - 1000);
Log.d("****", "Start Time is: " + startTime.toString());
Date originationEnd = new Date(startTime.getTime() + ORIGINATION_TIME_OFFSET);
Date consumptionEnd = new Date(startTime.getTime() + CONSUMPTION_TIME_OFFSET);
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keystoreAlias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setAlgorithmParameterSpec(new ECGenParameterSpec(ecCurve))
.setDigests(DIGEST_SHA256)
.setAttestationChallenge(challenge);
builder.setKeyValidityStart(startTime)
.setKeyValidityForOriginationEnd(originationEnd)
.setKeyValidityForConsumptionEnd(consumptionEnd);
generateKeyPair(KEY_ALGORITHM_EC, builder.build());
Log.d(INFO, "Key pair generated\n\n");
Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias);
Log.d(INFO, "Retrieved certificate chain of length " + certificates.length + "\n");
try {
verifyCertificateSignatures(certificates);
} catch (GeneralSecurityException e) {
Log.e(FAIL, "Certificate chain does not verify.", e);
}
X509Certificate attestationCert = (X509Certificate) certificates[0];
X509Certificate secureRoot = (X509Certificate) CertificateFactory
.getInstance("X.509").generateCertificate(
new ByteArrayInputStream(
GOOGLE_ROOT_CERTIFICATE.getBytes()));
X509Certificate rootCert = (X509Certificate) certificates[certificates.length - 1];
if (!Arrays.equals(secureRoot.getPublicKey().getEncoded(),
rootCert.getPublicKey().getEncoded())) {
Log.e(FAIL, "root certificate public key does not match Google public key");
}
printKeyUsage(attestationCert);
ImmutableSet<String> unexpectedExtensionOids =
(ImmutableSet) retrieveUnexpectedExtensionOids(attestationCert);
if (!unexpectedExtensionOids.isEmpty()) {
Log.e(FAIL, "attestation certificate contains unexpected OIDs");
for (String oid : unexpectedExtensionOids.toArray(new String[unexpectedExtensionOids.size()])) {
Log.e(FAIL, "Unexpected OID: " + oid);
}
}
Attestation attestation = new Attestation(attestationCert);
if (!Arrays.equals(attestation.getAttestationChallenge(), challenge)) {
Utils.logError("challenge mismatch\nExpected:",
challenge, attestation.getAttestationChallenge());
}
if (attestation.getAttestationSecurityLevel() != 1) {
Utils.logError("Attestation cert reporting non-TEE security level",
1, attestation.getAttestationSecurityLevel());
}
if (attestation.getKeymasterSecurityLevel() != 1) {
Utils.logError("Keymaster reporting non-TEE security level",
1, attestation.getKeymasterSecurityLevel());
}
checkRootOfTrust(attestation);
Signature signer = Signature.getInstance("SHA256WithECDSA");
KeyStore keystore = KeyStore.getInstance("AndroidKeyStore");
keystore.load(null);
PrivateKey key = (PrivateKey) keystore.getKey(keystoreAlias, null);
try {
signer.initSign(key);
signer.update("Hello".getBytes());
signer.sign();
} catch(Exception e) {
Log.e(FAIL,"Failed to sign with generated key", e);
}
Log.d(FINISHED, "Signing completed");
}
private void generateKeyPair(String algorithm, KeyGenParameterSpec spec)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm,
"AndroidKeyStore");
keyPairGenerator.initialize(spec);
keyPairGenerator.generateKeyPair();
}
private void verifyCertificateSignatures(Certificate[] certChain)
throws GeneralSecurityException {
for (Certificate cert : certChain) {
final byte[] derCert = cert.getEncoded();
final String pemCertPre = Base64.encodeToString(derCert, Base64.NO_WRAP);
Log.e("****", pemCertPre);
}
for (int i = 1; i < certChain.length; ++i) {
PublicKey pubKey = certChain[i].getPublicKey();
try {
certChain[i - 1].verify(pubKey);
} catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException
| NoSuchProviderException | SignatureException e) {
throw new GeneralSecurityException("Failed to verify certificate "
+ certChain[i - 1] + " with public key " + certChain[i].getPublicKey(), e);
}
if (i == certChain.length - 1) {
// Last cert is self-signed.
try {
certChain[i].verify(pubKey);
} catch (CertificateException e) {
throw new GeneralSecurityException(
"Root cert " + certChain[i] + " is not correctly self-signed", e);
}
}
}
}
private void printKeyUsage(X509Certificate attestationCert) {
Log.d(INFO, "Key usage:");
if (attestationCert.getKeyUsage() == null) {
Log.d(INFO, " NONE\n");
return;
}
if (attestationCert.getKeyUsage()[KEY_USAGE_DIGITAL_SIGNATURE_BIT_OFFSET]) {
Log.d(INFO, " sign");
}
if (attestationCert.getKeyUsage()[KEY_USAGE_DATA_ENCIPHERMENT_BIT_OFFSET]) {
Log.d(INFO, " encrypt_data");
}
if (attestationCert.getKeyUsage()[KEY_USAGE_KEY_ENCIPHERMENT_BIT_OFFSET]) {
Log.d(INFO, " encrypt_keys");
}
Log.d(INFO, "\n");
}
private Set<String> retrieveUnexpectedExtensionOids(X509Certificate x509Cert) {
return new ImmutableSet.Builder<String>()
.addAll(x509Cert.getCriticalExtensionOIDs()
.stream()
.filter(s -> !KEY_USAGE_OID.equals(s))
.iterator())
.addAll(x509Cert.getNonCriticalExtensionOIDs()
.stream()
.filter(s -> !KEY_DESCRIPTION_OID.equals(s))
.iterator())
.build();
}
private void checkRootOfTrust(Attestation attestation) {
RootOfTrust rootOfTrust = attestation.getTeeEnforced().getRootOfTrust();
if (rootOfTrust == null) {
Log.e(FAIL, "Root of trust is null");
return;
}
if (rootOfTrust.getVerifiedBootKey() == null) {
Log.e(FAIL, "Verified boot key is null");
return;
}
if (rootOfTrust.getVerifiedBootKey().length < 32) {
Log.e(FAIL, "Verified boot key is less than 32 bytes");
}
if (isAllZeroes(rootOfTrust.getVerifiedBootKey())) {
Log.e(FAIL, "Verified boot key is all zeros.");
}
if (!rootOfTrust.isDeviceLocked()) {
Log.e(FAIL, "Device isn't locked");
}
if (KM_VERIFIED_BOOT_VERIFIED != rootOfTrust.getVerifiedBootState()) {
Utils.logError("Root of trust isn't reporting boot verified.",
KM_VERIFIED_BOOT_VERIFIED, rootOfTrust.getVerifiedBootState());
}
if (rootOfTrust.getVerifiedBootHash() == null) {
Log.e(FAIL, "Verified boot hash is null");
return;
}
if (rootOfTrust.getVerifiedBootHash().length != 32) {
Log.e(FAIL, "Verified boot hash isn't 32 bytes");
}
if (isAllZeroes(rootOfTrust.getVerifiedBootHash())) {
Log.e(FAIL, "Verified boot hash is all zeros.");
}
}
private boolean isAllZeroes(byte[] checkArray) {
for (byte b : checkArray) {
if (b != 0) {
return false;
}
}
return true;
}
}