]> git.ipfire.org Git - thirdparty/strongswan.git/blob - src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
2f402de5dbcc551b6972e60fc84c6d5ef47b8fec
[thirdparty/strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / logic / CharonVpnService.java
1 /*
2 * Copyright (C) 2012-2018 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 android.annotation.TargetApi;
21 import android.app.Notification;
22 import android.app.NotificationChannel;
23 import android.app.NotificationManager;
24 import android.app.PendingIntent;
25 import android.app.Service;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.ServiceConnection;
30 import android.content.SharedPreferences;
31 import android.content.pm.PackageManager;
32 import android.net.VpnService;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.ParcelFileDescriptor;
38 import android.preference.PreferenceManager;
39 import android.security.KeyChain;
40 import android.security.KeyChainException;
41 import android.support.v4.app.NotificationCompat;
42 import android.support.v4.content.ContextCompat;
43 import android.system.OsConstants;
44 import android.util.Log;
45
46 import org.strongswan.android.R;
47 import org.strongswan.android.data.VpnProfile;
48 import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
49 import org.strongswan.android.data.VpnProfileDataSource;
50 import org.strongswan.android.data.VpnType.VpnTypeFeature;
51 import org.strongswan.android.logic.VpnStateService.ErrorState;
52 import org.strongswan.android.logic.VpnStateService.State;
53 import org.strongswan.android.logic.imc.ImcState;
54 import org.strongswan.android.logic.imc.RemediationInstruction;
55 import org.strongswan.android.ui.MainActivity;
56 import org.strongswan.android.ui.VpnProfileControlActivity;
57 import org.strongswan.android.utils.Constants;
58 import org.strongswan.android.utils.IPRange;
59 import org.strongswan.android.utils.IPRangeSet;
60 import org.strongswan.android.utils.SettingsWriter;
61
62 import java.io.File;
63 import java.io.FileInputStream;
64 import java.io.IOException;
65 import java.net.Inet4Address;
66 import java.net.Inet6Address;
67 import java.net.InetAddress;
68 import java.net.UnknownHostException;
69 import java.nio.ByteBuffer;
70 import java.nio.channels.ClosedByInterruptException;
71 import java.security.PrivateKey;
72 import java.security.cert.CertificateEncodingException;
73 import java.security.cert.X509Certificate;
74 import java.util.ArrayList;
75 import java.util.List;
76 import java.util.Locale;
77 import java.util.SortedSet;
78
79 public class CharonVpnService extends VpnService implements Runnable, VpnStateService.VpnStateListener
80 {
81 private static final String TAG = CharonVpnService.class.getSimpleName();
82 private static final String VPN_SERVICE_ACTION = "android.net.VpnService";
83 public static final String DISCONNECT_ACTION = "org.strongswan.android.CharonVpnService.DISCONNECT";
84 private static final String NOTIFICATION_CHANNEL = "org.strongswan.android.CharonVpnService.VPN_STATE_NOTIFICATION";
85 public static final String LOG_FILE = "charon.log";
86 public static final String KEY_IS_RETRY = "retry";
87 public static final int VPN_STATE_NOTIFICATION_ID = 1;
88
89 private String mLogFile;
90 private String mAppDir;
91 private VpnProfileDataSource mDataSource;
92 private Thread mConnectionHandler;
93 private VpnProfile mCurrentProfile;
94 private volatile String mCurrentCertificateAlias;
95 private volatile String mCurrentUserCertificateAlias;
96 private VpnProfile mNextProfile;
97 private volatile boolean mProfileUpdated;
98 private volatile boolean mTerminate;
99 private volatile boolean mIsDisconnecting;
100 private volatile boolean mShowNotification;
101 private BuilderAdapter mBuilderAdapter = new BuilderAdapter();
102 private Handler mHandler;
103 private VpnStateService mService;
104 private final Object mServiceLock = new Object();
105 private final ServiceConnection mServiceConnection = new ServiceConnection() {
106 @Override
107 public void onServiceDisconnected(ComponentName name)
108 { /* since the service is local this is theoretically only called when the process is terminated */
109 synchronized (mServiceLock)
110 {
111 mService = null;
112 }
113 }
114
115 @Override
116 public void onServiceConnected(ComponentName name, IBinder service)
117 {
118 synchronized (mServiceLock)
119 {
120 mService = ((VpnStateService.LocalBinder)service).getService();
121 }
122 /* we are now ready to start the handler thread */
123 mService.registerListener(CharonVpnService.this);
124 mConnectionHandler.start();
125 }
126 };
127
128 /**
129 * as defined in charonservice.h
130 */
131 static final int STATE_CHILD_SA_UP = 1;
132 static final int STATE_CHILD_SA_DOWN = 2;
133 static final int STATE_AUTH_ERROR = 3;
134 static final int STATE_PEER_AUTH_ERROR = 4;
135 static final int STATE_LOOKUP_ERROR = 5;
136 static final int STATE_UNREACHABLE_ERROR = 6;
137 static final int STATE_CERTIFICATE_UNAVAILABLE = 7;
138 static final int STATE_GENERIC_ERROR = 8;
139
140 @Override
141 public int onStartCommand(Intent intent, int flags, int startId)
142 {
143 if (intent != null)
144 {
145 VpnProfile profile = null;
146 boolean retry = false;
147
148 if (VPN_SERVICE_ACTION.equals(intent.getAction()))
149 { /* triggered when Always-on VPN is activated */
150 SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
151 String uuid = pref.getString(Constants.PREF_DEFAULT_VPN_PROFILE, null);
152 if (uuid == null || uuid.equals(Constants.PREF_DEFAULT_VPN_PROFILE_MRU))
153 {
154 uuid = pref.getString(Constants.PREF_MRU_VPN_PROFILE, null);
155 }
156 profile = mDataSource.getVpnProfile(uuid);
157 }
158 else if (!DISCONNECT_ACTION.equals(intent.getAction()))
159 {
160 Bundle bundle = intent.getExtras();
161 if (bundle != null)
162 {
163 profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID));
164 if (profile != null)
165 {
166 String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD);
167 profile.setPassword(password);
168
169 retry = bundle.getBoolean(CharonVpnService.KEY_IS_RETRY, false);
170
171 SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
172 pref.edit().putString(Constants.PREF_MRU_VPN_PROFILE, profile.getUUID().toString())
173 .apply();
174 }
175 }
176 }
177 if (profile != null && !retry)
178 { /* delete the log file if this is not an automatic retry */
179 deleteFile(LOG_FILE);
180 }
181 setNextProfile(profile);
182 }
183 return START_NOT_STICKY;
184 }
185
186 @Override
187 public void onCreate()
188 {
189 mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE;
190 mAppDir = getFilesDir().getAbsolutePath();
191
192 /* handler used to do changes in the main UI thread */
193 mHandler = new Handler();
194
195 mDataSource = new VpnProfileDataSource(this);
196 mDataSource.open();
197 /* use a separate thread as main thread for charon */
198 mConnectionHandler = new Thread(this);
199 /* the thread is started when the service is bound */
200 bindService(new Intent(this, VpnStateService.class),
201 mServiceConnection, Service.BIND_AUTO_CREATE);
202
203 createNotificationChannel();
204 }
205
206 @Override
207 public void onRevoke()
208 { /* the system revoked the rights grated with the initial prepare() call.
209 * called when the user clicks disconnect in the system's VPN dialog */
210 setNextProfile(null);
211 }
212
213 @Override
214 public void onDestroy()
215 {
216 mTerminate = true;
217 setNextProfile(null);
218 try
219 {
220 mConnectionHandler.join();
221 }
222 catch (InterruptedException e)
223 {
224 e.printStackTrace();
225 }
226 if (mService != null)
227 {
228 mService.unregisterListener(this);
229 unbindService(mServiceConnection);
230 }
231 mDataSource.close();
232 }
233
234 /**
235 * Set the profile that is to be initiated next. Notify the handler thread.
236 *
237 * @param profile the profile to initiate
238 */
239 private void setNextProfile(VpnProfile profile)
240 {
241 synchronized (this)
242 {
243 this.mNextProfile = profile;
244 mProfileUpdated = true;
245 notifyAll();
246 }
247 }
248
249 @Override
250 public void run()
251 {
252 while (true)
253 {
254 synchronized (this)
255 {
256 try
257 {
258 while (!mProfileUpdated)
259 {
260 wait();
261 }
262
263 mProfileUpdated = false;
264 stopCurrentConnection();
265 if (mNextProfile == null)
266 {
267 setState(State.DISABLED);
268 if (mTerminate)
269 {
270 break;
271 }
272 }
273 else
274 {
275 mCurrentProfile = mNextProfile;
276 mNextProfile = null;
277
278 /* store this in a separate (volatile) variable to avoid
279 * a possible deadlock during deinitialization */
280 mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias();
281 mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias();
282
283 startConnection(mCurrentProfile);
284 mIsDisconnecting = false;
285
286 addNotification();
287 mBuilderAdapter.setProfile(mCurrentProfile);
288 if (initializeCharon(mBuilderAdapter, mLogFile, mAppDir, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
289 {
290 Log.i(TAG, "charon started");
291
292 if (mCurrentProfile.getVpnType().has(VpnTypeFeature.USER_PASS) &&
293 mCurrentProfile.getPassword() == null)
294 { /* this can happen if Always-on VPN is enabled with an incomplete profile */
295 setError(ErrorState.PASSWORD_MISSING);
296 continue;
297 }
298
299 SettingsWriter writer = new SettingsWriter();
300 writer.setValue("global.language", Locale.getDefault().getLanguage());
301 writer.setValue("global.mtu", mCurrentProfile.getMTU());
302 writer.setValue("global.nat_keepalive", mCurrentProfile.getNATKeepAlive());
303 writer.setValue("global.crl", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_DISABLE_CRL) == 0);
304 writer.setValue("global.ocsp", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_DISABLE_OCSP) == 0);
305 writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier());
306 writer.setValue("connection.server", mCurrentProfile.getGateway());
307 writer.setValue("connection.port", mCurrentProfile.getPort());
308 writer.setValue("connection.username", mCurrentProfile.getUsername());
309 writer.setValue("connection.password", mCurrentProfile.getPassword());
310 writer.setValue("connection.local_id", mCurrentProfile.getLocalId());
311 writer.setValue("connection.remote_id", mCurrentProfile.getRemoteId());
312 writer.setValue("connection.certreq", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_SUPPRESS_CERT_REQS) == 0);
313 writer.setValue("connection.strict_revocation", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_STRICT_REVOCATION) != 0);
314 writer.setValue("connection.ike_proposal", mCurrentProfile.getIkeProposal());
315 writer.setValue("connection.esp_proposal", mCurrentProfile.getEspProposal());
316 initiate(writer.serialize());
317 }
318 else
319 {
320 Log.e(TAG, "failed to start charon");
321 setError(ErrorState.GENERIC_ERROR);
322 setState(State.DISABLED);
323 mCurrentProfile = null;
324 }
325 }
326 }
327 catch (InterruptedException ex)
328 {
329 stopCurrentConnection();
330 setState(State.DISABLED);
331 }
332 }
333 }
334 }
335
336 /**
337 * Stop any existing connection by deinitializing charon.
338 */
339 private void stopCurrentConnection()
340 {
341 synchronized (this)
342 {
343 if (mNextProfile != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
344 {
345 mBuilderAdapter.setProfile(mNextProfile);
346 mBuilderAdapter.establishBlocking();
347 }
348
349 if (mCurrentProfile != null)
350 {
351 setState(State.DISCONNECTING);
352 mIsDisconnecting = true;
353 deinitializeCharon();
354 Log.i(TAG, "charon stopped");
355 mCurrentProfile = null;
356 if (mNextProfile == null)
357 { /* only do this if we are not connecting to another profile */
358 removeNotification();
359 mBuilderAdapter.closeBlocking();
360 }
361 }
362 }
363 }
364
365 /**
366 * Add a permanent notification while we are connected to avoid the service getting killed by
367 * the system when low on memory.
368 */
369 private void addNotification()
370 {
371 mHandler.post(new Runnable()
372 {
373 @Override
374 public void run()
375 {
376 mShowNotification = true;
377 startForeground(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
378 }
379 });
380 }
381
382 /**
383 * Remove the permanent notification.
384 */
385 private void removeNotification()
386 {
387 mHandler.post(new Runnable()
388 {
389 @Override
390 public void run()
391 {
392 mShowNotification = false;
393 stopForeground(true);
394 }
395 });
396 }
397
398 /**
399 * Create a notification channel for Android 8+
400 */
401 private void createNotificationChannel()
402 {
403 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
404 {
405 NotificationChannel channel;
406 channel = new NotificationChannel(NOTIFICATION_CHANNEL, getString(R.string.permanent_notification_name),
407 NotificationManager.IMPORTANCE_LOW);
408 channel.setDescription(getString(R.string.permanent_notification_description));
409 channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
410 channel.setShowBadge(false);
411 NotificationManager notificationManager = getSystemService(NotificationManager.class);
412 notificationManager.createNotificationChannel(channel);
413 }
414 }
415
416
417 /**
418 * Build a notification matching the current state
419 */
420 private Notification buildNotification(boolean publicVersion)
421 {
422 VpnProfile profile = mService.getProfile();
423 State state = mService.getState();
424 ErrorState error = mService.getErrorState();
425 String name = "";
426 boolean add_action = false;
427
428 if (profile != null)
429 {
430 name = profile.getName();
431 }
432 NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
433 .setSmallIcon(R.drawable.ic_notification)
434 .setCategory(NotificationCompat.CATEGORY_SERVICE)
435 .setVisibility(publicVersion ? NotificationCompat.VISIBILITY_PUBLIC
436 : NotificationCompat.VISIBILITY_PRIVATE);
437 int s = R.string.state_disabled;
438 if (error != ErrorState.NO_ERROR)
439 {
440 s = mService.getErrorText();
441 builder.setSmallIcon(R.drawable.ic_notification_warning);
442 builder.setColor(ContextCompat.getColor(this, R.color.error_text));
443
444 if (!publicVersion && profile != null)
445 {
446 int retry = mService.getRetryIn();
447 if (retry > 0)
448 {
449 builder.setContentText(getResources().getQuantityString(R.plurals.retry_in, retry, retry));
450 builder.setProgress(mService.getRetryTimeout(), retry, false);
451 }
452
453 Intent intent = new Intent(getApplicationContext(), VpnProfileControlActivity.class);
454 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
455 intent.setAction(VpnProfileControlActivity.START_PROFILE);
456 intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
457 PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
458 PendingIntent.FLAG_UPDATE_CURRENT);
459 builder.addAction(R.drawable.ic_notification_connecting, getString(R.string.retry), pending);
460 add_action = true;
461 }
462 }
463 else
464 {
465 builder.setProgress(0, 0, false);
466
467 switch (state)
468 {
469 case CONNECTING:
470 s = R.string.state_connecting;
471 builder.setSmallIcon(R.drawable.ic_notification_connecting);
472 builder.setColor(ContextCompat.getColor(this, R.color.warning_text));
473 add_action = true;
474 break;
475 case CONNECTED:
476 s = R.string.state_connected;
477 builder.setColor(ContextCompat.getColor(this, R.color.success_text));
478 builder.setUsesChronometer(true);
479 add_action = true;
480 break;
481 case DISCONNECTING:
482 s = R.string.state_disconnecting;
483 break;
484 }
485 }
486 builder.setContentTitle(getString(s));
487 if (!publicVersion)
488 {
489 if (add_action)
490 {
491 Intent intent = new Intent(getApplicationContext(), VpnProfileControlActivity.class);
492 intent.setAction(VpnProfileControlActivity.DISCONNECT);
493 PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
494 PendingIntent.FLAG_UPDATE_CURRENT);
495 builder.addAction(R.drawable.ic_notification_disconnect, getString(R.string.disconnect), pending);
496 }
497 if (error == ErrorState.NO_ERROR)
498 {
499 builder.setContentText(name);
500 }
501 builder.setPublicVersion(buildNotification(true));
502 }
503
504 Intent intent = new Intent(getApplicationContext(), MainActivity.class);
505 PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
506 PendingIntent.FLAG_UPDATE_CURRENT);
507 builder.setContentIntent(pending);
508 return builder.build();
509 }
510
511 @Override
512 public void stateChanged() {
513 if (mShowNotification)
514 {
515 NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
516 manager.notify(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
517 }
518 }
519
520 /**
521 * Notify the state service about a new connection attempt.
522 * Called by the handler thread.
523 *
524 * @param profile currently active VPN profile
525 */
526 private void startConnection(VpnProfile profile)
527 {
528 synchronized (mServiceLock)
529 {
530 if (mService != null)
531 {
532 mService.startConnection(profile);
533 }
534 }
535 }
536
537 /**
538 * Update the current VPN state on the state service. Called by the handler
539 * thread and any of charon's threads.
540 *
541 * @param state current state
542 */
543 private void setState(State state)
544 {
545 synchronized (mServiceLock)
546 {
547 if (mService != null)
548 {
549 mService.setState(state);
550 }
551 }
552 }
553
554 /**
555 * Set an error on the state service. Called by the handler thread and any
556 * of charon's threads.
557 *
558 * @param error error state
559 */
560 private void setError(ErrorState error)
561 {
562 synchronized (mServiceLock)
563 {
564 if (mService != null)
565 {
566 mService.setError(error);
567 }
568 }
569 }
570
571 /**
572 * Set the IMC state on the state service. Called by the handler thread and
573 * any of charon's threads.
574 *
575 * @param state IMC state
576 */
577 private void setImcState(ImcState state)
578 {
579 synchronized (mServiceLock)
580 {
581 if (mService != null)
582 {
583 mService.setImcState(state);
584 }
585 }
586 }
587
588 /**
589 * Set an error on the state service. Called by the handler thread and any
590 * of charon's threads.
591 *
592 * @param error error state
593 */
594 private void setErrorDisconnect(ErrorState error)
595 {
596 synchronized (mServiceLock)
597 {
598 if (mService != null)
599 {
600 if (!mIsDisconnecting)
601 {
602 mService.setError(error);
603 }
604 }
605 }
606 }
607
608 /**
609 * Updates the state of the current connection.
610 * Called via JNI by different threads (but not concurrently).
611 *
612 * @param status new state
613 */
614 public void updateStatus(int status)
615 {
616 switch (status)
617 {
618 case STATE_CHILD_SA_DOWN:
619 if (!mIsDisconnecting)
620 {
621 setState(State.CONNECTING);
622 }
623 break;
624 case STATE_CHILD_SA_UP:
625 setState(State.CONNECTED);
626 break;
627 case STATE_AUTH_ERROR:
628 setErrorDisconnect(ErrorState.AUTH_FAILED);
629 break;
630 case STATE_PEER_AUTH_ERROR:
631 setErrorDisconnect(ErrorState.PEER_AUTH_FAILED);
632 break;
633 case STATE_LOOKUP_ERROR:
634 setErrorDisconnect(ErrorState.LOOKUP_FAILED);
635 break;
636 case STATE_UNREACHABLE_ERROR:
637 setErrorDisconnect(ErrorState.UNREACHABLE);
638 break;
639 case STATE_CERTIFICATE_UNAVAILABLE:
640 setErrorDisconnect(ErrorState.CERTIFICATE_UNAVAILABLE);
641 break;
642 case STATE_GENERIC_ERROR:
643 setErrorDisconnect(ErrorState.GENERIC_ERROR);
644 break;
645 default:
646 Log.e(TAG, "Unknown status code received");
647 break;
648 }
649 }
650
651 /**
652 * Updates the IMC state of the current connection.
653 * Called via JNI by different threads (but not concurrently).
654 *
655 * @param value new state
656 */
657 public void updateImcState(int value)
658 {
659 ImcState state = ImcState.fromValue(value);
660 if (state != null)
661 {
662 setImcState(state);
663 }
664 }
665
666 /**
667 * Add a remediation instruction to the VPN state service.
668 * Called via JNI by different threads (but not concurrently).
669 *
670 * @param xml XML text
671 */
672 public void addRemediationInstruction(String xml)
673 {
674 for (RemediationInstruction instruction : RemediationInstruction.fromXml(xml))
675 {
676 synchronized (mServiceLock)
677 {
678 if (mService != null)
679 {
680 mService.addRemediationInstruction(instruction);
681 }
682 }
683 }
684 }
685
686 /**
687 * Function called via JNI to generate a list of DER encoded CA certificates
688 * as byte array.
689 *
690 * @return a list of DER encoded CA certificates
691 */
692 private byte[][] getTrustedCertificates()
693 {
694 ArrayList<byte[]> certs = new ArrayList<byte[]>();
695 TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load();
696 try
697 {
698 String alias = this.mCurrentCertificateAlias;
699 if (alias != null)
700 {
701 X509Certificate cert = certman.getCACertificateFromAlias(alias);
702 if (cert == null)
703 {
704 return null;
705 }
706 certs.add(cert.getEncoded());
707 }
708 else
709 {
710 for (X509Certificate cert : certman.getAllCACertificates().values())
711 {
712 certs.add(cert.getEncoded());
713 }
714 }
715 }
716 catch (CertificateEncodingException e)
717 {
718 e.printStackTrace();
719 return null;
720 }
721 return certs.toArray(new byte[certs.size()][]);
722 }
723
724 /**
725 * Function called via JNI to get a list containing the DER encoded certificates
726 * of the user selected certificate chain (beginning with the user certificate).
727 *
728 * Since this method is called from a thread of charon's thread pool we are safe
729 * to call methods on KeyChain directly.
730 *
731 * @return list containing the certificates (first element is the user certificate)
732 * @throws InterruptedException
733 * @throws KeyChainException
734 * @throws CertificateEncodingException
735 */
736 private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException
737 {
738 ArrayList<byte[]> encodings = new ArrayList<byte[]>();
739 X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias);
740 if (chain == null || chain.length == 0)
741 {
742 return null;
743 }
744 for (X509Certificate cert : chain)
745 {
746 encodings.add(cert.getEncoded());
747 }
748 return encodings.toArray(new byte[encodings.size()][]);
749 }
750
751 /**
752 * Function called via JNI to get the private key the user selected.
753 *
754 * Since this method is called from a thread of charon's thread pool we are safe
755 * to call methods on KeyChain directly.
756 *
757 * @return the private key
758 * @throws InterruptedException
759 * @throws KeyChainException
760 */
761 private PrivateKey getUserKey() throws KeyChainException, InterruptedException
762 {
763 return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias);
764 }
765
766 /**
767 * Initialization of charon, provided by libandroidbridge.so
768 *
769 * @param builder BuilderAdapter for this connection
770 * @param logfile absolute path to the logfile
771 * @param appdir absolute path to the data directory of the app
772 * @param byod enable BYOD features
773 * @return TRUE if initialization was successful
774 */
775 public native boolean initializeCharon(BuilderAdapter builder, String logfile, String appdir, boolean byod);
776
777 /**
778 * Deinitialize charon, provided by libandroidbridge.so
779 */
780 public native void deinitializeCharon();
781
782 /**
783 * Initiate VPN, provided by libandroidbridge.so
784 */
785 public native void initiate(String config);
786
787 /**
788 * Adapter for VpnService.Builder which is used to access it safely via JNI.
789 * There is a corresponding C object to access it from native code.
790 */
791 public class BuilderAdapter
792 {
793 private VpnProfile mProfile;
794 private VpnService.Builder mBuilder;
795 private BuilderCache mCache;
796 private BuilderCache mEstablishedCache;
797 private PacketDropper mDropper = new PacketDropper();
798
799 public synchronized void setProfile(VpnProfile profile)
800 {
801 mProfile = profile;
802 mBuilder = createBuilder(mProfile.getName());
803 mCache = new BuilderCache(mProfile);
804 }
805
806 private VpnService.Builder createBuilder(String name)
807 {
808 VpnService.Builder builder = new CharonVpnService.Builder();
809 builder.setSession(name);
810
811 /* even though the option displayed in the system dialog says "Configure"
812 * we just use our main Activity */
813 Context context = getApplicationContext();
814 Intent intent = new Intent(context, MainActivity.class);
815 PendingIntent pending = PendingIntent.getActivity(context, 0, intent,
816 PendingIntent.FLAG_UPDATE_CURRENT);
817 builder.setConfigureIntent(pending);
818 return builder;
819 }
820
821 public synchronized boolean addAddress(String address, int prefixLength)
822 {
823 try
824 {
825 mCache.addAddress(address, prefixLength);
826 }
827 catch (IllegalArgumentException ex)
828 {
829 return false;
830 }
831 return true;
832 }
833
834 public synchronized boolean addDnsServer(String address)
835 {
836 try
837 {
838 mBuilder.addDnsServer(address);
839 mCache.recordAddressFamily(address);
840 }
841 catch (IllegalArgumentException ex)
842 {
843 return false;
844 }
845 return true;
846 }
847
848 public synchronized boolean addRoute(String address, int prefixLength)
849 {
850 try
851 {
852 mCache.addRoute(address, prefixLength);
853 }
854 catch (IllegalArgumentException ex)
855 {
856 return false;
857 }
858 return true;
859 }
860
861 public synchronized boolean addSearchDomain(String domain)
862 {
863 try
864 {
865 mBuilder.addSearchDomain(domain);
866 }
867 catch (IllegalArgumentException ex)
868 {
869 return false;
870 }
871 return true;
872 }
873
874 public synchronized boolean setMtu(int mtu)
875 {
876 try
877 {
878 mCache.setMtu(mtu);
879 }
880 catch (IllegalArgumentException ex)
881 {
882 return false;
883 }
884 return true;
885 }
886
887 private synchronized ParcelFileDescriptor establishIntern()
888 {
889 ParcelFileDescriptor fd;
890 try
891 {
892 mCache.applyData(mBuilder);
893 fd = mBuilder.establish();
894 if (fd != null)
895 {
896 closeBlocking();
897 }
898 }
899 catch (Exception ex)
900 {
901 ex.printStackTrace();
902 return null;
903 }
904 if (fd == null)
905 {
906 return null;
907 }
908 /* now that the TUN device is created we don't need the current
909 * builder anymore, but we might need another when reestablishing */
910 mBuilder = createBuilder(mProfile.getName());
911 mEstablishedCache = mCache;
912 mCache = new BuilderCache(mProfile);
913 return fd;
914 }
915
916 public synchronized int establish()
917 {
918 ParcelFileDescriptor fd = establishIntern();
919 return fd != null ? fd.detachFd() : -1;
920 }
921
922 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
923 public synchronized void establishBlocking()
924 {
925 /* just choose some arbitrary values to block all traffic (except for what's configured in the profile) */
926 mCache.addAddress("172.16.252.1", 32);
927 mCache.addAddress("fd00::fd02:1", 128);
928 mCache.addRoute("0.0.0.0", 0);
929 mCache.addRoute("::", 0);
930 /* use blocking mode to simplify packet dropping */
931 mBuilder.setBlocking(true);
932 ParcelFileDescriptor fd = establishIntern();
933 if (fd != null)
934 {
935 mDropper.start(fd);
936 }
937 }
938
939 public synchronized void closeBlocking()
940 {
941 mDropper.stop();
942 }
943
944 public synchronized int establishNoDns()
945 {
946 ParcelFileDescriptor fd;
947
948 if (mEstablishedCache == null)
949 {
950 return -1;
951 }
952 try
953 {
954 Builder builder = createBuilder(mProfile.getName());
955 mEstablishedCache.applyData(builder);
956 fd = builder.establish();
957 }
958 catch (Exception ex)
959 {
960 ex.printStackTrace();
961 return -1;
962 }
963 if (fd == null)
964 {
965 return -1;
966 }
967 return fd.detachFd();
968 }
969
970 private class PacketDropper implements Runnable
971 {
972 private ParcelFileDescriptor mFd;
973 private Thread mThread;
974
975 public void start(ParcelFileDescriptor fd)
976 {
977 mFd = fd;
978 mThread = new Thread(this);
979 mThread.start();
980 }
981
982 public void stop()
983 {
984 if (mFd != null)
985 {
986 try
987 {
988 mThread.interrupt();
989 mThread.join();
990 mFd.close();
991 }
992 catch (InterruptedException e)
993 {
994 e.printStackTrace();
995 }
996 catch (IOException e)
997 {
998 e.printStackTrace();
999 }
1000 mFd = null;
1001 }
1002 }
1003
1004 @Override
1005 public synchronized void run()
1006 {
1007 try
1008 {
1009 FileInputStream plain = new FileInputStream(mFd.getFileDescriptor());
1010 ByteBuffer packet = ByteBuffer.allocate(mCache.mMtu);
1011 while (true)
1012 { /* just read and ignore all data, regular read() is not properly interruptible */
1013 int len = plain.getChannel().read(packet);
1014 packet.clear();
1015 if (len < 0)
1016 {
1017 break;
1018 }
1019 }
1020 }
1021 catch (ClosedByInterruptException e)
1022 {
1023 /* regular interruption */
1024 }
1025 catch (Exception e)
1026 {
1027 e.printStackTrace();
1028 }
1029 }
1030 }
1031 }
1032
1033 /**
1034 * Cache non DNS related information so we can recreate the builder without
1035 * that information when reestablishing IKE_SAs
1036 */
1037 public class BuilderCache
1038 {
1039 private final List<IPRange> mAddresses = new ArrayList<>();
1040 private final List<IPRange> mRoutesIPv4 = new ArrayList<>();
1041 private final List<IPRange> mRoutesIPv6 = new ArrayList<>();
1042 private final IPRangeSet mIncludedSubnetsv4 = new IPRangeSet();
1043 private final IPRangeSet mIncludedSubnetsv6 = new IPRangeSet();
1044 private final IPRangeSet mExcludedSubnets;
1045 private final int mSplitTunneling;
1046 private final SelectedAppsHandling mAppHandling;
1047 private final SortedSet<String> mSelectedApps;
1048 private int mMtu;
1049 private boolean mIPv4Seen, mIPv6Seen;
1050
1051 public BuilderCache(VpnProfile profile)
1052 {
1053 IPRangeSet included = IPRangeSet.fromString(profile.getIncludedSubnets());
1054 for (IPRange range : included)
1055 {
1056 if (range.getFrom() instanceof Inet4Address)
1057 {
1058 mIncludedSubnetsv4.add(range);
1059 }
1060 else if (range.getFrom() instanceof Inet6Address)
1061 {
1062 mIncludedSubnetsv6.add(range);
1063 }
1064 }
1065 mExcludedSubnets = IPRangeSet.fromString(profile.getExcludedSubnets());
1066 Integer splitTunneling = profile.getSplitTunneling();
1067 mSplitTunneling = splitTunneling != null ? splitTunneling : 0;
1068 SelectedAppsHandling appHandling = profile.getSelectedAppsHandling();
1069 mSelectedApps = profile.getSelectedAppsSet();
1070 /* exclude our own app, otherwise the fetcher is blocked */
1071 switch (appHandling)
1072 {
1073 case SELECTED_APPS_DISABLE:
1074 appHandling = SelectedAppsHandling.SELECTED_APPS_EXCLUDE;
1075 mSelectedApps.clear();
1076 /* fall-through */
1077 case SELECTED_APPS_EXCLUDE:
1078 mSelectedApps.add(getPackageName());
1079 break;
1080 case SELECTED_APPS_ONLY:
1081 mSelectedApps.remove(getPackageName());
1082 break;
1083 }
1084 mAppHandling = appHandling;
1085
1086 /* set a default MTU, will be set by the daemon for regular interfaces */
1087 Integer mtu = profile.getMTU();
1088 mMtu = mtu == null ? Constants.MTU_MAX : mtu;
1089 }
1090
1091 public void addAddress(String address, int prefixLength)
1092 {
1093 try
1094 {
1095 mAddresses.add(new IPRange(address, prefixLength));
1096 recordAddressFamily(address);
1097 }
1098 catch (UnknownHostException ex)
1099 {
1100 ex.printStackTrace();
1101 }
1102 }
1103
1104 public void addRoute(String address, int prefixLength)
1105 {
1106 try
1107 {
1108 if (isIPv6(address))
1109 {
1110 mRoutesIPv6.add(new IPRange(address, prefixLength));
1111 }
1112 else
1113 {
1114 mRoutesIPv4.add(new IPRange(address, prefixLength));
1115 }
1116 }
1117 catch (UnknownHostException ex)
1118 {
1119 ex.printStackTrace();
1120 }
1121 }
1122
1123 public void setMtu(int mtu)
1124 {
1125 mMtu = mtu;
1126 }
1127
1128 public void recordAddressFamily(String address)
1129 {
1130 try
1131 {
1132 if (isIPv6(address))
1133 {
1134 mIPv6Seen = true;
1135 }
1136 else
1137 {
1138 mIPv4Seen = true;
1139 }
1140 }
1141 catch (UnknownHostException ex)
1142 {
1143 ex.printStackTrace();
1144 }
1145 }
1146
1147 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1148 public void applyData(VpnService.Builder builder)
1149 {
1150 for (IPRange address : mAddresses)
1151 {
1152 builder.addAddress(address.getFrom(), address.getPrefix());
1153 }
1154 /* add routes depending on whether split tunneling is allowed or not,
1155 * that is, whether we have to handle and block non-VPN traffic */
1156 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0)
1157 {
1158 if (mIPv4Seen)
1159 { /* split tunneling is used depending on the routes and configuration */
1160 IPRangeSet ranges = new IPRangeSet();
1161 if (mIncludedSubnetsv4.size() > 0)
1162 {
1163 ranges.add(mIncludedSubnetsv4);
1164 }
1165 else
1166 {
1167 ranges.addAll(mRoutesIPv4);
1168 }
1169 ranges.remove(mExcludedSubnets);
1170 for (IPRange subnet : ranges.subnets())
1171 {
1172 try
1173 {
1174 builder.addRoute(subnet.getFrom(), subnet.getPrefix());
1175 }
1176 catch (IllegalArgumentException e)
1177 { /* some Android versions don't seem to like multicast addresses here,
1178 * ignore it for now */
1179 if (!subnet.getFrom().isMulticastAddress())
1180 {
1181 throw e;
1182 }
1183 }
1184 }
1185 }
1186 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
1187 { /* allow traffic that would otherwise be blocked to bypass the VPN */
1188 builder.allowFamily(OsConstants.AF_INET);
1189 }
1190 }
1191 else if (mIPv4Seen)
1192 { /* only needed if we've seen any addresses. otherwise, traffic
1193 * is blocked by default (we also install no routes in that case) */
1194 builder.addRoute("0.0.0.0", 0);
1195 }
1196 /* same thing for IPv6 */
1197 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0)
1198 {
1199 if (mIPv6Seen)
1200 {
1201 IPRangeSet ranges = new IPRangeSet();
1202 if (mIncludedSubnetsv6.size() > 0)
1203 {
1204 ranges.add(mIncludedSubnetsv6);
1205 }
1206 else
1207 {
1208 ranges.addAll(mRoutesIPv6);
1209 }
1210 ranges.remove(mExcludedSubnets);
1211 for (IPRange subnet : ranges.subnets())
1212 {
1213 try
1214 {
1215 builder.addRoute(subnet.getFrom(), subnet.getPrefix());
1216 }
1217 catch (IllegalArgumentException e)
1218 {
1219 if (!subnet.getFrom().isMulticastAddress())
1220 {
1221 throw e;
1222 }
1223 }
1224 }
1225 }
1226 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
1227 {
1228 builder.allowFamily(OsConstants.AF_INET6);
1229 }
1230 }
1231 else if (mIPv6Seen)
1232 {
1233 builder.addRoute("::", 0);
1234 }
1235 /* apply selected applications */
1236 if (mSelectedApps.size() > 0 &&
1237 Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
1238 {
1239 switch (mAppHandling)
1240 {
1241 case SELECTED_APPS_EXCLUDE:
1242 for (String app : mSelectedApps)
1243 {
1244 try
1245 {
1246 builder.addDisallowedApplication(app);
1247 }
1248 catch (PackageManager.NameNotFoundException e)
1249 {
1250 // possible if not configured via GUI or app was uninstalled
1251 }
1252 }
1253 break;
1254 case SELECTED_APPS_ONLY:
1255 for (String app : mSelectedApps)
1256 {
1257 try
1258 {
1259 builder.addAllowedApplication(app);
1260 }
1261 catch (PackageManager.NameNotFoundException e)
1262 {
1263 // possible if not configured via GUI or app was uninstalled
1264 }
1265 }
1266 break;
1267 default:
1268 break;
1269 }
1270 }
1271 builder.setMtu(mMtu);
1272 }
1273
1274 private boolean isIPv6(String address) throws UnknownHostException
1275 {
1276 InetAddress addr = InetAddress.getByName(address);
1277 if (addr instanceof Inet4Address)
1278 {
1279 return false;
1280 }
1281 else if (addr instanceof Inet6Address)
1282 {
1283 return true;
1284 }
1285 return false;
1286 }
1287 }
1288
1289 /**
1290 * Function called via JNI to determine information about the Android version.
1291 */
1292 private static String getAndroidVersion()
1293 {
1294 String version = "Android " + Build.VERSION.RELEASE + " - " + Build.DISPLAY;
1295 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1296 {
1297 version += "/" + Build.VERSION.SECURITY_PATCH;
1298 }
1299 return version;
1300 }
1301
1302 /**
1303 * Function called via JNI to determine information about the device.
1304 */
1305 private static String getDeviceString()
1306 {
1307 return Build.MODEL + " - " + Build.BRAND + "/" + Build.PRODUCT + "/" + Build.MANUFACTURER;
1308 }
1309 }