2 * Copyright (C) 2012-2013 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * Hochschule fuer Technik Rapperswil
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>.
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
18 package org
.strongswan
.android
.logic
;
21 import java
.security
.PrivateKey
;
22 import java
.security
.cert
.CertificateEncodingException
;
23 import java
.security
.cert
.X509Certificate
;
24 import java
.util
.ArrayList
;
25 import java
.util
.List
;
27 import org
.strongswan
.android
.data
.VpnProfile
;
28 import org
.strongswan
.android
.data
.VpnProfileDataSource
;
29 import org
.strongswan
.android
.logic
.VpnStateService
.ErrorState
;
30 import org
.strongswan
.android
.logic
.VpnStateService
.State
;
31 import org
.strongswan
.android
.logic
.imc
.ImcState
;
32 import org
.strongswan
.android
.logic
.imc
.RemediationInstruction
;
33 import org
.strongswan
.android
.ui
.MainActivity
;
35 import android
.app
.PendingIntent
;
36 import android
.app
.Service
;
37 import android
.content
.ComponentName
;
38 import android
.content
.Context
;
39 import android
.content
.Intent
;
40 import android
.content
.ServiceConnection
;
41 import android
.net
.VpnService
;
42 import android
.os
.Bundle
;
43 import android
.os
.IBinder
;
44 import android
.os
.ParcelFileDescriptor
;
45 import android
.security
.KeyChain
;
46 import android
.security
.KeyChainException
;
47 import android
.util
.Log
;
49 public class CharonVpnService
extends VpnService
implements Runnable
51 private static final String TAG
= CharonVpnService
.class.getSimpleName();
52 public static final String LOG_FILE
= "charon.log";
54 private String mLogFile
;
55 private VpnProfileDataSource mDataSource
;
56 private Thread mConnectionHandler
;
57 private VpnProfile mCurrentProfile
;
58 private volatile String mCurrentCertificateAlias
;
59 private volatile String mCurrentUserCertificateAlias
;
60 private VpnProfile mNextProfile
;
61 private volatile boolean mProfileUpdated
;
62 private volatile boolean mTerminate
;
63 private volatile boolean mIsDisconnecting
;
64 private VpnStateService mService
;
65 private final Object mServiceLock
= new Object();
66 private final ServiceConnection mServiceConnection
= new ServiceConnection() {
68 public void onServiceDisconnected(ComponentName name
)
69 { /* since the service is local this is theoretically only called when the process is terminated */
70 synchronized (mServiceLock
)
77 public void onServiceConnected(ComponentName name
, IBinder service
)
79 synchronized (mServiceLock
)
81 mService
= ((VpnStateService
.LocalBinder
)service
).getService();
83 /* we are now ready to start the handler thread */
84 mConnectionHandler
.start();
89 * as defined in charonservice.h
91 static final int STATE_CHILD_SA_UP
= 1;
92 static final int STATE_CHILD_SA_DOWN
= 2;
93 static final int STATE_AUTH_ERROR
= 3;
94 static final int STATE_PEER_AUTH_ERROR
= 4;
95 static final int STATE_LOOKUP_ERROR
= 5;
96 static final int STATE_UNREACHABLE_ERROR
= 6;
97 static final int STATE_GENERIC_ERROR
= 7;
100 public int onStartCommand(Intent intent
, int flags
, int startId
)
104 Bundle bundle
= intent
.getExtras();
105 VpnProfile profile
= null
;
108 profile
= mDataSource
.getVpnProfile(bundle
.getLong(VpnProfileDataSource
.KEY_ID
));
111 String password
= bundle
.getString(VpnProfileDataSource
.KEY_PASSWORD
);
112 profile
.setPassword(password
);
115 setNextProfile(profile
);
117 return START_NOT_STICKY
;
121 public void onCreate()
123 mLogFile
= getFilesDir().getAbsolutePath() + File
.separator
+ LOG_FILE
;
125 mDataSource
= new VpnProfileDataSource(this);
127 /* use a separate thread as main thread for charon */
128 mConnectionHandler
= new Thread(this);
129 /* the thread is started when the service is bound */
130 bindService(new Intent(this, VpnStateService
.class),
131 mServiceConnection
, Service
.BIND_AUTO_CREATE
);
135 public void onRevoke()
136 { /* the system revoked the rights grated with the initial prepare() call.
137 * called when the user clicks disconnect in the system's VPN dialog */
138 setNextProfile(null
);
142 public void onDestroy()
145 setNextProfile(null
);
148 mConnectionHandler
.join();
150 catch (InterruptedException e
)
154 if (mService
!= null
)
156 unbindService(mServiceConnection
);
162 * Set the profile that is to be initiated next. Notify the handler thread.
164 * @param profile the profile to initiate
166 private void setNextProfile(VpnProfile profile
)
170 this.mNextProfile
= profile
;
171 mProfileUpdated
= true
;
185 while (!mProfileUpdated
)
190 mProfileUpdated
= false
;
191 stopCurrentConnection();
192 if (mNextProfile
== null
)
194 setState(State
.DISABLED
);
202 mCurrentProfile
= mNextProfile
;
205 /* store this in a separate (volatile) variable to avoid
206 * a possible deadlock during deinitialization */
207 mCurrentCertificateAlias
= mCurrentProfile
.getCertificateAlias();
208 mCurrentUserCertificateAlias
= mCurrentProfile
.getUserCertificateAlias();
210 startConnection(mCurrentProfile
);
211 mIsDisconnecting
= false
;
213 BuilderAdapter builder
= new BuilderAdapter(mCurrentProfile
.getName());
214 if (initializeCharon(builder
, mLogFile
, mCurrentProfile
.getVpnType().getEnableBYOD()))
216 Log
.i(TAG
, "charon started");
217 initiate(mCurrentProfile
.getVpnType().getIdentifier(),
218 mCurrentProfile
.getGateway(), mCurrentProfile
.getUsername(),
219 mCurrentProfile
.getPassword());
223 Log
.e(TAG
, "failed to start charon");
224 setError(ErrorState
.GENERIC_ERROR
);
225 setState(State
.DISABLED
);
226 mCurrentProfile
= null
;
230 catch (InterruptedException ex
)
232 stopCurrentConnection();
233 setState(State
.DISABLED
);
240 * Stop any existing connection by deinitializing charon.
242 private void stopCurrentConnection()
246 if (mCurrentProfile
!= null
)
248 setState(State
.DISCONNECTING
);
249 mIsDisconnecting
= true
;
250 deinitializeCharon();
251 Log
.i(TAG
, "charon stopped");
252 mCurrentProfile
= null
;
258 * Notify the state service about a new connection attempt.
259 * Called by the handler thread.
261 * @param profile currently active VPN profile
263 private void startConnection(VpnProfile profile
)
265 synchronized (mServiceLock
)
267 if (mService
!= null
)
269 mService
.startConnection(profile
);
275 * Update the current VPN state on the state service. Called by the handler
276 * thread and any of charon's threads.
278 * @param state current state
280 private void setState(State state
)
282 synchronized (mServiceLock
)
284 if (mService
!= null
)
286 mService
.setState(state
);
292 * Set an error on the state service. Called by the handler thread and any
293 * of charon's threads.
295 * @param error error state
297 private void setError(ErrorState error
)
299 synchronized (mServiceLock
)
301 if (mService
!= null
)
303 mService
.setError(error
);
309 * Set the IMC state on the state service. Called by the handler thread and
310 * any of charon's threads.
312 * @param state IMC state
314 private void setImcState(ImcState state
)
316 synchronized (mServiceLock
)
318 if (mService
!= null
)
320 mService
.setImcState(state
);
326 * Set an error on the state service. Called by the handler thread and any
327 * of charon's threads.
329 * @param error error state
331 private void setErrorDisconnect(ErrorState error
)
333 synchronized (mServiceLock
)
335 if (mService
!= null
)
337 if (!mIsDisconnecting
)
339 mService
.setError(error
);
346 * Updates the state of the current connection.
347 * Called via JNI by different threads (but not concurrently).
349 * @param status new state
351 public void updateStatus(int status
)
355 case STATE_CHILD_SA_DOWN
:
356 if (!mIsDisconnecting
)
358 setState(State
.CONNECTING
);
361 case STATE_CHILD_SA_UP
:
362 setState(State
.CONNECTED
);
364 case STATE_AUTH_ERROR
:
365 setErrorDisconnect(ErrorState
.AUTH_FAILED
);
367 case STATE_PEER_AUTH_ERROR
:
368 setErrorDisconnect(ErrorState
.PEER_AUTH_FAILED
);
370 case STATE_LOOKUP_ERROR
:
371 setErrorDisconnect(ErrorState
.LOOKUP_FAILED
);
373 case STATE_UNREACHABLE_ERROR
:
374 setErrorDisconnect(ErrorState
.UNREACHABLE
);
376 case STATE_GENERIC_ERROR
:
377 setErrorDisconnect(ErrorState
.GENERIC_ERROR
);
380 Log
.e(TAG
, "Unknown status code received");
386 * Updates the IMC state of the current connection.
387 * Called via JNI by different threads (but not concurrently).
389 * @param value new state
391 public void updateImcState(int value
)
393 ImcState state
= ImcState
.fromValue(value
);
401 * Add a remediation instruction to the VPN state service.
402 * Called via JNI by different threads (but not concurrently).
404 * @param xml XML text
406 public void addRemediationInstruction(String xml
)
408 for (RemediationInstruction instruction
: RemediationInstruction
.fromXml(xml
))
410 synchronized (mServiceLock
)
412 if (mService
!= null
)
414 mService
.addRemediationInstruction(instruction
);
421 * Function called via JNI to generate a list of DER encoded CA certificates
424 * @return a list of DER encoded CA certificates
426 private byte[][] getTrustedCertificates()
428 ArrayList
<byte[]> certs
= new ArrayList
<byte[]>();
429 TrustedCertificateManager certman
= TrustedCertificateManager
.getInstance();
432 String alias
= this.mCurrentCertificateAlias
;
435 X509Certificate cert
= certman
.getCACertificateFromAlias(alias
);
440 certs
.add(cert
.getEncoded());
444 for (X509Certificate cert
: certman
.getAllCACertificates().values())
446 certs
.add(cert
.getEncoded());
450 catch (CertificateEncodingException e
)
455 return certs
.toArray(new byte[certs
.size()][]);
459 * Function called via JNI to get a list containing the DER encoded certificates
460 * of the user selected certificate chain (beginning with the user certificate).
462 * Since this method is called from a thread of charon's thread pool we are safe
463 * to call methods on KeyChain directly.
465 * @return list containing the certificates (first element is the user certificate)
466 * @throws InterruptedException
467 * @throws KeyChainException
468 * @throws CertificateEncodingException
470 private byte[][] getUserCertificate() throws KeyChainException
, InterruptedException
, CertificateEncodingException
472 ArrayList
<byte[]> encodings
= new ArrayList
<byte[]>();
473 X509Certificate
[] chain
= KeyChain
.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias
);
474 if (chain
== null
|| chain
.length
== 0)
478 for (X509Certificate cert
: chain
)
480 encodings
.add(cert
.getEncoded());
482 return encodings
.toArray(new byte[encodings
.size()][]);
486 * Function called via JNI to get the private key the user selected.
488 * Since this method is called from a thread of charon's thread pool we are safe
489 * to call methods on KeyChain directly.
491 * @return the private key
492 * @throws InterruptedException
493 * @throws KeyChainException
494 * @throws CertificateEncodingException
496 private PrivateKey
getUserKey() throws KeyChainException
, InterruptedException
498 return KeyChain
.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias
);
503 * Initialization of charon, provided by libandroidbridge.so
505 * @param builder BuilderAdapter for this connection
506 * @param logfile absolute path to the logfile
507 * @param boyd enable BYOD features
508 * @return TRUE if initialization was successful
510 public native boolean initializeCharon(BuilderAdapter builder
, String logfile
, boolean byod
);
513 * Deinitialize charon, provided by libandroidbridge.so
515 public native void deinitializeCharon();
518 * Initiate VPN, provided by libandroidbridge.so
520 public native void initiate(String type
, String gateway
, String username
, String password
);
523 * Adapter for VpnService.Builder which is used to access it safely via JNI.
524 * There is a corresponding C object to access it from native code.
526 public class BuilderAdapter
528 private final String mName
;
529 private VpnService
.Builder mBuilder
;
530 private BuilderCache mCache
;
531 private BuilderCache mEstablishedCache
;
533 public BuilderAdapter(String name
)
536 mBuilder
= createBuilder(name
);
537 mCache
= new BuilderCache();
540 private VpnService
.Builder
createBuilder(String name
)
542 VpnService
.Builder builder
= new CharonVpnService
.Builder();
543 builder
.setSession(mName
);
545 /* even though the option displayed in the system dialog says "Configure"
546 * we just use our main Activity */
547 Context context
= getApplicationContext();
548 Intent intent
= new Intent(context
, MainActivity
.class);
549 PendingIntent pending
= PendingIntent
.getActivity(context
, 0, intent
,
550 PendingIntent
.FLAG_UPDATE_CURRENT
);
551 builder
.setConfigureIntent(pending
);
555 public synchronized boolean addAddress(String address
, int prefixLength
)
559 mBuilder
.addAddress(address
, prefixLength
);
560 mCache
.addAddress(address
, prefixLength
);
562 catch (IllegalArgumentException ex
)
569 public synchronized boolean addDnsServer(String address
)
573 mBuilder
.addDnsServer(address
);
575 catch (IllegalArgumentException ex
)
582 public synchronized boolean addRoute(String address
, int prefixLength
)
586 mBuilder
.addRoute(address
, prefixLength
);
587 mCache
.addRoute(address
, prefixLength
);
589 catch (IllegalArgumentException ex
)
596 public synchronized boolean addSearchDomain(String domain
)
600 mBuilder
.addSearchDomain(domain
);
602 catch (IllegalArgumentException ex
)
609 public synchronized boolean setMtu(int mtu
)
613 mBuilder
.setMtu(mtu
);
616 catch (IllegalArgumentException ex
)
623 public synchronized int establish()
625 ParcelFileDescriptor fd
;
628 fd
= mBuilder
.establish();
632 ex
.printStackTrace();
639 /* now that the TUN device is created we don't need the current
640 * builder anymore, but we might need another when reestablishing */
641 mBuilder
= createBuilder(mName
);
642 mEstablishedCache
= mCache
;
643 mCache
= new BuilderCache();
644 return fd
.detachFd();
647 public synchronized int establishNoDns()
649 ParcelFileDescriptor fd
;
651 if (mEstablishedCache
== null
)
657 Builder builder
= createBuilder(mName
);
658 mEstablishedCache
.applyData(builder
);
659 fd
= builder
.establish();
663 ex
.printStackTrace();
670 return fd
.detachFd();
675 * Cache non DNS related information so we can recreate the builder without
676 * that information when reestablishing IKE_SAs
678 public class BuilderCache
680 private final List
<PrefixedAddress
> mAddresses
= new ArrayList
<PrefixedAddress
>();
681 private final List
<PrefixedAddress
> mRoutes
= new ArrayList
<PrefixedAddress
>();
684 public void addAddress(String address
, int prefixLength
)
686 mAddresses
.add(new PrefixedAddress(address
, prefixLength
));
689 public void addRoute(String address
, int prefixLength
)
691 mRoutes
.add(new PrefixedAddress(address
, prefixLength
));
694 public void setMtu(int mtu
)
699 public void applyData(VpnService
.Builder builder
)
701 for (PrefixedAddress address
: mAddresses
)
703 builder
.addAddress(address
.mAddress
, address
.mPrefix
);
705 for (PrefixedAddress route
: mRoutes
)
707 builder
.addRoute(route
.mAddress
, route
.mPrefix
);
709 builder
.setMtu(mMtu
);
712 private class PrefixedAddress
714 public String mAddress
;
717 public PrefixedAddress(String address
, int prefix
)
719 this.mAddress
= address
;
720 this.mPrefix
= prefix
;
726 * The libraries are extracted to /data/data/org.strongswan.android/...
727 * during installation.
731 System
.loadLibrary("strongswan");
733 if (MainActivity
.USE_BYOD
)
735 System
.loadLibrary("tncif");
736 System
.loadLibrary("tnccs");
737 System
.loadLibrary("imcv");
740 System
.loadLibrary("hydra");
741 System
.loadLibrary("charon");
742 System
.loadLibrary("ipsec");
743 System
.loadLibrary("androidbridge");