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