1 package com.google.attestationexample; 2 3 import android.os.AsyncTask; 4 import android.security.keystore.KeyGenParameterSpec; 5 import android.security.keystore.KeyProperties; 6 import android.util.Base64; 7 import android.util.Log; 8 9 import com.google.common.collect.ImmutableSet; 10 11 import java.io.ByteArrayInputStream; 12 import java.security.GeneralSecurityException; 13 import java.security.InvalidAlgorithmParameterException; 14 import java.security.InvalidKeyException; 15 import java.security.KeyPairGenerator; 16 import java.security.KeyStore; 17 import java.security.NoSuchAlgorithmException; 18 import java.security.NoSuchProviderException; 19 import java.security.PrivateKey; 20 import java.security.PublicKey; 21 import java.security.Signature; 22 import java.security.SignatureException; 23 import java.security.cert.Certificate; 24 import java.security.cert.CertificateException; 25 import java.security.cert.CertificateFactory; 26 import java.security.cert.X509Certificate; 27 import java.security.spec.ECGenParameterSpec; 28 import java.util.Arrays; 29 import java.util.Date; 30 import java.util.HashSet; 31 import java.util.Set; 32 import java.util.regex.Pattern; 33 34 import static android.security.keystore.KeyProperties.DIGEST_SHA256; 35 import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC; 36 import static com.google.attestationexample.RootOfTrust.KM_VERIFIED_BOOT_VERIFIED; 37 38 /** 39 * AttestationTest generates an EC Key pair, with attestation, and displays the result in the 40 * TextView provided to its constructor. 41 */ 42 public class AttestationTest extends AsyncTask<Void, String, Void> { 43 private static final int ORIGINATION_TIME_OFFSET = 1000000; 44 private static final int CONSUMPTION_TIME_OFFSET = 2000000; 45 46 private static final int KEY_USAGE_BITSTRING_LENGTH = 9; 47 private static final int KEY_USAGE_DIGITAL_SIGNATURE_BIT_OFFSET = 0; 48 private static final int KEY_USAGE_KEY_ENCIPHERMENT_BIT_OFFSET = 2; 49 private static final int KEY_USAGE_DATA_ENCIPHERMENT_BIT_OFFSET = 3; 50 51 private static final int OS_MAJOR_VERSION_MATCH_GROUP_NAME = 1; 52 private static final int OS_MINOR_VERSION_MATCH_GROUP_NAME = 2; 53 private static final int OS_SUBMINOR_VERSION_MATCH_GROUP_NAME = 3; 54 private static final Pattern OS_VERSION_STRING_PATTERN = Pattern 55 .compile("([0-9]{1,2})(?:\\.([0-9]{1,2}))?(?:\\.([0-9]{1,2}))?(?:[^0-9.]+.*)?"); 56 57 private static final int OS_PATCH_LEVEL_YEAR_GROUP_NAME = 1; 58 private static final int OS_PATCH_LEVEL_MONTH_GROUP_NAME = 2; 59 private static final Pattern OS_PATCH_LEVEL_STRING_PATTERN = Pattern 60 .compile("([0-9]{4})-([0-9]{2})-[0-9]{2}"); 61 62 private static final int KM_ERROR_INVALID_INPUT_LENGTH = -21; 63 64 private static final String FINISHED = "AttestationFinished"; 65 private static final String FAIL = "AttestationFail"; 66 private static final String INFO = "AttestationInfo"; 67 private static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17"; 68 private static final String KEY_USAGE_OID = "2.5.29.15"; // Standard key usage extension. 69 70 private static final String GOOGLE_ROOT_CERTIFICATE = 71 "-----BEGIN CERTIFICATE-----\n" 72 + "MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV" 73 + "BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy" 74 + "ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B" 75 + "AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS" 76 + "Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7" 77 + "tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj" 78 + "nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq" 79 + "C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ" 80 + "oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O" 81 + "JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg" 82 + "sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi" 83 + "igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M" 84 + "RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E" 85 + "aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um" 86 + "AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD" 87 + "VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO" 88 + "BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk" 89 + "Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD" 90 + "ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB" 91 + "Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m" 92 + "qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY" 93 + "DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm" 94 + "QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u" 95 + "JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD" 96 + "CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy" 97 + "ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD" 98 + "qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic" 99 + "MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1" 100 + "wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk\n" 101 + "-----END CERTIFICATE-----"; 102 103 @Override doInBackground(Void... params)104 protected Void doInBackground(Void... params) { 105 try { 106 testEcAttestation(); 107 } catch (Exception e) { 108 Log.e(FAIL, "Something is wrong, check detailed logcat logs:\n", e); 109 } 110 return null; 111 } 112 testEcAttestation()113 private void testEcAttestation() throws Exception { 114 String ecCurve = "secp256r1"; 115 int keySize = 256; 116 byte[] challenge = "challenge".getBytes(); 117 String keystoreAlias = "test_key"; 118 119 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 120 keyStore.load(null); 121 122 keyStore.deleteEntry(keystoreAlias); 123 124 Log.d(INFO,"Generating key pair..."); 125 Date startTime = new Date(new Date().getTime() - 1000); 126 Log.d("****", "Start Time is: " + startTime.toString()); 127 Date originationEnd = new Date(startTime.getTime() + ORIGINATION_TIME_OFFSET); 128 Date consumptionEnd = new Date(startTime.getTime() + CONSUMPTION_TIME_OFFSET); 129 KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keystoreAlias, 130 KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) 131 .setAlgorithmParameterSpec(new ECGenParameterSpec(ecCurve)) 132 .setDigests(DIGEST_SHA256) 133 .setAttestationChallenge(challenge); 134 135 builder.setKeyValidityStart(startTime) 136 .setKeyValidityForOriginationEnd(originationEnd) 137 .setKeyValidityForConsumptionEnd(consumptionEnd); 138 139 generateKeyPair(KEY_ALGORITHM_EC, builder.build()); 140 Log.d(INFO, "Key pair generated\n\n"); 141 142 Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias); 143 Log.d(INFO, "Retrieved certificate chain of length " + certificates.length + "\n"); 144 try { 145 verifyCertificateSignatures(certificates); 146 } catch (GeneralSecurityException e) { 147 Log.e(FAIL, "Certificate chain does not verify.", e); 148 } 149 150 X509Certificate attestationCert = (X509Certificate) certificates[0]; 151 X509Certificate secureRoot = (X509Certificate) CertificateFactory 152 .getInstance("X.509").generateCertificate( 153 new ByteArrayInputStream( 154 GOOGLE_ROOT_CERTIFICATE.getBytes())); 155 X509Certificate rootCert = (X509Certificate) certificates[certificates.length - 1]; 156 if (!Arrays.equals(secureRoot.getPublicKey().getEncoded(), 157 rootCert.getPublicKey().getEncoded())) { 158 Log.e(FAIL, "root certificate public key does not match Google public key"); 159 } 160 printKeyUsage(attestationCert); 161 162 ImmutableSet<String> unexpectedExtensionOids = 163 (ImmutableSet) retrieveUnexpectedExtensionOids(attestationCert); 164 if (!unexpectedExtensionOids.isEmpty()) { 165 Log.e(FAIL, "attestation certificate contains unexpected OIDs"); 166 for (String oid : unexpectedExtensionOids.toArray(new String[unexpectedExtensionOids.size()])) { 167 Log.e(FAIL, "Unexpected OID: " + oid); 168 } 169 } 170 171 Attestation attestation = new Attestation(attestationCert); 172 if (!Arrays.equals(attestation.getAttestationChallenge(), challenge)) { 173 Utils.logError("challenge mismatch\nExpected:", 174 challenge, attestation.getAttestationChallenge()); 175 } 176 177 if (attestation.getAttestationSecurityLevel() != 1) { 178 Utils.logError("Attestation cert reporting non-TEE security level", 179 1, attestation.getAttestationSecurityLevel()); 180 } 181 if (attestation.getKeymasterSecurityLevel() != 1) { 182 Utils.logError("Keymaster reporting non-TEE security level", 183 1, attestation.getKeymasterSecurityLevel()); 184 } 185 186 checkRootOfTrust(attestation); 187 188 Signature signer = Signature.getInstance("SHA256WithECDSA"); 189 KeyStore keystore = KeyStore.getInstance("AndroidKeyStore"); 190 keystore.load(null); 191 192 PrivateKey key = (PrivateKey) keystore.getKey(keystoreAlias, null); 193 try { 194 signer.initSign(key); 195 signer.update("Hello".getBytes()); 196 signer.sign(); 197 } catch(Exception e) { 198 Log.e(FAIL,"Failed to sign with generated key", e); 199 } 200 Log.d(FINISHED, "Signing completed"); 201 } 202 generateKeyPair(String algorithm, KeyGenParameterSpec spec)203 private void generateKeyPair(String algorithm, KeyGenParameterSpec spec) 204 throws NoSuchAlgorithmException, NoSuchProviderException, 205 InvalidAlgorithmParameterException { 206 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm, 207 "AndroidKeyStore"); 208 keyPairGenerator.initialize(spec); 209 keyPairGenerator.generateKeyPair(); 210 } 211 verifyCertificateSignatures(Certificate[] certChain)212 private void verifyCertificateSignatures(Certificate[] certChain) 213 throws GeneralSecurityException { 214 215 for (Certificate cert : certChain) { 216 final byte[] derCert = cert.getEncoded(); 217 final String pemCertPre = Base64.encodeToString(derCert, Base64.NO_WRAP); 218 Log.e("****", pemCertPre); 219 } 220 221 for (int i = 1; i < certChain.length; ++i) { 222 PublicKey pubKey = certChain[i].getPublicKey(); 223 try { 224 certChain[i - 1].verify(pubKey); 225 } catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException 226 | NoSuchProviderException | SignatureException e) { 227 throw new GeneralSecurityException("Failed to verify certificate " 228 + certChain[i - 1] + " with public key " + certChain[i].getPublicKey(), e); 229 } 230 if (i == certChain.length - 1) { 231 // Last cert is self-signed. 232 try { 233 certChain[i].verify(pubKey); 234 } catch (CertificateException e) { 235 throw new GeneralSecurityException( 236 "Root cert " + certChain[i] + " is not correctly self-signed", e); 237 } 238 } 239 } 240 } 241 printKeyUsage(X509Certificate attestationCert)242 private void printKeyUsage(X509Certificate attestationCert) { 243 Log.d(INFO, "Key usage:"); 244 if (attestationCert.getKeyUsage() == null) { 245 Log.d(INFO, " NONE\n"); 246 return; 247 } 248 if (attestationCert.getKeyUsage()[KEY_USAGE_DIGITAL_SIGNATURE_BIT_OFFSET]) { 249 Log.d(INFO, " sign"); 250 } 251 if (attestationCert.getKeyUsage()[KEY_USAGE_DATA_ENCIPHERMENT_BIT_OFFSET]) { 252 Log.d(INFO, " encrypt_data"); 253 } 254 if (attestationCert.getKeyUsage()[KEY_USAGE_KEY_ENCIPHERMENT_BIT_OFFSET]) { 255 Log.d(INFO, " encrypt_keys"); 256 } 257 Log.d(INFO, "\n"); 258 } 259 retrieveUnexpectedExtensionOids(X509Certificate x509Cert)260 private Set<String> retrieveUnexpectedExtensionOids(X509Certificate x509Cert) { 261 return new ImmutableSet.Builder<String>() 262 .addAll(x509Cert.getCriticalExtensionOIDs() 263 .stream() 264 .filter(s -> !KEY_USAGE_OID.equals(s)) 265 .iterator()) 266 .addAll(x509Cert.getNonCriticalExtensionOIDs() 267 .stream() 268 .filter(s -> !KEY_DESCRIPTION_OID.equals(s)) 269 .iterator()) 270 .build(); 271 } 272 273 checkRootOfTrust(Attestation attestation)274 private void checkRootOfTrust(Attestation attestation) { 275 RootOfTrust rootOfTrust = attestation.getTeeEnforced().getRootOfTrust(); 276 if (rootOfTrust == null) { 277 Log.e(FAIL, "Root of trust is null"); 278 return; 279 } 280 if (rootOfTrust.getVerifiedBootKey() == null) { 281 Log.e(FAIL, "Verified boot key is null"); 282 return; 283 } 284 if (rootOfTrust.getVerifiedBootKey().length < 32) { 285 Log.e(FAIL, "Verified boot key is less than 32 bytes"); 286 } 287 if (isAllZeroes(rootOfTrust.getVerifiedBootKey())) { 288 Log.e(FAIL, "Verified boot key is all zeros."); 289 } 290 if (!rootOfTrust.isDeviceLocked()) { 291 Log.e(FAIL, "Device isn't locked"); 292 } 293 if (KM_VERIFIED_BOOT_VERIFIED != rootOfTrust.getVerifiedBootState()) { 294 Utils.logError("Root of trust isn't reporting boot verified.", 295 KM_VERIFIED_BOOT_VERIFIED, rootOfTrust.getVerifiedBootState()); 296 } 297 if (rootOfTrust.getVerifiedBootHash() == null) { 298 Log.e(FAIL, "Verified boot hash is null"); 299 return; 300 } 301 if (rootOfTrust.getVerifiedBootHash().length != 32) { 302 Log.e(FAIL, "Verified boot hash isn't 32 bytes"); 303 } 304 if (isAllZeroes(rootOfTrust.getVerifiedBootHash())) { 305 Log.e(FAIL, "Verified boot hash is all zeros."); 306 } 307 } 308 isAllZeroes(byte[] checkArray)309 private boolean isAllZeroes(byte[] checkArray) { 310 for (byte b : checkArray) { 311 if (b != 0) { 312 return false; 313 } 314 } 315 return true; 316 } 317 } 318