libhydra: Remove empty unused library
[strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / logic / CharonVpnService.java
1 /*
2 * Copyright (C) 2012-2015 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.io.File;
21 import java.net.Inet4Address;
22 import java.net.Inet6Address;
23 import java.net.InetAddress;
24 import java.net.UnknownHostException;
25 import java.security.PrivateKey;
26 import java.security.cert.CertificateEncodingException;
27 import java.security.cert.X509Certificate;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Locale;
31
32 import org.strongswan.android.data.VpnProfile;
33 import org.strongswan.android.data.VpnProfileDataSource;
34 import org.strongswan.android.data.VpnType.VpnTypeFeature;
35 import org.strongswan.android.logic.VpnStateService.ErrorState;
36 import org.strongswan.android.logic.VpnStateService.State;
37 import org.strongswan.android.logic.imc.ImcState;
38 import org.strongswan.android.logic.imc.RemediationInstruction;
39 import org.strongswan.android.ui.MainActivity;
40 import org.strongswan.android.utils.SettingsWriter;
41
42 import android.annotation.TargetApi;
43 import android.app.PendingIntent;
44 import android.app.Service;
45 import android.content.ComponentName;
46 import android.content.Context;
47 import android.content.Intent;
48 import android.content.ServiceConnection;
49 import android.net.VpnService;
50 import android.os.Build;
51 import android.os.Bundle;
52 import android.os.IBinder;
53 import android.os.ParcelFileDescriptor;
54 import android.security.KeyChain;
55 import android.security.KeyChainException;
56 import android.system.OsConstants;
57 import android.util.Log;
58
59 public class CharonVpnService extends VpnService implements Runnable
60 {
61 private static final String TAG = CharonVpnService.class.getSimpleName();
62 public static final String LOG_FILE = "charon.log";
63
64 private String mLogFile;
65 private VpnProfileDataSource mDataSource;
66 private Thread mConnectionHandler;
67 private VpnProfile mCurrentProfile;
68 private volatile String mCurrentCertificateAlias;
69 private volatile String mCurrentUserCertificateAlias;
70 private VpnProfile mNextProfile;
71 private volatile boolean mProfileUpdated;
72 private volatile boolean mTerminate;
73 private volatile boolean mIsDisconnecting;
74 private VpnStateService mService;
75 private final Object mServiceLock = new Object();
76 private final ServiceConnection mServiceConnection = new ServiceConnection() {
77 @Override
78 public void onServiceDisconnected(ComponentName name)
79 { /* since the service is local this is theoretically only called when the process is terminated */
80 synchronized (mServiceLock)
81 {
82 mService = null;
83 }
84 }
85
86 @Override
87 public void onServiceConnected(ComponentName name, IBinder service)
88 {
89 synchronized (mServiceLock)
90 {
91 mService = ((VpnStateService.LocalBinder)service).getService();
92 }
93 /* we are now ready to start the handler thread */
94 mConnectionHandler.start();
95 }
96 };
97
98 /**
99 * as defined in charonservice.h
100 */
101 static final int STATE_CHILD_SA_UP = 1;
102 static final int STATE_CHILD_SA_DOWN = 2;
103 static final int STATE_AUTH_ERROR = 3;
104 static final int STATE_PEER_AUTH_ERROR = 4;
105 static final int STATE_LOOKUP_ERROR = 5;
106 static final int STATE_UNREACHABLE_ERROR = 6;
107 static final int STATE_GENERIC_ERROR = 7;
108
109 @Override
110 public int onStartCommand(Intent intent, int flags, int startId)
111 {
112 if (intent != null)
113 {
114 Bundle bundle = intent.getExtras();
115 VpnProfile profile = null;
116 if (bundle != null)
117 {
118 profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID));
119 if (profile != null)
120 {
121 String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD);
122 profile.setPassword(password);
123 }
124 }
125 setNextProfile(profile);
126 }
127 return START_NOT_STICKY;
128 }
129
130 @Override
131 public void onCreate()
132 {
133 mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE;
134
135 mDataSource = new VpnProfileDataSource(this);
136 mDataSource.open();
137 /* use a separate thread as main thread for charon */
138 mConnectionHandler = new Thread(this);
139 /* the thread is started when the service is bound */
140 bindService(new Intent(this, VpnStateService.class),
141 mServiceConnection, Service.BIND_AUTO_CREATE);
142 }
143
144 @Override
145 public void onRevoke()
146 { /* the system revoked the rights grated with the initial prepare() call.
147 * called when the user clicks disconnect in the system's VPN dialog */
148 setNextProfile(null);
149 }
150
151 @Override
152 public void onDestroy()
153 {
154 mTerminate = true;
155 setNextProfile(null);
156 try
157 {
158 mConnectionHandler.join();
159 }
160 catch (InterruptedException e)
161 {
162 e.printStackTrace();
163 }
164 if (mService != null)
165 {
166 unbindService(mServiceConnection);
167 }
168 mDataSource.close();
169 }
170
171 /**
172 * Set the profile that is to be initiated next. Notify the handler thread.
173 *
174 * @param profile the profile to initiate
175 */
176 private void setNextProfile(VpnProfile profile)
177 {
178 synchronized (this)
179 {
180 this.mNextProfile = profile;
181 mProfileUpdated = true;
182 notifyAll();
183 }
184 }
185
186 @Override
187 public void run()
188 {
189 while (true)
190 {
191 synchronized (this)
192 {
193 try
194 {
195 while (!mProfileUpdated)
196 {
197 wait();
198 }
199
200 mProfileUpdated = false;
201 stopCurrentConnection();
202 if (mNextProfile == null)
203 {
204 setState(State.DISABLED);
205 if (mTerminate)
206 {
207 break;
208 }
209 }
210 else
211 {
212 mCurrentProfile = mNextProfile;
213 mNextProfile = null;
214
215 /* store this in a separate (volatile) variable to avoid
216 * a possible deadlock during deinitialization */
217 mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias();
218 mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias();
219
220 startConnection(mCurrentProfile);
221 mIsDisconnecting = false;
222
223 BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling());
224 if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
225 {
226 Log.i(TAG, "charon started");
227 SettingsWriter writer = new SettingsWriter();
228 writer.setValue("global.language", Locale.getDefault().getLanguage());
229 writer.setValue("global.mtu", mCurrentProfile.getMTU());
230 writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier());
231 writer.setValue("connection.server", mCurrentProfile.getGateway());
232 writer.setValue("connection.port", mCurrentProfile.getPort());
233 writer.setValue("connection.username", mCurrentProfile.getUsername());
234 writer.setValue("connection.password", mCurrentProfile.getPassword());
235 initiate(writer.serialize());
236 }
237 else
238 {
239 Log.e(TAG, "failed to start charon");
240 setError(ErrorState.GENERIC_ERROR);
241 setState(State.DISABLED);
242 mCurrentProfile = null;
243 }
244 }
245 }
246 catch (InterruptedException ex)
247 {
248 stopCurrentConnection();
249 setState(State.DISABLED);
250 }
251 }
252 }
253 }
254
255 /**
256 * Stop any existing connection by deinitializing charon.
257 */
258 private void stopCurrentConnection()
259 {
260 synchronized (this)
261 {
262 if (mCurrentProfile != null)
263 {
264 setState(State.DISCONNECTING);
265 mIsDisconnecting = true;
266 deinitializeCharon();
267 Log.i(TAG, "charon stopped");
268 mCurrentProfile = null;
269 }
270 }
271 }
272
273 /**
274 * Notify the state service about a new connection attempt.
275 * Called by the handler thread.
276 *
277 * @param profile currently active VPN profile
278 */
279 private void startConnection(VpnProfile profile)
280 {
281 synchronized (mServiceLock)
282 {
283 if (mService != null)
284 {
285 mService.startConnection(profile);
286 }
287 }
288 }
289
290 /**
291 * Update the current VPN state on the state service. Called by the handler
292 * thread and any of charon's threads.
293 *
294 * @param state current state
295 */
296 private void setState(State state)
297 {
298 synchronized (mServiceLock)
299 {
300 if (mService != null)
301 {
302 mService.setState(state);
303 }
304 }
305 }
306
307 /**
308 * Set an error on the state service. Called by the handler thread and any
309 * of charon's threads.
310 *
311 * @param error error state
312 */
313 private void setError(ErrorState error)
314 {
315 synchronized (mServiceLock)
316 {
317 if (mService != null)
318 {
319 mService.setError(error);
320 }
321 }
322 }
323
324 /**
325 * Set the IMC state on the state service. Called by the handler thread and
326 * any of charon's threads.
327 *
328 * @param state IMC state
329 */
330 private void setImcState(ImcState state)
331 {
332 synchronized (mServiceLock)
333 {
334 if (mService != null)
335 {
336 mService.setImcState(state);
337 }
338 }
339 }
340
341 /**
342 * Set an error on the state service. Called by the handler thread and any
343 * of charon's threads.
344 *
345 * @param error error state
346 */
347 private void setErrorDisconnect(ErrorState error)
348 {
349 synchronized (mServiceLock)
350 {
351 if (mService != null)
352 {
353 if (!mIsDisconnecting)
354 {
355 mService.setError(error);
356 }
357 }
358 }
359 }
360
361 /**
362 * Updates the state of the current connection.
363 * Called via JNI by different threads (but not concurrently).
364 *
365 * @param status new state
366 */
367 public void updateStatus(int status)
368 {
369 switch (status)
370 {
371 case STATE_CHILD_SA_DOWN:
372 if (!mIsDisconnecting)
373 {
374 setState(State.CONNECTING);
375 }
376 break;
377 case STATE_CHILD_SA_UP:
378 setState(State.CONNECTED);
379 break;
380 case STATE_AUTH_ERROR:
381 setErrorDisconnect(ErrorState.AUTH_FAILED);
382 break;
383 case STATE_PEER_AUTH_ERROR:
384 setErrorDisconnect(ErrorState.PEER_AUTH_FAILED);
385 break;
386 case STATE_LOOKUP_ERROR:
387 setErrorDisconnect(ErrorState.LOOKUP_FAILED);
388 break;
389 case STATE_UNREACHABLE_ERROR:
390 setErrorDisconnect(ErrorState.UNREACHABLE);
391 break;
392 case STATE_GENERIC_ERROR:
393 setErrorDisconnect(ErrorState.GENERIC_ERROR);
394 break;
395 default:
396 Log.e(TAG, "Unknown status code received");
397 break;
398 }
399 }
400
401 /**
402 * Updates the IMC state of the current connection.
403 * Called via JNI by different threads (but not concurrently).
404 *
405 * @param value new state
406 */
407 public void updateImcState(int value)
408 {
409 ImcState state = ImcState.fromValue(value);
410 if (state != null)
411 {
412 setImcState(state);
413 }
414 }
415
416 /**
417 * Add a remediation instruction to the VPN state service.
418 * Called via JNI by different threads (but not concurrently).
419 *
420 * @param xml XML text
421 */
422 public void addRemediationInstruction(String xml)
423 {
424 for (RemediationInstruction instruction : RemediationInstruction.fromXml(xml))
425 {
426 synchronized (mServiceLock)
427 {
428 if (mService != null)
429 {
430 mService.addRemediationInstruction(instruction);
431 }
432 }
433 }
434 }
435
436 /**
437 * Function called via JNI to generate a list of DER encoded CA certificates
438 * as byte array.
439 *
440 * @return a list of DER encoded CA certificates
441 */
442 private byte[][] getTrustedCertificates()
443 {
444 ArrayList<byte[]> certs = new ArrayList<byte[]>();
445 TrustedCertificateManager certman = TrustedCertificateManager.getInstance();
446 try
447 {
448 String alias = this.mCurrentCertificateAlias;
449 if (alias != null)
450 {
451 X509Certificate cert = certman.getCACertificateFromAlias(alias);
452 if (cert == null)
453 {
454 return null;
455 }
456 certs.add(cert.getEncoded());
457 }
458 else
459 {
460 for (X509Certificate cert : certman.getAllCACertificates().values())
461 {
462 certs.add(cert.getEncoded());
463 }
464 }
465 }
466 catch (CertificateEncodingException e)
467 {
468 e.printStackTrace();
469 return null;
470 }
471 return certs.toArray(new byte[certs.size()][]);
472 }
473
474 /**
475 * Function called via JNI to get a list containing the DER encoded certificates
476 * of the user selected certificate chain (beginning with the user certificate).
477 *
478 * Since this method is called from a thread of charon's thread pool we are safe
479 * to call methods on KeyChain directly.
480 *
481 * @return list containing the certificates (first element is the user certificate)
482 * @throws InterruptedException
483 * @throws KeyChainException
484 * @throws CertificateEncodingException
485 */
486 private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException
487 {
488 ArrayList<byte[]> encodings = new ArrayList<byte[]>();
489 X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias);
490 if (chain == null || chain.length == 0)
491 {
492 return null;
493 }
494 for (X509Certificate cert : chain)
495 {
496 encodings.add(cert.getEncoded());
497 }
498 return encodings.toArray(new byte[encodings.size()][]);
499 }
500
501 /**
502 * Function called via JNI to get the private key the user selected.
503 *
504 * Since this method is called from a thread of charon's thread pool we are safe
505 * to call methods on KeyChain directly.
506 *
507 * @return the private key
508 * @throws InterruptedException
509 * @throws KeyChainException
510 * @throws CertificateEncodingException
511 */
512 private PrivateKey getUserKey() throws KeyChainException, InterruptedException
513 {
514 return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias);
515 }
516
517 /**
518 * Initialization of charon, provided by libandroidbridge.so
519 *
520 * @param builder BuilderAdapter for this connection
521 * @param logfile absolute path to the logfile
522 * @param boyd enable BYOD features
523 * @return TRUE if initialization was successful
524 */
525 public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod);
526
527 /**
528 * Deinitialize charon, provided by libandroidbridge.so
529 */
530 public native void deinitializeCharon();
531
532 /**
533 * Initiate VPN, provided by libandroidbridge.so
534 */
535 public native void initiate(String config);
536
537 /**
538 * Adapter for VpnService.Builder which is used to access it safely via JNI.
539 * There is a corresponding C object to access it from native code.
540 */
541 public class BuilderAdapter
542 {
543 private final String mName;
544 private final Integer mSplitTunneling;
545 private VpnService.Builder mBuilder;
546 private BuilderCache mCache;
547 private BuilderCache mEstablishedCache;
548
549 public BuilderAdapter(String name, Integer splitTunneling)
550 {
551 mName = name;
552 mSplitTunneling = splitTunneling;
553 mBuilder = createBuilder(name);
554 mCache = new BuilderCache(mSplitTunneling);
555 }
556
557 private VpnService.Builder createBuilder(String name)
558 {
559 VpnService.Builder builder = new CharonVpnService.Builder();
560 builder.setSession(mName);
561
562 /* even though the option displayed in the system dialog says "Configure"
563 * we just use our main Activity */
564 Context context = getApplicationContext();
565 Intent intent = new Intent(context, MainActivity.class);
566 PendingIntent pending = PendingIntent.getActivity(context, 0, intent,
567 PendingIntent.FLAG_UPDATE_CURRENT);
568 builder.setConfigureIntent(pending);
569 return builder;
570 }
571
572 public synchronized boolean addAddress(String address, int prefixLength)
573 {
574 try
575 {
576 mCache.addAddress(address, prefixLength);
577 }
578 catch (IllegalArgumentException ex)
579 {
580 return false;
581 }
582 return true;
583 }
584
585 public synchronized boolean addDnsServer(String address)
586 {
587 try
588 {
589 mBuilder.addDnsServer(address);
590 mCache.recordAddressFamily(address);
591 }
592 catch (IllegalArgumentException ex)
593 {
594 return false;
595 }
596 return true;
597 }
598
599 public synchronized boolean addRoute(String address, int prefixLength)
600 {
601 try
602 {
603 mCache.addRoute(address, prefixLength);
604 }
605 catch (IllegalArgumentException ex)
606 {
607 return false;
608 }
609 return true;
610 }
611
612 public synchronized boolean addSearchDomain(String domain)
613 {
614 try
615 {
616 mBuilder.addSearchDomain(domain);
617 }
618 catch (IllegalArgumentException ex)
619 {
620 return false;
621 }
622 return true;
623 }
624
625 public synchronized boolean setMtu(int mtu)
626 {
627 try
628 {
629 mCache.setMtu(mtu);
630 }
631 catch (IllegalArgumentException ex)
632 {
633 return false;
634 }
635 return true;
636 }
637
638 public synchronized int establish()
639 {
640 ParcelFileDescriptor fd;
641 try
642 {
643 mCache.applyData(mBuilder);
644 fd = mBuilder.establish();
645 }
646 catch (Exception ex)
647 {
648 ex.printStackTrace();
649 return -1;
650 }
651 if (fd == null)
652 {
653 return -1;
654 }
655 /* now that the TUN device is created we don't need the current
656 * builder anymore, but we might need another when reestablishing */
657 mBuilder = createBuilder(mName);
658 mEstablishedCache = mCache;
659 mCache = new BuilderCache(mSplitTunneling);
660 return fd.detachFd();
661 }
662
663 public synchronized int establishNoDns()
664 {
665 ParcelFileDescriptor fd;
666
667 if (mEstablishedCache == null)
668 {
669 return -1;
670 }
671 try
672 {
673 Builder builder = createBuilder(mName);
674 mEstablishedCache.applyData(builder);
675 fd = builder.establish();
676 }
677 catch (Exception ex)
678 {
679 ex.printStackTrace();
680 return -1;
681 }
682 if (fd == null)
683 {
684 return -1;
685 }
686 return fd.detachFd();
687 }
688 }
689
690 /**
691 * Cache non DNS related information so we can recreate the builder without
692 * that information when reestablishing IKE_SAs
693 */
694 public class BuilderCache
695 {
696 private final List<PrefixedAddress> mAddresses = new ArrayList<PrefixedAddress>();
697 private final List<PrefixedAddress> mRoutesIPv4 = new ArrayList<PrefixedAddress>();
698 private final List<PrefixedAddress> mRoutesIPv6 = new ArrayList<PrefixedAddress>();
699 private final int mSplitTunneling;
700 private int mMtu;
701 private boolean mIPv4Seen, mIPv6Seen;
702
703 public BuilderCache(Integer splitTunneling)
704 {
705 mSplitTunneling = splitTunneling != null ? splitTunneling : 0;
706 }
707
708 public void addAddress(String address, int prefixLength)
709 {
710 mAddresses.add(new PrefixedAddress(address, prefixLength));
711 recordAddressFamily(address);
712 }
713
714 public void addRoute(String address, int prefixLength)
715 {
716 try
717 {
718 if (isIPv6(address))
719 {
720 mRoutesIPv6.add(new PrefixedAddress(address, prefixLength));
721 }
722 else
723 {
724 mRoutesIPv4.add(new PrefixedAddress(address, prefixLength));
725 }
726 }
727 catch (UnknownHostException ex)
728 {
729 ex.printStackTrace();
730 }
731 }
732
733 public void setMtu(int mtu)
734 {
735 mMtu = mtu;
736 }
737
738 public void recordAddressFamily(String address)
739 {
740 try
741 {
742 if (isIPv6(address))
743 {
744 mIPv6Seen = true;
745 }
746 else
747 {
748 mIPv4Seen = true;
749 }
750 }
751 catch (UnknownHostException ex)
752 {
753 ex.printStackTrace();
754 }
755 }
756
757 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
758 public void applyData(VpnService.Builder builder)
759 {
760 for (PrefixedAddress address : mAddresses)
761 {
762 builder.addAddress(address.mAddress, address.mPrefix);
763 }
764 /* add routes depending on whether split tunneling is allowed or not,
765 * that is, whether we have to handle and block non-VPN traffic */
766 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0)
767 {
768 if (mIPv4Seen)
769 { /* split tunneling is used depending on the routes */
770 for (PrefixedAddress route : mRoutesIPv4)
771 {
772 builder.addRoute(route.mAddress, route.mPrefix);
773 }
774 }
775 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
776 { /* allow traffic that would otherwise be blocked to bypass the VPN */
777 builder.allowFamily(OsConstants.AF_INET);
778 }
779 }
780 else if (mIPv4Seen)
781 { /* only needed if we've seen any addresses. otherwise, traffic
782 * is blocked by default (we also install no routes in that case) */
783 builder.addRoute("0.0.0.0", 0);
784 }
785 /* same thing for IPv6 */
786 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0)
787 {
788 if (mIPv6Seen)
789 {
790 for (PrefixedAddress route : mRoutesIPv6)
791 {
792 builder.addRoute(route.mAddress, route.mPrefix);
793 }
794 }
795 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
796 {
797 builder.allowFamily(OsConstants.AF_INET6);
798 }
799 }
800 else if (mIPv6Seen)
801 {
802 builder.addRoute("::", 0);
803 }
804 builder.setMtu(mMtu);
805 }
806
807 private boolean isIPv6(String address) throws UnknownHostException
808 {
809 InetAddress addr = InetAddress.getByName(address);
810 if (addr instanceof Inet4Address)
811 {
812 return false;
813 }
814 else if (addr instanceof Inet6Address)
815 {
816 return true;
817 }
818 return false;
819 }
820
821 private class PrefixedAddress
822 {
823 public String mAddress;
824 public int mPrefix;
825
826 public PrefixedAddress(String address, int prefix)
827 {
828 this.mAddress = address;
829 this.mPrefix = prefix;
830 }
831 }
832 }
833
834 /*
835 * The libraries are extracted to /data/data/org.strongswan.android/...
836 * during installation. On newer releases most are loaded in JNI_OnLoad.
837 */
838 static
839 {
840 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
841 {
842 System.loadLibrary("strongswan");
843
844 if (MainActivity.USE_BYOD)
845 {
846 System.loadLibrary("tncif");
847 System.loadLibrary("tnccs");
848 System.loadLibrary("imcv");
849 }
850
851 System.loadLibrary("charon");
852 System.loadLibrary("ipsec");
853 }
854 System.loadLibrary("androidbridge");
855 }
856 }