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