1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.updates; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.test.AndroidTestCase; 22 import android.provider.Settings; 23 import android.util.Base64; 24 import android.util.Log; 25 26 import java.io.ByteArrayInputStream; 27 import java.io.File; 28 import java.io.FileInputStream; 29 import java.io.FileOutputStream; 30 import java.io.FileWriter; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.security.cert.CertificateFactory; 34 import java.security.cert.Certificate; 35 import java.security.cert.X509Certificate; 36 import java.security.MessageDigest; 37 import java.security.NoSuchAlgorithmException; 38 import java.security.PrivateKey; 39 import java.security.Signature; 40 import java.security.spec.PKCS8EncodedKeySpec; 41 import java.security.KeyFactory; 42 import java.util.HashSet; 43 import java.io.*; 44 import libcore.io.IoUtils; 45 46 /** 47 * Tests for {@link com.android.server.CertPinInstallReceiver} 48 */ 49 public class CertPinInstallReceiverTest extends AndroidTestCase { 50 51 private static final String TAG = "CertPinInstallReceiverTest"; 52 53 private static final String PINLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/"; 54 55 public static final String PINLIST_CONTENT_PATH = PINLIST_ROOT + "pins"; 56 public static final String PINLIST_METADATA_PATH = PINLIST_CONTENT_PATH + "metadata"; 57 58 public static final String PINLIST_CONTENT_URL_KEY = "pinlist_content_url"; 59 public static final String PINLIST_METADATA_URL_KEY = "pinlist_metadata_url"; 60 public static final String PINLIST_CERTIFICATE_KEY = "config_update_certificate"; 61 public static final String PINLIST_VERSION_KEY = "pinlist_version"; 62 63 private static final String EXTRA_CONTENT_PATH = "CONTENT_PATH"; 64 private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH"; 65 private static final String EXTRA_SIGNATURE = "SIGNATURE"; 66 private static final String EXTRA_VERSION_NUMBER = "VERSION"; 67 68 public static final String TEST_CERT = "" + 69 "MIIDsjCCAxugAwIBAgIJAPLf2gS0zYGUMA0GCSqGSIb3DQEBBQUAMIGYMQswCQYDVQQGEwJVUzET" + 70 "MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEPMA0GA1UEChMGR29v" + 71 "Z2xlMRAwDgYDVQQLEwd0ZXN0aW5nMRYwFAYDVQQDEw1HZXJlbXkgQ29uZHJhMSEwHwYJKoZIhvcN" + 72 "AQkBFhJnY29uZHJhQGdvb2dsZS5jb20wHhcNMTIwNzE0MTc1MjIxWhcNMTIwODEzMTc1MjIxWjCB" + 73 "mDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZp" + 74 "ZXcxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHdGVzdGluZzEWMBQGA1UEAxMNR2VyZW15IENv" + 75 "bmRyYTEhMB8GCSqGSIb3DQEJARYSZ2NvbmRyYUBnb29nbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUA" + 76 "A4GNADCBiQKBgQCjGGHATBYlmas+0sEECkno8LZ1KPglb/mfe6VpCT3GhSr+7br7NG/ZwGZnEhLq" + 77 "E7YIH4fxltHmQC3Tz+jM1YN+kMaQgRRjo/LBCJdOKaMwUbkVynAH6OYsKevjrOPk8lfM5SFQzJMG" + 78 "sA9+Tfopr5xg0BwZ1vA/+E3mE7Tr3M2UvwIDAQABo4IBADCB/TAdBgNVHQ4EFgQUhzkS9E6G+x8W" + 79 "L4EsmRjDxu28tHUwgc0GA1UdIwSBxTCBwoAUhzkS9E6G+x8WL4EsmRjDxu28tHWhgZ6kgZswgZgx" + 80 "CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3" + 81 "MQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB3Rlc3RpbmcxFjAUBgNVBAMTDUdlcmVteSBDb25k" + 82 "cmExITAfBgkqhkiG9w0BCQEWEmdjb25kcmFAZ29vZ2xlLmNvbYIJAPLf2gS0zYGUMAwGA1UdEwQF" + 83 "MAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAYiugFDmbDOQ2U/+mqNt7o8ftlEo9SJrns6O8uTtK6AvR" + 84 "orDrR1AXTXkuxwLSbmVfedMGOZy7Awh7iZa8hw5x9XmUudfNxvmrKVEwGQY2DZ9PXbrnta/dwbhK" + 85 "mWfoepESVbo7CKIhJp8gRW0h1Z55ETXD57aGJRvQS4pxkP8ANhM="; 86 87 88 public static final String TEST_KEY = "" + 89 "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKMYYcBMFiWZqz7SwQQKSejwtnUo" + 90 "+CVv+Z97pWkJPcaFKv7tuvs0b9nAZmcSEuoTtggfh/GW0eZALdPP6MzVg36QxpCBFGOj8sEIl04p" + 91 "ozBRuRXKcAfo5iwp6+Os4+TyV8zlIVDMkwawD35N+imvnGDQHBnW8D/4TeYTtOvczZS/AgMBAAEC" + 92 "gYBxwFalNSwZK3WJipq+g6KLCiBn1JxGGDQlLKrweFaSuFyFky9fd3IvkIabirqQchD612sMb+GT" + 93 "0t1jptW6z4w2w6++IW0A3apDOCwoD+uvDBXrbFqI0VbyAWUNqHVdaFFIRk2IHGEE6463mGRdmILX" + 94 "IlCd/85RTHReg4rl/GFqWQJBANgLAIR4pWbl5Gm+DtY18wp6Q3pJAAMkmP/lISCBIidu1zcqYIKt" + 95 "PoDW4Knq9xnhxPbXrXKv4YzZWHBK8GkKhQ0CQQDBQnXufQcMew+PwiS0oJvS+eQ6YJwynuqG2ejg" + 96 "WE+T7489jKtscRATpUXpZUYmDLGg9bLt7L62hFvFSj2LO2X7AkBcdrD9AWnBFWlh/G77LVHczSEu" + 97 "KCoyLiqxcs5vy/TjLaQ8vw1ZQG580/qJnr+tOxyCjSJ18GK3VppsTRaBznfNAkB3nuCKNp9HTWCL" + 98 "dfrsRsFMrFpk++mSt6SoxXaMbn0LL2u1CD4PCEiQMGt+lK3/3TmRTKNs+23sYS7Ahjxj0udDAkEA" + 99 "p57Nj65WNaWeYiOfTwKXkLj8l29H5NbaGWxPT0XkWr4PvBOFZVH/wj0/qc3CMVGnv11+DyO+QUCN" + 100 "SqBB5aRe8g=="; 101 102 private void overrideSettings(String key, String value) throws Exception { 103 assertTrue(Settings.Secure.putString(mContext.getContentResolver(), key, value)); 104 Thread.sleep(1000); 105 } 106 107 private void overrideCert(String value) throws Exception { 108 overrideSettings(PINLIST_CERTIFICATE_KEY, value); 109 } 110 111 private String readPins() throws Exception { 112 return IoUtils.readFileAsString(PINLIST_CONTENT_PATH); 113 } 114 115 private String readCurrentVersion() throws Exception { 116 return IoUtils.readFileAsString("/data/misc/keychain/metadata/version"); 117 } 118 119 private String getNextVersion() throws Exception { 120 int currentVersion = Integer.parseInt(readCurrentVersion()); 121 return Integer.toString(currentVersion + 1); 122 } 123 124 private static String getCurrentHash(String content) throws Exception { 125 if (content == null) { 126 return "0"; 127 } 128 MessageDigest dgst = MessageDigest.getInstance("SHA512"); 129 byte[] encoded = content.getBytes(); 130 byte[] fingerprint = dgst.digest(encoded); 131 return IntegralToString.bytesToHexString(fingerprint, false); 132 } 133 134 private static String getHashOfCurrentContent() throws Exception { 135 String content = IoUtils.readFileAsString("/data/misc/keychain/pins"); 136 return getCurrentHash(content); 137 } 138 139 private PrivateKey createKey() throws Exception { 140 byte[] derKey = Base64.decode(TEST_KEY.getBytes(), Base64.DEFAULT); 141 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(derKey); 142 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 143 return (PrivateKey) keyFactory.generatePrivate(keySpec); 144 } 145 146 private X509Certificate createCertificate() throws Exception { 147 byte[] derCert = Base64.decode(TEST_CERT.getBytes(), Base64.DEFAULT); 148 InputStream istream = new ByteArrayInputStream(derCert); 149 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 150 return (X509Certificate) cf.generateCertificate(istream); 151 } 152 153 private String makeTemporaryContentFile(String content) throws Exception { 154 FileOutputStream fw = mContext.openFileOutput("content.txt", mContext.MODE_WORLD_READABLE); 155 fw.write(content.getBytes(), 0, content.length()); 156 fw.close(); 157 return mContext.getFilesDir() + "/content.txt"; 158 } 159 160 private String createSignature(String content, String version, String requiredHash) 161 throws Exception { 162 Signature signer = Signature.getInstance("SHA512withRSA"); 163 signer.initSign(createKey()); 164 signer.update(content.trim().getBytes()); 165 signer.update(version.trim().getBytes()); 166 signer.update(requiredHash.getBytes()); 167 String sig = new String(Base64.encode(signer.sign(), Base64.DEFAULT)); 168 assertEquals(true, 169 verifySignature(content, version, requiredHash, sig, createCertificate())); 170 return sig; 171 } 172 173 public boolean verifySignature(String content, String version, String requiredPrevious, 174 String signature, X509Certificate cert) throws Exception { 175 Signature signer = Signature.getInstance("SHA512withRSA"); 176 signer.initVerify(cert); 177 signer.update(content.trim().getBytes()); 178 signer.update(version.trim().getBytes()); 179 signer.update(requiredPrevious.trim().getBytes()); 180 return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT)); 181 } 182 183 private void sendIntent(String contentPath, String version, String required, String sig) { 184 Intent i = new Intent(); 185 i.setAction("android.intent.action.UPDATE_PINS"); 186 i.putExtra(EXTRA_CONTENT_PATH, contentPath); 187 i.putExtra(EXTRA_VERSION_NUMBER, version); 188 i.putExtra(EXTRA_REQUIRED_HASH, required); 189 i.putExtra(EXTRA_SIGNATURE, sig); 190 mContext.sendBroadcast(i); 191 } 192 193 private String runTest(String cert, String content, String version, String required, String sig) 194 throws Exception { 195 Log.e(TAG, "started test"); 196 overrideCert(cert); 197 String contentPath = makeTemporaryContentFile(content); 198 sendIntent(contentPath, version, required, sig); 199 Thread.sleep(1000); 200 return readPins(); 201 } 202 203 private String runTestWithoutSig(String cert, String content, String version, String required) 204 throws Exception { 205 String sig = createSignature(content, version, required); 206 return runTest(cert, content, version, required, sig); 207 } 208 209 public void testOverwritePinlist() throws Exception { 210 Log.e(TAG, "started testOverwritePinList"); 211 assertEquals("abcde", runTestWithoutSig(TEST_CERT, "abcde", getNextVersion(), getHashOfCurrentContent())); 212 Log.e(TAG, "started testOverwritePinList"); 213 } 214 215 public void testBadSignatureFails() throws Exception { 216 Log.e(TAG, "started testOverwritePinList"); 217 String text = "blahblah"; 218 runTestWithoutSig(TEST_CERT, text, getNextVersion(), getHashOfCurrentContent()); 219 assertEquals(text, runTest(TEST_CERT, "bcdef", getNextVersion(), getCurrentHash(text), "")); 220 Log.e(TAG, "started testOverwritePinList"); 221 } 222 223 public void testBadRequiredHashFails() throws Exception { 224 runTestWithoutSig(TEST_CERT, "blahblahblah", getNextVersion(), getHashOfCurrentContent()); 225 assertEquals("blahblahblah", runTestWithoutSig(TEST_CERT, "cdefg", getNextVersion(), "0")); 226 Log.e(TAG, "started testOverwritePinList"); 227 } 228 229 public void testBadVersionFails() throws Exception { 230 String text = "blahblahblahblah"; 231 String version = getNextVersion(); 232 runTestWithoutSig(TEST_CERT, text, version, getHashOfCurrentContent()); 233 assertEquals(text, runTestWithoutSig(TEST_CERT, "defgh", version, getCurrentHash(text))); 234 Log.e(TAG, "started testOverwritePinList"); 235 } 236 237 public void testOverrideRequiredHash() throws Exception { 238 runTestWithoutSig(TEST_CERT, "blahblahblah", getNextVersion(), getHashOfCurrentContent()); 239 assertEquals("blahblahblah", runTestWithoutSig(TEST_CERT, "cdefg", "NONE", "0")); 240 Log.e(TAG, "started testOverwritePinList"); 241 } 242 243 } 244