blob: b6742a104c3058a1f6926f71c6f95969b5cc98f5 [file] [log] [blame]
Geremy Condrab6310842012-08-23 22:00:15 -07001/*
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
17package com.android.server.updates;
18
19import android.content.Context;
20import android.content.Intent;
21import android.test.AndroidTestCase;
22import android.provider.Settings;
23import android.util.Base64;
24import android.util.Log;
25
26import java.io.ByteArrayInputStream;
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.FileOutputStream;
30import java.io.FileWriter;
31import java.io.IOException;
32import java.io.InputStream;
33import java.security.cert.CertificateFactory;
34import java.security.cert.Certificate;
35import java.security.cert.X509Certificate;
36import java.security.MessageDigest;
37import java.security.NoSuchAlgorithmException;
38import java.security.PrivateKey;
39import java.security.Signature;
40import java.security.spec.PKCS8EncodedKeySpec;
41import java.security.KeyFactory;
42import java.util.HashSet;
43import java.io.*;
44import libcore.io.IoUtils;
45
46/**
47 * Tests for {@link com.android.server.CertPinInstallReceiver}
48 */
49public 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}