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