d66a5517d8a5b75c108d473d140f2366fc495fb0
[strongswan.git] / src / frontends / android / src / org / strongswan / android / logic / CharonVpnService.java
1 /*
2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * Hochschule fuer Technik Rapperswil
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 package org.strongswan.android.logic;
19
20 import java.security.cert.CertificateEncodingException;
21 import java.security.cert.X509Certificate;
22 import java.util.ArrayList;
23
24 import org.strongswan.android.data.VpnProfile;
25 import org.strongswan.android.data.VpnProfileDataSource;
26 import org.strongswan.android.logic.VpnStateService.ErrorState;
27 import org.strongswan.android.logic.VpnStateService.State;
28
29 import android.app.Service;
30 import android.content.ComponentName;
31 import android.content.Intent;
32 import android.content.ServiceConnection;
33 import android.net.VpnService;
34 import android.os.Bundle;
35 import android.os.IBinder;
36 import android.util.Log;
37
38 public class CharonVpnService extends VpnService implements Runnable
39 {
40 private static final String TAG = CharonVpnService.class.getSimpleName();
41 private VpnProfileDataSource mDataSource;
42 private Thread mConnectionHandler;
43 private VpnProfile mCurrentProfile;
44 private VpnProfile mNextProfile;
45 private volatile boolean mProfileUpdated;
46 private volatile boolean mTerminate;
47 private VpnStateService mService;
48 private final Object mServiceLock = new Object();
49 private final ServiceConnection mServiceConnection = new ServiceConnection() {
50 @Override
51 public void onServiceDisconnected(ComponentName name)
52 { /* since the service is local this is theoretically only called when the process is terminated */
53 synchronized (mServiceLock)
54 {
55 mService = null;
56 }
57 }
58
59 @Override
60 public void onServiceConnected(ComponentName name, IBinder service)
61 {
62 synchronized (mServiceLock)
63 {
64 mService = ((VpnStateService.LocalBinder)service).getService();
65 }
66 /* we are now ready to start the handler thread */
67 mConnectionHandler.start();
68 }
69 };
70
71 /**
72 * as defined in charonservice.h
73 */
74 static final int STATE_CHILD_SA_UP = 1;
75 static final int STATE_CHILD_SA_DOWN = 2;
76 static final int STATE_AUTH_ERROR = 3;
77 static final int STATE_PEER_AUTH_ERROR = 4;
78 static final int STATE_LOOKUP_ERROR = 5;
79 static final int STATE_UNREACHABLE_ERROR = 6;
80 static final int STATE_GENERIC_ERROR = 7;
81
82 @Override
83 public int onStartCommand(Intent intent, int flags, int startId)
84 {
85 if (intent != null)
86 {
87 Bundle bundle = intent.getExtras();
88 VpnProfile profile = null;
89 if (bundle != null)
90 {
91 profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID));
92 if (profile != null)
93 {
94 String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD);
95 profile.setPassword(password);
96 }
97 }
98 setNextProfile(profile);
99 }
100 return START_NOT_STICKY;
101 }
102
103 @Override
104 public void onCreate()
105 {
106 mDataSource = new VpnProfileDataSource(this);
107 mDataSource.open();
108 /* use a separate thread as main thread for charon */
109 mConnectionHandler = new Thread(this);
110 /* the thread is started when the service is bound */
111 bindService(new Intent(this, VpnStateService.class),
112 mServiceConnection, Service.BIND_AUTO_CREATE);
113 }
114
115 @Override
116 public void onRevoke()
117 { /* the system revoked the rights grated with the initial prepare() call.
118 * called when the user clicks disconnect in the system's VPN dialog */
119 setNextProfile(null);
120 }
121
122 @Override
123 public void onDestroy()
124 {
125 mTerminate = true;
126 setNextProfile(null);
127 try
128 {
129 mConnectionHandler.join();
130 }
131 catch (InterruptedException e)
132 {
133 e.printStackTrace();
134 }
135 if (mService != null)
136 {
137 unbindService(mServiceConnection);
138 }
139 mDataSource.close();
140 }
141
142 /**
143 * Set the profile that is to be initiated next. Notify the handler thread.
144 *
145 * @param profile the profile to initiate
146 */
147 private void setNextProfile(VpnProfile profile)
148 {
149 synchronized (this)
150 {
151 this.mNextProfile = profile;
152 mProfileUpdated = true;
153 notifyAll();
154 }
155 }
156
157 @Override
158 public void run()
159 {
160 while (true)
161 {
162 synchronized (this)
163 {
164 try
165 {
166 while (!mProfileUpdated)
167 {
168 wait();
169 }
170
171 mProfileUpdated = false;
172 stopCurrentConnection();
173 if (mNextProfile == null)
174 {
175 setProfile(null);
176 setState(State.DISABLED);
177 if (mTerminate)
178 {
179 break;
180 }
181 }
182 else
183 {
184 mCurrentProfile = mNextProfile;
185 mNextProfile = null;
186
187 setProfile(mCurrentProfile);
188 setError(ErrorState.NO_ERROR);
189 setState(State.CONNECTING);
190
191 initializeCharon();
192 Log.i(TAG, "charon started");
193 }
194 }
195 catch (InterruptedException ex)
196 {
197 stopCurrentConnection();
198 setState(State.DISABLED);
199 }
200 }
201 }
202 }
203
204 /**
205 * Stop any existing connection by deinitializing charon.
206 */
207 private void stopCurrentConnection()
208 {
209 synchronized (this)
210 {
211 if (mCurrentProfile != null)
212 {
213 setState(State.DISCONNECTING);
214 deinitializeCharon();
215 Log.i(TAG, "charon stopped");
216 mCurrentProfile = null;
217 }
218 }
219 }
220
221 /**
222 * Update the VPN profile on the state service. Called by the handler thread.
223 *
224 * @param profile currently active VPN profile
225 */
226 private void setProfile(VpnProfile profile)
227 {
228 synchronized (mServiceLock)
229 {
230 if (mService != null)
231 {
232 mService.setProfile(profile);
233 }
234 }
235 }
236
237 /**
238 * Update the current VPN state on the state service. Called by the handler
239 * thread and any of charon's threads.
240 *
241 * @param state current state
242 */
243 private void setState(State state)
244 {
245 synchronized (mServiceLock)
246 {
247 if (mService != null)
248 {
249 mService.setState(state);
250 }
251 }
252 }
253
254 /**
255 * Set an error on the state service. Called by the handler thread and any
256 * of charon's threads.
257 *
258 * @param error error state
259 */
260 private void setError(ErrorState error)
261 {
262 synchronized (mServiceLock)
263 {
264 if (mService != null)
265 {
266 mService.setError(error);
267 }
268 }
269 }
270
271 /**
272 * Set an error on the state service and disconnect the current connection.
273 * This is not done by calling stopCurrentConnection() above, but instead
274 * is done asynchronously via state service.
275 *
276 * @param error error state
277 */
278 private void setErrorDisconnect(ErrorState error)
279 {
280 synchronized (mServiceLock)
281 {
282 if (mService != null)
283 {
284 mService.setError(error);
285 mService.disconnect();
286 }
287 }
288 }
289
290 /**
291 * Updates the state of the current connection.
292 * Called via JNI by different threads (but not concurrently).
293 *
294 * @param status new state
295 */
296 public void updateStatus(int status)
297 {
298 switch (status)
299 {
300 case STATE_CHILD_SA_DOWN:
301 synchronized (mServiceLock)
302 {
303 /* since this state is also reached when the SA is closed remotely,
304 * we call disconnect() to make sure charon is properly deinitialized */
305 if (mService != null)
306 {
307 mService.disconnect();
308 }
309 }
310 break;
311 case STATE_CHILD_SA_UP:
312 setState(State.CONNECTED);
313 break;
314 case STATE_AUTH_ERROR:
315 setErrorDisconnect(ErrorState.AUTH_FAILED);
316 break;
317 case STATE_PEER_AUTH_ERROR:
318 setErrorDisconnect(ErrorState.PEER_AUTH_FAILED);
319 break;
320 case STATE_LOOKUP_ERROR:
321 setErrorDisconnect(ErrorState.LOOKUP_FAILED);
322 break;
323 case STATE_UNREACHABLE_ERROR:
324 setErrorDisconnect(ErrorState.UNREACHABLE);
325 break;
326 case STATE_GENERIC_ERROR:
327 setErrorDisconnect(ErrorState.GENERIC_ERROR);
328 break;
329 default:
330 Log.e(TAG, "Unknown status code received");
331 break;
332 }
333 }
334
335 /**
336 * Function called via JNI to generate a list of DER encoded CA certificates
337 * as byte array.
338 *
339 * @param hash optional alias (only hash part), if given matching certificates are returned
340 * @return a list of DER encoded CA certificates
341 */
342 private synchronized byte[][] getTrustedCertificates(String hash)
343 {
344 ArrayList<byte[]> certs = new ArrayList<byte[]>();
345 TrustedCertificateManager certman = TrustedCertificateManager.getInstance();
346 try
347 {
348 if (hash != null)
349 {
350 String alias = "user:" + hash + ".0";
351 X509Certificate cert = certman.getCACertificateFromAlias(alias);
352 if (cert == null)
353 {
354 alias = "system:" + hash + ".0";
355 cert = certman.getCACertificateFromAlias(alias);
356 }
357 if (cert == null)
358 {
359 return null;
360 }
361 certs.add(cert.getEncoded());
362 }
363 else
364 {
365 String alias = this.mCurrentProfile.getCertificateAlias();
366 if (alias != null)
367 {
368 X509Certificate cert = certman.getCACertificateFromAlias(alias);
369 if (cert == null)
370 {
371 return null;
372 }
373 certs.add(cert.getEncoded());
374 }
375 else
376 {
377 for (X509Certificate cert : certman.getAllCACertificates().values())
378 {
379 certs.add(cert.getEncoded());
380 }
381 }
382 }
383 }
384 catch (CertificateEncodingException e)
385 {
386 e.printStackTrace();
387 return null;
388 }
389 return certs.toArray(new byte[certs.size()][]);
390 }
391
392 /**
393 * Initialization of charon, provided by libandroidbridge.so
394 */
395 public native void initializeCharon();
396
397 /**
398 * Deinitialize charon, provided by libandroidbridge.so
399 */
400 public native void deinitializeCharon();
401
402 /*
403 * The libraries are extracted to /data/data/org.strongswan.android/...
404 * during installation.
405 */
406 static
407 {
408 System.loadLibrary("crypto");
409 System.loadLibrary("strongswan");
410 System.loadLibrary("hydra");
411 System.loadLibrary("charon");
412 System.loadLibrary("ipsec");
413 System.loadLibrary("androidbridge");
414 }
415 }