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