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