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