android: Remove modp1024 from the ESP proposals
[strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / ui / VpnProfileDetailActivity.java
1 /*
2 * Copyright (C) 2012-2017 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.ui;
19
20 import android.app.Dialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.os.AsyncTask;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.security.KeyChain;
28 import android.security.KeyChainAliasCallback;
29 import android.security.KeyChainException;
30 import android.support.v4.content.LocalBroadcastManager;
31 import android.support.v7.app.AlertDialog;
32 import android.support.v7.app.AppCompatActivity;
33 import android.support.v7.app.AppCompatDialogFragment;
34 import android.text.Editable;
35 import android.text.Html;
36 import android.text.SpannableString;
37 import android.text.Spanned;
38 import android.text.TextUtils;
39 import android.text.TextWatcher;
40 import android.util.Log;
41 import android.view.Menu;
42 import android.view.MenuInflater;
43 import android.view.MenuItem;
44 import android.view.View;
45 import android.view.View.OnClickListener;
46 import android.view.ViewGroup;
47 import android.widget.AdapterView;
48 import android.widget.AdapterView.OnItemSelectedListener;
49 import android.widget.ArrayAdapter;
50 import android.widget.CheckBox;
51 import android.widget.CompoundButton;
52 import android.widget.CompoundButton.OnCheckedChangeListener;
53 import android.widget.EditText;
54 import android.widget.MultiAutoCompleteTextView;
55 import android.widget.RelativeLayout;
56 import android.widget.Spinner;
57 import android.widget.Switch;
58 import android.widget.TextView;
59
60 import org.strongswan.android.R;
61 import org.strongswan.android.data.VpnProfile;
62 import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
63 import org.strongswan.android.data.VpnProfileDataSource;
64 import org.strongswan.android.data.VpnType;
65 import org.strongswan.android.data.VpnType.VpnTypeFeature;
66 import org.strongswan.android.logic.TrustedCertificateManager;
67 import org.strongswan.android.security.TrustedCertificateEntry;
68 import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter;
69 import org.strongswan.android.ui.widget.TextInputLayoutHelper;
70 import org.strongswan.android.utils.Constants;
71 import org.strongswan.android.utils.IPRangeSet;
72
73 import java.security.cert.X509Certificate;
74 import java.util.ArrayList;
75 import java.util.SortedSet;
76 import java.util.TreeSet;
77 import java.util.UUID;
78
79 public class VpnProfileDetailActivity extends AppCompatActivity
80 {
81 private static final int SELECT_TRUSTED_CERTIFICATE = 0;
82 private static final int SELECT_APPLICATIONS = 1;
83
84 private VpnProfileDataSource mDataSource;
85 private Long mId;
86 private TrustedCertificateEntry mCertEntry;
87 private String mUserCertLoading;
88 private CertificateIdentitiesAdapter mSelectUserIdAdapter;
89 private String mSelectedUserId;
90 private TrustedCertificateEntry mUserCertEntry;
91 private VpnType mVpnType = VpnType.IKEV2_EAP;
92 private SelectedAppsHandling mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
93 private SortedSet<String> mSelectedApps = new TreeSet<>();
94 private VpnProfile mProfile;
95 private MultiAutoCompleteTextView mName;
96 private TextInputLayoutHelper mNameWrap;
97 private EditText mGateway;
98 private TextInputLayoutHelper mGatewayWrap;
99 private Spinner mSelectVpnType;
100 private ViewGroup mUsernamePassword;
101 private EditText mUsername;
102 private TextInputLayoutHelper mUsernameWrap;
103 private EditText mPassword;
104 private ViewGroup mUserCertificate;
105 private RelativeLayout mSelectUserCert;
106 private Spinner mSelectUserId;
107 private CheckBox mCheckAuto;
108 private RelativeLayout mSelectCert;
109 private RelativeLayout mTncNotice;
110 private CheckBox mShowAdvanced;
111 private ViewGroup mAdvancedSettings;
112 private MultiAutoCompleteTextView mRemoteId;
113 private TextInputLayoutHelper mRemoteIdWrap;
114 private EditText mMTU;
115 private TextInputLayoutHelper mMTUWrap;
116 private EditText mPort;
117 private TextInputLayoutHelper mPortWrap;
118 private Switch mCertReq;
119 private EditText mNATKeepalive;
120 private TextInputLayoutHelper mNATKeepaliveWrap;
121 private EditText mIncludedSubnets;
122 private TextInputLayoutHelper mIncludedSubnetsWrap;
123 private EditText mExcludedSubnets;
124 private TextInputLayoutHelper mExcludedSubnetsWrap;
125 private CheckBox mBlockIPv4;
126 private CheckBox mBlockIPv6;
127 private Spinner mSelectSelectedAppsHandling;
128 private RelativeLayout mSelectApps;
129
130 @Override
131 public void onCreate(Bundle savedInstanceState)
132 {
133 super.onCreate(savedInstanceState);
134
135 /* the title is set when we load the profile, if any */
136 getSupportActionBar().setDisplayHomeAsUpEnabled(true);
137
138 mDataSource = new VpnProfileDataSource(this);
139 mDataSource.open();
140
141 setContentView(R.layout.profile_detail_view);
142
143 mName = (MultiAutoCompleteTextView)findViewById(R.id.name);
144 mNameWrap = (TextInputLayoutHelper)findViewById(R.id.name_wrap);
145 mGateway = (EditText)findViewById(R.id.gateway);
146 mGatewayWrap = (TextInputLayoutHelper) findViewById(R.id.gateway_wrap);
147 mSelectVpnType = (Spinner)findViewById(R.id.vpn_type);
148 mTncNotice = (RelativeLayout)findViewById(R.id.tnc_notice);
149
150 mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group);
151 mUsername = (EditText)findViewById(R.id.username);
152 mUsernameWrap = (TextInputLayoutHelper) findViewById(R.id.username_wrap);
153 mPassword = (EditText)findViewById(R.id.password);
154
155 mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group);
156 mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate);
157 mSelectUserId = (Spinner)findViewById(R.id.select_user_id);
158
159 mCheckAuto = (CheckBox)findViewById(R.id.ca_auto);
160 mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate);
161
162 mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced);
163 mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings);
164
165 mRemoteId = (MultiAutoCompleteTextView)findViewById(R.id.remote_id);
166 mRemoteIdWrap = (TextInputLayoutHelper) findViewById(R.id.remote_id_wrap);
167 mMTU = (EditText)findViewById(R.id.mtu);
168 mMTUWrap = (TextInputLayoutHelper) findViewById(R.id.mtu_wrap);
169 mPort = (EditText)findViewById(R.id.port);
170 mPortWrap = (TextInputLayoutHelper) findViewById(R.id.port_wrap);
171 mNATKeepalive = (EditText)findViewById(R.id.nat_keepalive);
172 mNATKeepaliveWrap = (TextInputLayoutHelper) findViewById(R.id.nat_keepalive_wrap);
173 mCertReq = (Switch)findViewById(R.id.cert_req);
174 mIncludedSubnets = (EditText)findViewById(R.id.included_subnets);
175 mIncludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.included_subnets_wrap);
176 mExcludedSubnets = (EditText)findViewById(R.id.excluded_subnets);
177 mExcludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.excluded_subnets_wrap);
178 mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4);
179 mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6);
180
181 mSelectSelectedAppsHandling = (Spinner)findViewById(R.id.apps_handling);
182 mSelectApps = (RelativeLayout)findViewById(R.id.select_applications);
183
184 final SpaceTokenizer spaceTokenizer = new SpaceTokenizer();
185 mName.setTokenizer(spaceTokenizer);
186 mRemoteId.setTokenizer(spaceTokenizer);
187 final ArrayAdapter<String> completeAdapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line);
188 mName.setAdapter(completeAdapter);
189 mRemoteId.setAdapter(completeAdapter);
190
191 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
192 {
193 findViewById(R.id.apps).setVisibility(View.GONE);
194 mSelectSelectedAppsHandling.setVisibility(View.GONE);
195 mSelectApps.setVisibility(View.GONE);
196 }
197
198 mGateway.addTextChangedListener(new TextWatcher() {
199 @Override
200 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
201
202 @Override
203 public void onTextChanged(CharSequence s, int start, int before, int count) {}
204
205 @Override
206 public void afterTextChanged(Editable s)
207 {
208 completeAdapter.clear();
209 completeAdapter.add(mGateway.getText().toString());
210 if (TextUtils.isEmpty(mGateway.getText()))
211 {
212 mNameWrap.setHelperText(getString(R.string.profile_name_hint));
213 mRemoteIdWrap.setHelperText(getString(R.string.profile_remote_id_hint));
214 }
215 else
216 {
217 mNameWrap.setHelperText(String.format(getString(R.string.profile_name_hint_gateway), mGateway.getText()));
218 mRemoteIdWrap.setHelperText(String.format(getString(R.string.profile_remote_id_hint_gateway), mGateway.getText()));
219 }
220 }
221 });
222
223 mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() {
224 @Override
225 public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
226 {
227 mVpnType = VpnType.values()[position];
228 updateCredentialView();
229 }
230
231 @Override
232 public void onNothingSelected(AdapterView<?> parent)
233 { /* should not happen */
234 mVpnType = VpnType.IKEV2_EAP;
235 updateCredentialView();
236 }
237 });
238
239 ((TextView)mTncNotice.findViewById(android.R.id.text1)).setText(R.string.tnc_notice_title);
240 ((TextView)mTncNotice.findViewById(android.R.id.text2)).setText(R.string.tnc_notice_subtitle);
241 mTncNotice.setOnClickListener(new OnClickListener() {
242 @Override
243 public void onClick(View v)
244 {
245 new TncNoticeDialog().show(VpnProfileDetailActivity.this.getSupportFragmentManager(), "TncNotice");
246 }
247 });
248
249 mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
250 mSelectUserIdAdapter = new CertificateIdentitiesAdapter(this);
251 mSelectUserId.setAdapter(mSelectUserIdAdapter);
252 mSelectUserId.setOnItemSelectedListener(new OnItemSelectedListener() {
253 @Override
254 public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
255 {
256 if (mUserCertEntry != null)
257 { /* we don't store the subject DN as it is in the reverse order and the default anyway */
258 mSelectedUserId = position == 0 ? null : mSelectUserIdAdapter.getItem(position);
259 }
260 }
261
262 @Override
263 public void onNothingSelected(AdapterView<?> parent)
264 {
265 mSelectedUserId = null;
266 }
267 });
268
269 mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() {
270 @Override
271 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
272 {
273 updateCertificateSelector();
274 }
275 });
276
277 mSelectCert.setOnClickListener(new OnClickListener() {
278 @Override
279 public void onClick(View v)
280 {
281 Intent intent = new Intent(VpnProfileDetailActivity.this, TrustedCertificatesActivity.class);
282 intent.setAction(TrustedCertificatesActivity.SELECT_CERTIFICATE);
283 startActivityForResult(intent, SELECT_TRUSTED_CERTIFICATE);
284 }
285 });
286
287 mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() {
288 @Override
289 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
290 {
291 updateAdvancedSettings();
292 }
293 });
294
295 mSelectSelectedAppsHandling.setOnItemSelectedListener(new OnItemSelectedListener() {
296 @Override
297 public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
298 {
299 mSelectedAppsHandling = SelectedAppsHandling.values()[position];
300 updateAppsSelector();
301 }
302
303 @Override
304 public void onNothingSelected(AdapterView<?> parent)
305 { /* should not happen */
306 mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
307 updateAppsSelector();
308 }
309 });
310
311 mSelectApps.setOnClickListener(new OnClickListener() {
312 @Override
313 public void onClick(View v)
314 {
315 Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class);
316 intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
317 startActivityForResult(intent, SELECT_APPLICATIONS);
318 }
319 });
320
321 mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID);
322 if (mId == null)
323 {
324 Bundle extras = getIntent().getExtras();
325 mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID);
326 }
327
328 loadProfileData(savedInstanceState);
329
330 updateCredentialView();
331 updateCertificateSelector();
332 updateAdvancedSettings();
333 updateAppsSelector();
334 }
335
336 @Override
337 protected void onDestroy()
338 {
339 super.onDestroy();
340 mDataSource.close();
341 }
342
343 @Override
344 protected void onSaveInstanceState(Bundle outState)
345 {
346 super.onSaveInstanceState(outState);
347 if (mId != null)
348 {
349 outState.putLong(VpnProfileDataSource.KEY_ID, mId);
350 }
351 if (mUserCertEntry != null)
352 {
353 outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias());
354 }
355 if (mSelectedUserId != null)
356 {
357 outState.putString(VpnProfileDataSource.KEY_LOCAL_ID, mSelectedUserId);
358 }
359 if (mCertEntry != null)
360 {
361 outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias());
362 }
363 outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
364 }
365
366 @Override
367 public boolean onCreateOptionsMenu(Menu menu)
368 {
369 MenuInflater inflater = getMenuInflater();
370 inflater.inflate(R.menu.profile_edit, menu);
371 return true;
372 }
373
374 @Override
375 public boolean onOptionsItemSelected(MenuItem item)
376 {
377 switch (item.getItemId())
378 {
379 case android.R.id.home:
380 case R.id.menu_cancel:
381 finish();
382 return true;
383 case R.id.menu_accept:
384 saveProfile();
385 return true;
386 default:
387 return super.onOptionsItemSelected(item);
388 }
389 }
390
391 @Override
392 protected void onActivityResult(int requestCode, int resultCode, Intent data)
393 {
394 switch (requestCode)
395 {
396 case SELECT_TRUSTED_CERTIFICATE:
397 if (resultCode == RESULT_OK)
398 {
399 String alias = data.getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE);
400 X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
401 mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate);
402 updateCertificateSelector();
403 }
404 break;
405 case SELECT_APPLICATIONS:
406 if (resultCode == RESULT_OK)
407 {
408 ArrayList<String> selection = data.getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
409 mSelectedApps = new TreeSet<>(selection);
410 updateAppsSelector();
411 }
412 break;
413 default:
414 super.onActivityResult(requestCode, resultCode, data);
415 }
416 }
417
418 /**
419 * Update the UI to enter credentials depending on the type of VPN currently selected
420 */
421 private void updateCredentialView()
422 {
423 mUsernamePassword.setVisibility(mVpnType.has(VpnTypeFeature.USER_PASS) ? View.VISIBLE : View.GONE);
424 mUserCertificate.setVisibility(mVpnType.has(VpnTypeFeature.CERTIFICATE) ? View.VISIBLE : View.GONE);
425 mTncNotice.setVisibility(mVpnType.has(VpnTypeFeature.BYOD) ? View.VISIBLE : View.GONE);
426
427 if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
428 {
429 mSelectUserId.setEnabled(false);
430 if (mUserCertLoading != null)
431 {
432 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertLoading);
433 ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.loading);
434 }
435 else if (mUserCertEntry != null)
436 { /* clear any errors and set the new data */
437 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(null);
438 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertEntry.getAlias());
439 ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(mUserCertEntry.getCertificate().getSubjectDN().toString());
440 mSelectUserIdAdapter.setCertificate(mUserCertEntry);
441 mSelectUserId.setSelection(mSelectUserIdAdapter.getPosition(mSelectedUserId));
442 mSelectUserId.setEnabled(true);
443 }
444 else
445 {
446 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(R.string.profile_user_select_certificate_label);
447 ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.profile_user_select_certificate);
448 mSelectUserIdAdapter.setCertificate(null);
449 }
450 }
451 }
452
453 /**
454 * Show an alert in case the previously selected certificate is not found anymore
455 * or the user did not select a certificate in the spinner.
456 */
457 private void showCertificateAlert()
458 {
459 AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this);
460 adb.setTitle(R.string.alert_text_nocertfound_title);
461 adb.setMessage(R.string.alert_text_nocertfound);
462 adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
463 @Override
464 public void onClick(DialogInterface dialog, int id)
465 {
466 dialog.cancel();
467 }
468 });
469 adb.show();
470 }
471
472 /**
473 * Update the CA certificate selection UI depending on whether the
474 * certificate should be automatically selected or not.
475 */
476 private void updateCertificateSelector()
477 {
478 if (!mCheckAuto.isChecked())
479 {
480 mSelectCert.setEnabled(true);
481 mSelectCert.setVisibility(View.VISIBLE);
482
483 if (mCertEntry != null)
484 {
485 ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(mCertEntry.getSubjectPrimary());
486 ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(mCertEntry.getSubjectSecondary());
487 }
488 else
489 {
490 ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(R.string.profile_ca_select_certificate_label);
491 ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(R.string.profile_ca_select_certificate);
492 }
493 }
494 else
495 {
496 mSelectCert.setEnabled(false);
497 mSelectCert.setVisibility(View.GONE);
498 }
499 }
500
501 /**
502 * Update the application selection UI
503 */
504 private void updateAppsSelector()
505 {
506 if (mSelectedAppsHandling == SelectedAppsHandling.SELECTED_APPS_DISABLE)
507 {
508 mSelectApps.setEnabled(false);
509 mSelectApps.setVisibility(View.GONE);
510
511 }
512 else
513 {
514 mSelectApps.setEnabled(true);
515 mSelectApps.setVisibility(View.VISIBLE);
516
517 ((TextView)mSelectApps.findViewById(android.R.id.text1)).setText(R.string.profile_select_apps);
518 String selected;
519 switch (mSelectedApps.size())
520 {
521 case 0:
522 selected = getString(R.string.profile_select_no_apps);
523 break;
524 case 1:
525 selected = getString(R.string.profile_select_one_app);
526 break;
527 default:
528 selected = getString(R.string.profile_select_x_apps, mSelectedApps.size());
529 break;
530 }
531 ((TextView)mSelectApps.findViewById(android.R.id.text2)).setText(selected);
532 }
533 }
534
535 /**
536 * Update the advanced settings UI depending on whether any advanced
537 * settings have already been made.
538 */
539 private void updateAdvancedSettings()
540 {
541 boolean show = mShowAdvanced.isChecked();
542 if (!show && mProfile != null)
543 {
544 Integer st = mProfile.getSplitTunneling(), flags = mProfile.getFlags();
545 show = mProfile.getRemoteId() != null || mProfile.getMTU() != null ||
546 mProfile.getPort() != null || mProfile.getNATKeepAlive() != null ||
547 (flags != null && flags != 0) || (st != null && st != 0) ||
548 mProfile.getIncludedSubnets() != null || mProfile.getExcludedSubnets() != null ||
549 mProfile.getSelectedAppsHandling() != SelectedAppsHandling.SELECTED_APPS_DISABLE;
550 }
551 mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE);
552 mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE);
553 }
554
555 /**
556 * Save or update the profile depending on whether we actually have a
557 * profile object or not (this was created in updateProfileData)
558 */
559 private void saveProfile()
560 {
561 if (verifyInput())
562 {
563 if (mProfile != null)
564 {
565 updateProfileData();
566 if (mProfile.getUUID() == null)
567 {
568 mProfile.setUUID(UUID.randomUUID());
569 }
570 mDataSource.updateVpnProfile(mProfile);
571 }
572 else
573 {
574 mProfile = new VpnProfile();
575 updateProfileData();
576 mDataSource.insertProfile(mProfile);
577 }
578 Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
579 intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getId());
580 LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
581
582 setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId()));
583 finish();
584 }
585 }
586
587 /**
588 * Verify the user input and display error messages.
589 * @return true if the input is valid
590 */
591 private boolean verifyInput()
592 {
593 boolean valid = true;
594 if (mGateway.getText().toString().trim().isEmpty())
595 {
596 mGatewayWrap.setError(getString(R.string.alert_text_no_input_gateway));
597 valid = false;
598 }
599 if (mVpnType.has(VpnTypeFeature.USER_PASS))
600 {
601 if (mUsername.getText().toString().trim().isEmpty())
602 {
603 mUsernameWrap.setError(getString(R.string.alert_text_no_input_username));
604 valid = false;
605 }
606 }
607 if (mVpnType.has(VpnTypeFeature.CERTIFICATE) && mUserCertEntry == null)
608 { /* let's show an error icon */
609 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
610 valid = false;
611 }
612 if (!mCheckAuto.isChecked() && mCertEntry == null)
613 {
614 showCertificateAlert();
615 valid = false;
616 }
617 if (!validateInteger(mMTU, Constants.MTU_MIN, Constants.MTU_MAX))
618 {
619 mMTUWrap.setError(String.format(getString(R.string.alert_text_out_of_range), Constants.MTU_MIN, Constants.MTU_MAX));
620 valid = false;
621 }
622 if (!validateSubnets(mIncludedSubnets))
623 {
624 mIncludedSubnetsWrap.setError(getString(R.string.alert_text_no_subnets));
625 valid = false;
626 }
627 if (!validateSubnets(mExcludedSubnets))
628 {
629 mExcludedSubnetsWrap.setError(getString(R.string.alert_text_no_subnets));
630 valid = false;
631 }
632 if (!validateInteger(mPort, 1, 65535))
633 {
634 mPortWrap.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
635 valid = false;
636 }
637 if (!validateInteger(mNATKeepalive, Constants.NAT_KEEPALIVE_MIN, Constants.NAT_KEEPALIVE_MAX))
638 {
639 mNATKeepaliveWrap.setError(String.format(getString(R.string.alert_text_out_of_range),
640 Constants.NAT_KEEPALIVE_MIN, Constants.NAT_KEEPALIVE_MAX));
641 valid = false;
642 }
643 return valid;
644 }
645
646 /**
647 * Update the profile object with the data entered by the user
648 */
649 private void updateProfileData()
650 {
651 /* the name is optional, we default to the gateway if none is given */
652 String name = mName.getText().toString().trim();
653 String gateway = mGateway.getText().toString().trim();
654 mProfile.setName(name.isEmpty() ? gateway : name);
655 mProfile.setGateway(gateway);
656 mProfile.setVpnType(mVpnType);
657 if (mVpnType.has(VpnTypeFeature.USER_PASS))
658 {
659 mProfile.setUsername(mUsername.getText().toString().trim());
660 String password = mPassword.getText().toString().trim();
661 password = password.isEmpty() ? null : password;
662 mProfile.setPassword(password);
663 }
664 if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
665 {
666 mProfile.setUserCertificateAlias(mUserCertEntry.getAlias());
667 mProfile.setLocalId(mSelectedUserId);
668 }
669 String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias();
670 mProfile.setCertificateAlias(certAlias);
671 String remote_id = mRemoteId.getText().toString().trim();
672 mProfile.setRemoteId(remote_id.isEmpty() ? null : remote_id);
673 mProfile.setMTU(getInteger(mMTU));
674 mProfile.setPort(getInteger(mPort));
675 mProfile.setNATKeepAlive(getInteger(mNATKeepalive));
676 int flags = 0;
677 flags |= !mCertReq.isChecked() ? VpnProfile.FLAGS_SUPPRESS_CERT_REQS : 0;
678 mProfile.setFlags(flags);
679 String included = mIncludedSubnets.getText().toString().trim();
680 mProfile.setIncludedSubnets(included.isEmpty() ? null : included);
681 String excluded = mExcludedSubnets.getText().toString().trim();
682 mProfile.setExcludedSubnets(excluded.isEmpty() ? null : excluded);
683 int st = 0;
684 st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0;
685 st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0;
686 mProfile.setSplitTunneling(st == 0 ? null : st);
687 mProfile.setSelectedAppsHandling(mSelectedAppsHandling);
688 mProfile.setSelectedApps(mSelectedApps);
689 }
690
691 /**
692 * Load an existing profile if we got an ID
693 *
694 * @param savedInstanceState previously saved state
695 */
696 private void loadProfileData(Bundle savedInstanceState)
697 {
698 String useralias = null, local_id = null, alias = null;
699 Integer flags = null;
700
701 getSupportActionBar().setTitle(R.string.add_profile);
702 if (mId != null && mId != 0)
703 {
704 mProfile = mDataSource.getVpnProfile(mId);
705 if (mProfile != null)
706 {
707 mName.setText(mProfile.getName());
708 mGateway.setText(mProfile.getGateway());
709 mVpnType = mProfile.getVpnType();
710 mUsername.setText(mProfile.getUsername());
711 mPassword.setText(mProfile.getPassword());
712 mRemoteId.setText(mProfile.getRemoteId());
713 mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null);
714 mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null);
715 mNATKeepalive.setText(mProfile.getNATKeepAlive() != null ? mProfile.getNATKeepAlive().toString() : null);
716 mIncludedSubnets.setText(mProfile.getIncludedSubnets());
717 mExcludedSubnets.setText(mProfile.getExcludedSubnets());
718 mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0);
719 mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0);
720 mSelectedAppsHandling = mProfile.getSelectedAppsHandling();
721 mSelectedApps = mProfile.getSelectedAppsSet();
722 flags = mProfile.getFlags();
723 useralias = mProfile.getUserCertificateAlias();
724 local_id = mProfile.getLocalId();
725 alias = mProfile.getCertificateAlias();
726 getSupportActionBar().setTitle(mProfile.getName());
727 }
728 else
729 {
730 Log.e(VpnProfileDetailActivity.class.getSimpleName(),
731 "VPN profile with id " + mId + " not found");
732 finish();
733 }
734 }
735
736 mSelectVpnType.setSelection(mVpnType.ordinal());
737 mCertReq.setChecked(flags == null || (flags & VpnProfile.FLAGS_SUPPRESS_CERT_REQS) == 0);
738
739 /* check if the user selected a user certificate previously */
740 useralias = savedInstanceState == null ? useralias : savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
741 local_id = savedInstanceState == null ? local_id : savedInstanceState.getString(VpnProfileDataSource.KEY_LOCAL_ID);
742 if (useralias != null)
743 {
744 UserCertificateLoader loader = new UserCertificateLoader(this, useralias);
745 mUserCertLoading = useralias;
746 mSelectedUserId = local_id;
747 loader.execute();
748 }
749
750 /* check if the user selected a CA certificate previously */
751 alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE);
752 mCheckAuto.setChecked(alias == null);
753 if (alias != null)
754 {
755 X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
756 if (certificate != null)
757 {
758 mCertEntry = new TrustedCertificateEntry(alias, certificate);
759 }
760 else
761 { /* previously selected certificate is not here anymore */
762 showCertificateAlert();
763 mCertEntry = null;
764 }
765 }
766
767 mSelectSelectedAppsHandling.setSelection(mSelectedAppsHandling.ordinal());
768 if (savedInstanceState != null)
769 {
770 ArrayList<String> selectedApps = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
771 mSelectedApps = new TreeSet<>(selectedApps);
772 }
773 }
774
775 /**
776 * Get the integer value in the given text box or null if empty
777 *
778 * @param view text box (numeric entry assumed)
779 */
780 private Integer getInteger(EditText view)
781 {
782 String value = view.getText().toString().trim();
783 try
784 {
785 return value.isEmpty() ? null : Integer.valueOf(value);
786 }
787 catch (NumberFormatException e)
788 {
789 return null;
790 }
791 }
792
793 /**
794 * Check that the value in the given text box is a valid integer in the given range
795 *
796 * @param view text box (numeric entry assumed)
797 * @param min minimum value (inclusive)
798 * @param max maximum value (inclusive)
799 */
800 private boolean validateInteger(EditText view, Integer min, Integer max)
801 {
802 String value = view.getText().toString().trim();
803 try
804 {
805 if (value.isEmpty())
806 {
807 return true;
808 }
809 Integer val = Integer.valueOf(value);
810 return min <= val && val <= max;
811 }
812 catch (NumberFormatException e)
813 {
814 return false;
815 }
816 }
817
818 /**
819 * Check that the value in the given text box is a valid list of subnets/ranges
820 *
821 * @param view text box
822 */
823 private boolean validateSubnets(EditText view)
824 {
825 String value = view.getText().toString().trim();
826 return value.isEmpty() || IPRangeSet.fromString(value) != null;
827 }
828
829 private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback
830 {
831 @Override
832 public void onClick(View v)
833 {
834 String useralias = mUserCertEntry != null ? mUserCertEntry.getAlias() : null;
835 KeyChain.choosePrivateKeyAlias(VpnProfileDetailActivity.this, this, new String[] { "RSA" }, null, null, -1, useralias);
836 }
837
838 @Override
839 public void alias(final String alias)
840 {
841 if (alias != null)
842 { /* otherwise the dialog was canceled, the request denied */
843 try
844 {
845 final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias);
846 /* alias() is not called from our main thread */
847 runOnUiThread(new Runnable() {
848 @Override
849 public void run()
850 {
851 if (chain != null && chain.length > 0)
852 {
853 mUserCertEntry = new TrustedCertificateEntry(alias, chain[0]);
854 }
855 updateCredentialView();
856 }
857 });
858 }
859 catch (KeyChainException | InterruptedException e)
860 {
861 e.printStackTrace();
862 }
863 }
864 }
865 }
866
867 /**
868 * Load the selected user certificate asynchronously. This cannot be done
869 * from the main thread as getCertificateChain() calls back to our main
870 * thread to bind to the KeyChain service resulting in a deadlock.
871 */
872 private class UserCertificateLoader extends AsyncTask<Void, Void, X509Certificate>
873 {
874 private final Context mContext;
875 private final String mAlias;
876
877 public UserCertificateLoader(Context context, String alias)
878 {
879 mContext = context;
880 mAlias = alias;
881 }
882
883 @Override
884 protected X509Certificate doInBackground(Void... params)
885 {
886 X509Certificate[] chain = null;
887 try
888 {
889 chain = KeyChain.getCertificateChain(mContext, mAlias);
890 }
891 catch (KeyChainException | InterruptedException e)
892 {
893 e.printStackTrace();
894 }
895 if (chain != null && chain.length > 0)
896 {
897 return chain[0];
898 }
899 return null;
900 }
901
902 @Override
903 protected void onPostExecute(X509Certificate result)
904 {
905 if (result != null)
906 {
907 mUserCertEntry = new TrustedCertificateEntry(mAlias, result);
908 }
909 else
910 { /* previously selected certificate is not here anymore */
911 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
912 mUserCertEntry = null;
913 }
914 mUserCertLoading = null;
915 updateCredentialView();
916 }
917 }
918
919 /**
920 * Dialog with notification message if EAP-TNC is used.
921 */
922 public static class TncNoticeDialog extends AppCompatDialogFragment
923 {
924 @Override
925 public Dialog onCreateDialog(Bundle savedInstanceState)
926 {
927 return new AlertDialog.Builder(getActivity())
928 .setTitle(R.string.tnc_notice_title)
929 .setMessage(Html.fromHtml(getString(R.string.tnc_notice_details)))
930 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
931 @Override
932 public void onClick(DialogInterface dialog, int id)
933 {
934 dialog.dismiss();
935 }
936 }).create();
937 }
938 }
939
940 /**
941 * Tokenizer implementation that separates by white-space
942 */
943 public static class SpaceTokenizer implements MultiAutoCompleteTextView.Tokenizer
944 {
945 @Override
946 public int findTokenStart(CharSequence text, int cursor)
947 {
948 int i = cursor;
949
950 while (i > 0 && !Character.isWhitespace(text.charAt(i - 1)))
951 {
952 i--;
953 }
954 return i;
955 }
956
957 @Override
958 public int findTokenEnd(CharSequence text, int cursor)
959 {
960 int i = cursor;
961 int len = text.length();
962
963 while (i < len)
964 {
965 if (Character.isWhitespace(text.charAt(i)))
966 {
967 return i;
968 }
969 else
970 {
971 i++;
972 }
973 }
974 return len;
975 }
976
977 @Override
978 public CharSequence terminateToken(CharSequence text)
979 {
980 int i = text.length();
981
982 if (i > 0 && Character.isWhitespace(text.charAt(i - 1)))
983 {
984 return text;
985 }
986 else
987 {
988 if (text instanceof Spanned)
989 {
990 SpannableString sp = new SpannableString(text + " ");
991 TextUtils.copySpansFrom((Spanned) text, 0, text.length(), Object.class, sp, 0);
992 return sp;
993 }
994 else
995 {
996 return text + " ";
997 }
998 }
999 }
1000 }
1001 }