Geremy Condra | b631084 | 2012-08-23 22:00:15 -0700 | [diff] [blame] | 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 | } |