android: Pass local and remote identities as settings of a connection
[strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / logic / CharonVpnService.java
1 /*
2 * Copyright (C) 2012-2016 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * HSR 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 writer.setValue("connection.local_id", mCurrentProfile.getLocalId());
236 writer.setValue("connection.remote_id", mCurrentProfile.getRemoteId());
237 initiate(writer.serialize());
238 }
239 else
240 {
241 Log.e(TAG, "failed to start charon");
242 setError(ErrorState.GENERIC_ERROR);
243 setState(State.DISABLED);
244 mCurrentProfile = null;
245 }
246 }
247 }
248 catch (InterruptedException ex)
249 {
250 stopCurrentConnection();
251 setState(State.DISABLED);
252 }
253 }
254 }
255 }
256
257 /**
258 * Stop any existing connection by deinitializing charon.
259 */
260 private void stopCurrentConnection()
261 {
262 synchronized (this)
263 {
264 if (mCurrentProfile != null)
265 {
266 setState(State.DISCONNECTING);
267 mIsDisconnecting = true;
268 deinitializeCharon();
269 Log.i(TAG, "charon stopped");
270 mCurrentProfile = null;
271 }
272 }
273 }
274
275 /**
276 * Notify the state service about a new connection attempt.
277 * Called by the handler thread.
278 *
279 * @param profile currently active VPN profile
280 */
281 private void startConnection(VpnProfile profile)
282 {
283 synchronized (mServiceLock)
284 {
285 if (mService != null)
286 {
287 mService.startConnection(profile);
288 }
289 }
290 }
291
292 /**
293 * Update the current VPN state on the state service. Called by the handler
294 * thread and any of charon's threads.
295 *
296 * @param state current state
297 */
298 private void setState(State state)
299 {
300 synchronized (mServiceLock)
301 {
302 if (mService != null)
303 {
304 mService.setState(state);
305 }
306 }
307 }
308
309 /**
310 * Set an error on the state service. Called by the handler thread and any
311 * of charon's threads.
312 *
313 * @param error error state
314 */
315 private void setError(ErrorState error)
316 {
317 synchronized (mServiceLock)
318 {
319 if (mService != null)
320 {
321 mService.setError(error);
322 }
323 }
324 }
325
326 /**
327 * Set the IMC state on the state service. Called by the handler thread and
328 * any of charon's threads.
329 *
330 * @param state IMC state
331 */
332 private void setImcState(ImcState state)
333 {
334 synchronized (mServiceLock)
335 {
336 if (mService != null)
337 {
338 mService.setImcState(state);
339 }
340 }
341 }
342
343 /**
344 * Set an error on the state service. Called by the handler thread and any
345 * of charon's threads.
346 *
347 * @param error error state
348 */
349 private void setErrorDisconnect(ErrorState error)
350 {
351 synchronized (mServiceLock)
352 {
353 if (mService != null)
354 {
355 if (!mIsDisconnecting)
356 {
357 mService.setError(error);
358 }
359 }
360 }
361 }
362
363 /**
364 * Updates the state of the current connection.
365 * Called via JNI by different threads (but not concurrently).
366 *
367 * @param status new state
368 */
369 public void updateStatus(int status)
370 {
371 switch (status)
372 {
373 case STATE_CHILD_SA_DOWN:
374 if (!mIsDisconnecting)
375 {
376 setState(State.CONNECTING);
377 }
378 break;
379 case STATE_CHILD_SA_UP:
380 setState(State.CONNECTED);
381 break;
382 case STATE_AUTH_ERROR:
383 setErrorDisconnect(ErrorState.AUTH_FAILED);
384 break;
385 case STATE_PEER_AUTH_ERROR:
386 setErrorDisconnect(ErrorState.PEER_AUTH_FAILED);
387 break;
388 case STATE_LOOKUP_ERROR:
389 setErrorDisconnect(ErrorState.LOOKUP_FAILED);
390 break;
391 case STATE_UNREACHABLE_ERROR:
392 setErrorDisconnect(ErrorState.UNREACHABLE);
393 break;
394 case STATE_GENERIC_ERROR:
395 setErrorDisconnect(ErrorState.GENERIC_ERROR);
396 break;
397 default:
398 Log.e(TAG, "Unknown status code received");
399 break;
400 }
401 }
402
403 /**
404 * Updates the IMC state of the current connection.
405 * Called via JNI by different threads (but not concurrently).
406 *
407 * @param value new state
408 */
409 public void updateImcState(int value)
410 {
411 ImcState state = ImcState.fromValue(value);
412 if (state != null)
413 {
414 setImcState(state);
415 }
416 }
417
418 /**
419 * Add a remediation instruction to the VPN state service.
420 * Called via JNI by different threads (but not concurrently).
421 *
422 * @param xml XML text
423 */
424 public void addRemediationInstruction(String xml)
425 {
426 for (RemediationInstruction instruction : RemediationInstruction.fromXml(xml))
427 {
428 synchronized (mServiceLock)
429 {
430 if (mService != null)
431 {
432 mService.addRemediationInstruction(instruction);
433 }
434 }
435 }
436 }
437
438 /**
439 * Function called via JNI to generate a list of DER encoded CA certificates
440 * as byte array.
441 *
442 * @return a list of DER encoded CA certificates
443 */
444 private byte[][] getTrustedCertificates()
445 {
446 ArrayList<byte[]> certs = new ArrayList<byte[]>();
447 TrustedCertificateManager certman = TrustedCertificateManager.getInstance();
448 try
449 {
450 String alias = this.mCurrentCertificateAlias;
451 if (alias != null)
452 {
453 X509Certificate cert = certman.getCACertificateFromAlias(alias);
454 if (cert == null)
455 {
456 return null;
457 }
458 certs.add(cert.getEncoded());
459 }
460 else
461 {
462 for (X509Certificate cert : certman.getAllCACertificates().values())
463 {
464 certs.add(cert.getEncoded());
465 }
466 }
467 }
468 catch (CertificateEncodingException e)
469 {
470 e.printStackTrace();
471 return null;
472 }
473 return certs.toArray(new byte[certs.size()][]);
474 }
475
476 /**
477 * Function called via JNI to get a list containing the DER encoded certificates
478 * of the user selected certificate chain (beginning with the user certificate).
479 *
480 * Since this method is called from a thread of charon's thread pool we are safe
481 * to call methods on KeyChain directly.
482 *
483 * @return list containing the certificates (first element is the user certificate)
484 * @throws InterruptedException
485 * @throws KeyChainException
486 * @throws CertificateEncodingException
487 */
488 private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException
489 {
490 ArrayList<byte[]> encodings = new ArrayList<byte[]>();
491 X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias);
492 if (chain == null || chain.length == 0)
493 {
494 return null;
495 }
496 for (X509Certificate cert : chain)
497 {
498 encodings.add(cert.getEncoded());
499 }
500 return encodings.toArray(new byte[encodings.size()][]);
501 }
502
503 /**
504 * Function called via JNI to get the private key the user selected.
505 *
506 * Since this method is called from a thread of charon's thread pool we are safe
507 * to call methods on KeyChain directly.
508 *
509 * @return the private key
510 * @throws InterruptedException
511 * @throws KeyChainException
512 * @throws CertificateEncodingException
513 */
514 private PrivateKey getUserKey() throws KeyChainException, InterruptedException
515 {
516 return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias);
517 }
518
519 /**
520 * Initialization of charon, provided by libandroidbridge.so
521 *
522 * @param builder BuilderAdapter for this connection
523 * @param logfile absolute path to the logfile
524 * @param boyd enable BYOD features
525 * @return TRUE if initialization was successful
526 */
527 public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod);
528
529 /**
530 * Deinitialize charon, provided by libandroidbridge.so
531 */
532 public native void deinitializeCharon();
533
534 /**
535 * Initiate VPN, provided by libandroidbridge.so
536 */
537 public native void initiate(String config);
538
539 /**
540 * Adapter for VpnService.Builder which is used to access it safely via JNI.
541 * There is a corresponding C object to access it from native code.
542 */
543 public class BuilderAdapter
544 {
545 private final String mName;
546 private final Integer mSplitTunneling;
547 private VpnService.Builder mBuilder;
548 private BuilderCache mCache;
549 private BuilderCache mEstablishedCache;
550
551 public BuilderAdapter(String name, Integer splitTunneling)
552 {
553 mName = name;
554 mSplitTunneling = splitTunneling;
555 mBuilder = createBuilder(name);
556 mCache = new BuilderCache(mSplitTunneling);
557 }
558
559 private VpnService.Builder createBuilder(String name)
560 {
561 VpnService.Builder builder = new CharonVpnService.Builder();
562 builder.setSession(mName);
563
564 /* even though the option displayed in the system dialog says "Configure"
565 * we just use our main Activity */
566 Context context = getApplicationContext();
567 Intent intent = new Intent(context, MainActivity.class);
568 PendingIntent pending = PendingIntent.getActivity(context, 0, intent,
569 PendingIntent.FLAG_UPDATE_CURRENT);
570 builder.setConfigureIntent(pending);
571 return builder;
572 }
573
574 public synchronized boolean addAddress(String address, int prefixLength)
575 {
576 try
577 {
578 mCache.addAddress(address, prefixLength);
579 }
580 catch (IllegalArgumentException ex)
581 {
582 return false;
583 }
584 return true;
585 }
586
587 public synchronized boolean addDnsServer(String address)
588 {
589 try
590 {
591 mBuilder.addDnsServer(address);
592 mCache.recordAddressFamily(address);
593 }
594 catch (IllegalArgumentException ex)
595 {
596 return false;
597 }
598 return true;
599 }
600
601 public synchronized boolean addRoute(String address, int prefixLength)
602 {
603 try
604 {
605 mCache.addRoute(address, prefixLength);
606 }
607 catch (IllegalArgumentException ex)
608 {
609 return false;
610 }
611 return true;
612 }
613
614 public synchronized boolean addSearchDomain(String domain)
615 {
616 try
617 {
618 mBuilder.addSearchDomain(domain);
619 }
620 catch (IllegalArgumentException ex)
621 {
622 return false;
623 }
624 return true;
625 }
626
627 public synchronized boolean setMtu(int mtu)
628 {
629 try
630 {
631 mCache.setMtu(mtu);
632 }
633 catch (IllegalArgumentException ex)
634 {
635 return false;
636 }
637 return true;
638 }
639
640 public synchronized int establish()
641 {
642 ParcelFileDescriptor fd;
643 try
644 {
645 mCache.applyData(mBuilder);
646 fd = mBuilder.establish();
647 }
648 catch (Exception ex)
649 {
650 ex.printStackTrace();
651 return -1;
652 }
653 if (fd == null)
654 {
655 return -1;
656 }
657 /* now that the TUN device is created we don't need the current
658 * builder anymore, but we might need another when reestablishing */
659 mBuilder = createBuilder(mName);
660 mEstablishedCache = mCache;
661 mCache = new BuilderCache(mSplitTunneling);
662 return fd.detachFd();
663 }
664
665 public synchronized int establishNoDns()
666 {
667 ParcelFileDescriptor fd;
668
669 if (mEstablishedCache == null)
670 {
671 return -1;
672 }
673 try
674 {
675 Builder builder = createBuilder(mName);
676 mEstablishedCache.applyData(builder);
677 fd = builder.establish();
678 }
679 catch (Exception ex)
680 {
681 ex.printStackTrace();
682 return -1;
683 }
684 if (fd == null)
685 {
686 return -1;
687 }
688 return fd.detachFd();
689 }
690 }
691
692 /**
693 * Cache non DNS related information so we can recreate the builder without
694 * that information when reestablishing IKE_SAs
695 */
696 public class BuilderCache
697 {
698 private final List<PrefixedAddress> mAddresses = new ArrayList<PrefixedAddress>();
699 private final List<PrefixedAddress> mRoutesIPv4 = new ArrayList<PrefixedAddress>();
700 private final List<PrefixedAddress> mRoutesIPv6 = new ArrayList<PrefixedAddress>();
701 private final int mSplitTunneling;
702 private int mMtu;
703 private boolean mIPv4Seen, mIPv6Seen;
704
705 public BuilderCache(Integer splitTunneling)
706 {
707 mSplitTunneling = splitTunneling != null ? splitTunneling : 0;
708 }
709
710 public void addAddress(String address, int prefixLength)
711 {
712 mAddresses.add(new PrefixedAddress(address, prefixLength));
713 recordAddressFamily(address);
714 }
715
716 public void addRoute(String address, int prefixLength)
717 {
718 try
719 {
720 if (isIPv6(address))
721 {
722 mRoutesIPv6.add(new PrefixedAddress(address, prefixLength));
723 }
724 else
725 {
726 mRoutesIPv4.add(new PrefixedAddress(address, prefixLength));
727 }
728 }
729 catch (UnknownHostException ex)
730 {
731 ex.printStackTrace();
732 }
733 }
734
735 public void setMtu(int mtu)
736 {
737 mMtu = mtu;
738 }
739
740 public void recordAddressFamily(String address)
741 {
742 try
743 {
744 if (isIPv6(address))
745 {
746 mIPv6Seen = true;
747 }
748 else
749 {
750 mIPv4Seen = true;
751 }
752 }
753 catch (UnknownHostException ex)
754 {
755 ex.printStackTrace();
756 }
757 }
758
759 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
760 public void applyData(VpnService.Builder builder)
761 {
762 for (PrefixedAddress address : mAddresses)
763 {
764 builder.addAddress(address.mAddress, address.mPrefix);
765 }
766 /* add routes depending on whether split tunneling is allowed or not,
767 * that is, whether we have to handle and block non-VPN traffic */
768 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0)
769 {
770 if (mIPv4Seen)
771 { /* split tunneling is used depending on the routes */
772 for (PrefixedAddress route : mRoutesIPv4)
773 {
774 builder.addRoute(route.mAddress, route.mPrefix);
775 }
776 }
777 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
778 { /* allow traffic that would otherwise be blocked to bypass the VPN */
779 builder.allowFamily(OsConstants.AF_INET);
780 }
781 }
782 else if (mIPv4Seen)
783 { /* only needed if we've seen any addresses. otherwise, traffic
784 * is blocked by default (we also install no routes in that case) */
785 builder.addRoute("0.0.0.0", 0);
786 }
787 /* same thing for IPv6 */
788 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0)
789 {
790 if (mIPv6Seen)
791 {
792 for (PrefixedAddress route : mRoutesIPv6)
793 {
794 builder.addRoute(route.mAddress, route.mPrefix);
795 }
796 }
797 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
798 {
799 builder.allowFamily(OsConstants.AF_INET6);
800 }
801 }
802 else if (mIPv6Seen)
803 {
804 builder.addRoute("::", 0);
805 }
806 builder.setMtu(mMtu);
807 }
808
809 private boolean isIPv6(String address) throws UnknownHostException
810 {
811 InetAddress addr = InetAddress.getByName(address);
812 if (addr instanceof Inet4Address)
813 {
814 return false;
815 }
816 else if (addr instanceof Inet6Address)
817 {
818 return true;
819 }
820 return false;
821 }
822
823 private class PrefixedAddress
824 {
825 public String mAddress;
826 public int mPrefix;
827
828 public PrefixedAddress(String address, int prefix)
829 {
830 this.mAddress = address;
831 this.mPrefix = prefix;
832 }
833 }
834 }
835
836 /*
837 * The libraries are extracted to /data/data/org.strongswan.android/...
838 * during installation. On newer releases most are loaded in JNI_OnLoad.
839 */
840 static
841 {
842 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
843 {
844 System.loadLibrary("strongswan");
845
846 if (MainActivity.USE_BYOD)
847 {
848 System.loadLibrary("tncif");
849 System.loadLibrary("tnccs");
850 System.loadLibrary("imcv");
851 }
852
853 System.loadLibrary("charon");
854 System.loadLibrary("ipsec");
855 }
856 System.loadLibrary("androidbridge");
857 }
858 }