1/*
2 * Copyright (C) 2011 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.keychain.tests;
18
19import android.app.Service;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.os.IBinder;
25import android.security.Credentials;
26import android.security.IKeyChainService;
27import android.security.KeyStore;
28import android.util.Log;
29import com.android.keychain.tests.support.IKeyChainServiceTestSupport;
30import java.security.KeyStore.PrivateKeyEntry;
31import java.security.cert.Certificate;
32import java.util.Arrays;
33import junit.framework.Assert;
34import libcore.java.security.TestKeyStore;
35
36public class KeyChainServiceTest extends Service {
37
38    private static final String TAG = "KeyChainServiceTest";
39
40    private final Object mSupportLock = new Object();
41    private IKeyChainServiceTestSupport mSupport;
42    private boolean mIsBoundSupport;
43
44    private final Object mServiceLock = new Object();
45    private IKeyChainService mService;
46    private boolean mIsBoundService;
47
48    private ServiceConnection mSupportConnection = new ServiceConnection() {
49        @Override public void onServiceConnected(ComponentName name, IBinder service) {
50            synchronized (mSupportLock) {
51                mSupport = IKeyChainServiceTestSupport.Stub.asInterface(service);
52                mSupportLock.notifyAll();
53            }
54        }
55
56        @Override public void onServiceDisconnected(ComponentName name) {
57            synchronized (mSupportLock) {
58                mSupport = null;
59            }
60        }
61    };
62
63    private ServiceConnection mServiceConnection = new ServiceConnection() {
64        @Override public void onServiceConnected(ComponentName name, IBinder service) {
65            synchronized (mServiceLock) {
66                mService = IKeyChainService.Stub.asInterface(service);
67                mServiceLock.notifyAll();
68            }
69        }
70
71        @Override public void onServiceDisconnected(ComponentName name) {
72            synchronized (mServiceLock) {
73                mService = null;
74            }
75        }
76    };
77
78    private void bindSupport() {
79        mIsBoundSupport = bindService(new Intent(IKeyChainServiceTestSupport.class.getName()),
80                                      mSupportConnection,
81                                      Context.BIND_AUTO_CREATE);
82    }
83
84    private void bindService() {
85        mIsBoundService = bindService(new Intent(IKeyChainService.class.getName()),
86                                      mServiceConnection,
87                                      Context.BIND_AUTO_CREATE);
88    }
89
90    private void unbindServices() {
91        if (mIsBoundSupport) {
92            unbindService(mSupportConnection);
93            mIsBoundSupport = false;
94        }
95        if (mIsBoundService) {
96            unbindService(mServiceConnection);
97            mIsBoundService = false;
98        }
99    }
100
101    @Override public IBinder onBind(Intent intent) {
102        Log.d(TAG, "onBind");
103        return null;
104    }
105
106    @Override public int onStartCommand(Intent intent, int flags, int startId) {
107        Log.d(TAG, "onStartCommand");
108        new Thread(new Test(), TAG).start();
109        return START_STICKY;
110    }
111
112    @Override public void onDestroy () {
113        Log.d(TAG, "onDestroy");
114        unbindServices();
115    }
116
117    private final class Test extends Assert implements Runnable {
118
119        @Override public void run() {
120            try {
121                test_KeyChainService();
122            } catch (RuntimeException e) {
123                // rethrow RuntimeException without wrapping
124                throw e;
125            } catch (Exception e) {
126                throw new RuntimeException(e);
127            } finally {
128                stopSelf();
129            }
130        }
131
132        public void test_KeyChainService() throws Exception {
133            Log.d(TAG, "test_KeyChainService uid=" + getApplicationInfo().uid);
134
135            Log.d(TAG, "test_KeyChainService bind support");
136            bindSupport();
137            assertTrue(mIsBoundSupport);
138            synchronized (mSupportLock) {
139                if (mSupport == null) {
140                    mSupportLock.wait(10 * 1000);
141                }
142            }
143            assertNotNull(mSupport);
144
145            Log.d(TAG, "test_KeyChainService setup keystore");
146            KeyStore keyStore = KeyStore.getInstance();
147            assertTrue(mSupport.keystoreReset());
148            assertTrue(mSupport.keystorePassword("newpasswd"));
149
150            String intermediate = "-intermediate";
151            String root = "-root";
152
153            String alias1 = "client";
154            String alias1Intermediate = alias1 + intermediate;
155            String alias1Root = alias1 + root;
156            String alias1Pkey = (Credentials.USER_PRIVATE_KEY + alias1);
157            String alias1Cert = (Credentials.USER_CERTIFICATE + alias1);
158            String alias1ICert = (Credentials.CA_CERTIFICATE + alias1Intermediate);
159            String alias1RCert = (Credentials.CA_CERTIFICATE + alias1Root);
160            PrivateKeyEntry pke1 = TestKeyStore.getClientCertificate().getPrivateKey("RSA", "RSA");
161            Certificate intermediate1 = pke1.getCertificateChain()[1];
162            Certificate root1 = TestKeyStore.getClientCertificate().getRootCertificate("RSA");
163
164            final String alias2 = "server";
165            String alias2Intermediate = alias2 + intermediate;
166            String alias2Root = alias2 + root;
167            String alias2Pkey = (Credentials.USER_PRIVATE_KEY + alias2);
168            String alias2Cert = (Credentials.USER_CERTIFICATE + alias2);
169            String alias2ICert = (Credentials.CA_CERTIFICATE + alias2Intermediate);
170            String alias2RCert = (Credentials.CA_CERTIFICATE + alias2Root);
171            PrivateKeyEntry pke2 = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
172            Certificate intermediate2 = pke2.getCertificateChain()[1];
173            Certificate root2 = TestKeyStore.getServer().getRootCertificate("RSA");
174
175            assertTrue(mSupport.keystoreImportKey(alias1Pkey,
176                                           pke1.getPrivateKey().getEncoded()));
177            assertTrue(mSupport.keystorePut(alias1Cert,
178                                            Credentials.convertToPem(pke1.getCertificate())));
179            assertTrue(mSupport.keystorePut(alias1ICert,
180                                            Credentials.convertToPem(intermediate1)));
181            assertTrue(mSupport.keystorePut(alias1RCert,
182                                            Credentials.convertToPem(root1)));
183            assertTrue(mSupport.keystoreImportKey(alias2Pkey,
184                                            pke2.getPrivateKey().getEncoded()));
185            assertTrue(mSupport.keystorePut(alias2Cert,
186                                            Credentials.convertToPem(pke2.getCertificate())));
187            assertTrue(mSupport.keystorePut(alias2ICert,
188                                            Credentials.convertToPem(intermediate2)));
189            assertTrue(mSupport.keystorePut(alias2RCert,
190                                            Credentials.convertToPem(root2)));
191
192            assertEquals(KeyStore.State.UNLOCKED, keyStore.state());
193
194            Log.d(TAG, "test_KeyChainService bind service");
195            bindService();
196            assertTrue(mIsBoundService);
197            synchronized (mServiceLock) {
198                if (mService == null) {
199                    mServiceLock.wait(10 * 1000);
200                }
201            }
202            assertNotNull(mService);
203
204            mSupport.grantAppPermission(getApplicationInfo().uid, alias1);
205            // don't grant alias2, so it can be done manually with KeyChainTestActivity
206            Log.d(TAG, "test_KeyChainService positive testing");
207            assertNotNull("Requesting private key should succeed",
208                    mService.requestPrivateKey(alias1));
209
210            byte[] certificate = mService.getCertificate(alias1);
211            assertNotNull(certificate);
212            assertEquals(Arrays.toString(Credentials.convertToPem(pke1.getCertificate())),
213                         Arrays.toString(certificate));
214
215            Log.d(TAG, "test_KeyChainService negative testing");
216            mSupport.revokeAppPermission(getApplicationInfo().uid, alias2);
217            try {
218                mService.requestPrivateKey(alias2);
219                fail();
220            } catch (IllegalStateException expected) {
221            }
222
223            try {
224                mService.getCertificate(alias2);
225                fail();
226            } catch (IllegalStateException expected) {
227            }
228
229            Log.d(TAG, "test_KeyChainService unbind");
230            unbindServices();
231            assertFalse(mIsBoundSupport);
232            assertFalse(mIsBoundService);
233
234            Log.d(TAG, "test_KeyChainService end");
235        }
236    }
237}
238