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
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>.
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
18 package org
.strongswan
.android
.ui
;
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
;
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
;
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
;
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
;
83 public class VpnProfileDetailActivity
extends AppCompatActivity
85 private static final int SELECT_TRUSTED_CERTIFICATE
= 0;
86 private static final int SELECT_APPLICATIONS
= 1;
88 private VpnProfileDataSource mDataSource
;
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
;
147 public void onCreate(Bundle savedInstanceState
)
149 super.onCreate(savedInstanceState
);
151 /* the title is set when we load the profile, if any */
152 getSupportActionBar().setDisplayHomeAsUpEnabled(true);
154 mDataSource
= new VpnProfileDataSource(this);
157 setContentView(R
.layout
.profile_detail_view
);
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
);
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
);
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
);
175 mCheckAuto
= (CheckBox
)findViewById(R
.id
.ca_auto
);
176 mSelectCert
= (RelativeLayout
)findViewById(R
.id
.select_certificate
);
178 mShowAdvanced
= (CheckBox
)findViewById(R
.id
.show_advanced
);
179 mAdvancedSettings
= (ViewGroup
)findViewById(R
.id
.advanced_settings
);
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
);
203 mSelectSelectedAppsHandling
= (Spinner
)findViewById(R
.id
.apps_handling
);
204 mSelectApps
= (RelativeLayout
)findViewById(R
.id
.select_applications
);
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());
213 mProfileIdLabel
= (TextView
)findViewById(R
.id
.profile_id_label
);
214 mProfileId
= (TextView
)findViewById(R
.id
.profile_id
);
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
);
223 if (Build
.VERSION
.SDK_INT
< Build
.VERSION_CODES
.LOLLIPOP
)
225 findViewById(R
.id
.apps
).setVisibility(View
.GONE
);
226 mSelectSelectedAppsHandling
.setVisibility(View
.GONE
);
227 mSelectApps
.setVisibility(View
.GONE
);
230 mGateway
.addTextChangedListener(new TextWatcher() {
232 public void beforeTextChanged(CharSequence s
, int start
, int count
, int after
) {}
235 public void onTextChanged(CharSequence s
, int start
, int before
, int count
) {}
238 public void afterTextChanged(Editable s
)
240 completeAdapter
.clear();
241 completeAdapter
.add(mGateway
.getText().toString());
242 if (TextUtils
.isEmpty(mGateway
.getText()))
244 mNameWrap
.setHelperText(getString(R
.string
.profile_name_hint
));
245 mRemoteIdWrap
.setHelperText(getString(R
.string
.profile_remote_id_hint
));
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()));
255 mSelectVpnType
.setOnItemSelectedListener(new OnItemSelectedListener() {
257 public void onItemSelected(AdapterView
<?
> parent
, View view
, int position
, long id
)
259 mVpnType
= VpnType
.values()[position
];
260 updateCredentialView();
264 public void onNothingSelected(AdapterView
<?
> parent
)
265 { /* should not happen */
266 mVpnType
= VpnType
.IKEV2_EAP
;
267 updateCredentialView();
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() {
275 public void onClick(View v
)
277 new TncNoticeDialog().show(VpnProfileDetailActivity
.this.getSupportFragmentManager(), "TncNotice");
281 mSelectUserCert
.setOnClickListener(new SelectUserCertOnClickListener());
282 mSelectUserIdAdapter
= new CertificateIdentitiesAdapter(this);
283 mSelectUserId
.setAdapter(mSelectUserIdAdapter
);
284 mSelectUserId
.setOnItemSelectedListener(new OnItemSelectedListener() {
286 public void onItemSelected(AdapterView
<?
> parent
, View view
, int position
, long id
)
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
);
295 public void onNothingSelected(AdapterView
<?
> parent
)
297 mSelectedUserId
= null;
301 mCheckAuto
.setOnCheckedChangeListener(new OnCheckedChangeListener() {
303 public void onCheckedChanged(CompoundButton buttonView
, boolean isChecked
)
305 updateCertificateSelector();
309 mSelectCert
.setOnClickListener(new OnClickListener() {
311 public void onClick(View v
)
313 Intent intent
= new Intent(VpnProfileDetailActivity
.this, TrustedCertificatesActivity
.class);
314 intent
.setAction(TrustedCertificatesActivity
.SELECT_CERTIFICATE
);
315 startActivityForResult(intent
, SELECT_TRUSTED_CERTIFICATE
);
319 mShowAdvanced
.setOnCheckedChangeListener(new OnCheckedChangeListener() {
321 public void onCheckedChanged(CompoundButton buttonView
, boolean isChecked
)
323 updateAdvancedSettings();
327 mSelectSelectedAppsHandling
.setOnItemSelectedListener(new OnItemSelectedListener() {
329 public void onItemSelected(AdapterView
<?
> parent
, View view
, int position
, long id
)
331 mSelectedAppsHandling
= SelectedAppsHandling
.values()[position
];
332 updateAppsSelector();
336 public void onNothingSelected(AdapterView
<?
> parent
)
337 { /* should not happen */
338 mSelectedAppsHandling
= SelectedAppsHandling
.SELECTED_APPS_DISABLE
;
339 updateAppsSelector();
343 mSelectApps
.setOnClickListener(new OnClickListener() {
345 public void onClick(View v
)
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
);
353 mId
= savedInstanceState
== null ?
null : savedInstanceState
.getLong(VpnProfileDataSource
.KEY_ID
);
356 Bundle extras
= getIntent().getExtras();
357 mId
= extras
== null ?
null : extras
.getLong(VpnProfileDataSource
.KEY_ID
);
360 loadProfileData(savedInstanceState
);
362 updateCredentialView();
363 updateCertificateSelector();
364 updateAdvancedSettings();
365 updateAppsSelector();
369 protected void onDestroy()
376 protected void onSaveInstanceState(Bundle outState
)
378 super.onSaveInstanceState(outState
);
381 outState
.putLong(VpnProfileDataSource
.KEY_ID
, mId
);
383 if (mUserCertEntry
!= null)
385 outState
.putString(VpnProfileDataSource
.KEY_USER_CERTIFICATE
, mUserCertEntry
.getAlias());
387 if (mSelectedUserId
!= null)
389 outState
.putString(VpnProfileDataSource
.KEY_LOCAL_ID
, mSelectedUserId
);
391 if (mCertEntry
!= null)
393 outState
.putString(VpnProfileDataSource
.KEY_CERTIFICATE
, mCertEntry
.getAlias());
395 outState
.putStringArrayList(VpnProfileDataSource
.KEY_SELECTED_APPS_LIST
, new ArrayList
<>(mSelectedApps
));
399 public boolean onCreateOptionsMenu(Menu menu
)
401 MenuInflater inflater
= getMenuInflater();
402 inflater
.inflate(R
.menu
.profile_edit
, menu
);
407 public boolean onOptionsItemSelected(MenuItem item
)
409 switch (item
.getItemId())
411 case android
.R
.id
.home
:
412 case R
.id
.menu_cancel
:
415 case R
.id
.menu_accept
:
419 return super.onOptionsItemSelected(item
);
424 protected void onActivityResult(int requestCode
, int resultCode
, Intent data
)
428 case SELECT_TRUSTED_CERTIFICATE
:
429 if (resultCode
== RESULT_OK
)
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();
437 case SELECT_APPLICATIONS
:
438 if (resultCode
== RESULT_OK
)
440 ArrayList
<String
> selection
= data
.getStringArrayListExtra(VpnProfileDataSource
.KEY_SELECTED_APPS_LIST
);
441 mSelectedApps
= new TreeSet
<>(selection
);
442 updateAppsSelector();
446 super.onActivityResult(requestCode
, resultCode
, data
);
451 * Update the UI to enter credentials depending on the type of VPN currently selected
453 private void updateCredentialView()
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
);
459 if (mVpnType
.has(VpnTypeFeature
.CERTIFICATE
))
461 mSelectUserId
.setEnabled(false);
462 if (mUserCertLoading
!= null)
464 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text1
)).setText(mUserCertLoading
);
465 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text2
)).setText(R
.string
.loading
);
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);
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);
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.
489 private void showCertificateAlert()
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() {
496 public void onClick(DialogInterface dialog
, int id
)
505 * Update the CA certificate selection UI depending on whether the
506 * certificate should be automatically selected or not.
508 private void updateCertificateSelector()
510 if (!mCheckAuto
.isChecked())
512 mSelectCert
.setEnabled(true);
513 mSelectCert
.setVisibility(View
.VISIBLE
);
515 if (mCertEntry
!= null)
517 ((TextView
)mSelectCert
.findViewById(android
.R
.id
.text1
)).setText(mCertEntry
.getSubjectPrimary());
518 ((TextView
)mSelectCert
.findViewById(android
.R
.id
.text2
)).setText(mCertEntry
.getSubjectSecondary());
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
);
528 mSelectCert
.setEnabled(false);
529 mSelectCert
.setVisibility(View
.GONE
);
534 * Update the application selection UI
536 private void updateAppsSelector()
538 if (mSelectedAppsHandling
== SelectedAppsHandling
.SELECTED_APPS_DISABLE
)
540 mSelectApps
.setEnabled(false);
541 mSelectApps
.setVisibility(View
.GONE
);
546 mSelectApps
.setEnabled(true);
547 mSelectApps
.setVisibility(View
.VISIBLE
);
549 ((TextView
)mSelectApps
.findViewById(android
.R
.id
.text1
)).setText(R
.string
.profile_select_apps
);
551 switch (mSelectedApps
.size())
554 selected
= getString(R
.string
.profile_select_no_apps
);
557 selected
= getString(R
.string
.profile_select_one_app
);
560 selected
= getString(R
.string
.profile_select_x_apps
, mSelectedApps
.size());
563 ((TextView
)mSelectApps
.findViewById(android
.R
.id
.text2
)).setText(selected
);
568 * Update the advanced settings UI depending on whether any advanced
569 * settings have already been made.
571 private void updateAdvancedSettings()
573 boolean show
= mShowAdvanced
.isChecked();
574 if (!show
&& mProfile
!= null)
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;
585 mShowAdvanced
.setVisibility(!show ? View
.VISIBLE
: View
.GONE
);
586 mAdvancedSettings
.setVisibility(show ? View
.VISIBLE
: View
.GONE
);
588 if (show
&& mProfile
== null)
590 mProfileIdLabel
.setVisibility(View
.GONE
);
591 mProfileId
.setVisibility(View
.GONE
);
596 * Save or update the profile depending on whether we actually have a
597 * profile object or not (this was created in updateProfileData)
599 private void saveProfile()
603 if (mProfile
!= null)
606 if (mProfile
.getUUID() == null)
608 mProfile
.setUUID(UUID
.randomUUID());
610 mDataSource
.updateVpnProfile(mProfile
);
614 mProfile
= new VpnProfile();
616 mDataSource
.insertProfile(mProfile
);
618 Intent intent
= new Intent(Constants
.VPN_PROFILES_CHANGED
);
619 intent
.putExtra(Constants
.VPN_PROFILES_SINGLE
, mProfile
.getId());
620 LocalBroadcastManager
.getInstance(this).sendBroadcast(intent
);
622 setResult(RESULT_OK
, new Intent().putExtra(VpnProfileDataSource
.KEY_ID
, mProfile
.getId()));
628 * Verify the user input and display error messages.
629 * @return true if the input is valid
631 private boolean verifyInput()
633 boolean valid
= true;
634 if (getString(mGateway
) == null)
636 mGatewayWrap
.setError(getString(R
.string
.alert_text_no_input_gateway
));
639 if (mVpnType
.has(VpnTypeFeature
.USER_PASS
))
641 if (getString(mUsername
) == null)
643 mUsernameWrap
.setError(getString(R
.string
.alert_text_no_input_username
));
647 if (mVpnType
.has(VpnTypeFeature
.CERTIFICATE
) && mUserCertEntry
== null)
648 { /* let's show an error icon */
649 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text1
)).setError("");
652 if (!mCheckAuto
.isChecked() && mCertEntry
== null)
654 showCertificateAlert();
657 if (!validateInteger(mMTU
, Constants
.MTU_MIN
, Constants
.MTU_MAX
))
659 mMTUWrap
.setError(String
.format(getString(R
.string
.alert_text_out_of_range
), Constants
.MTU_MIN
, Constants
.MTU_MAX
));
662 if (!validateSubnets(mIncludedSubnets
))
664 mIncludedSubnetsWrap
.setError(getString(R
.string
.alert_text_no_subnets
));
667 if (!validateSubnets(mExcludedSubnets
))
669 mExcludedSubnetsWrap
.setError(getString(R
.string
.alert_text_no_subnets
));
672 if (!validateInteger(mPort
, 1, 65535))
674 mPortWrap
.setError(String
.format(getString(R
.string
.alert_text_out_of_range
), 1, 65535));
677 if (!validateInteger(mNATKeepalive
, Constants
.NAT_KEEPALIVE_MIN
, Constants
.NAT_KEEPALIVE_MAX
))
679 mNATKeepaliveWrap
.setError(String
.format(getString(R
.string
.alert_text_out_of_range
),
680 Constants
.NAT_KEEPALIVE_MIN
, Constants
.NAT_KEEPALIVE_MAX
));
683 if (!validateProposal(mIkeProposal
, true))
685 mIkeProposalWrap
.setError(getString(R
.string
.alert_text_no_proposal
));
688 if (!validateProposal(mEspProposal
, false))
690 mEspProposalWrap
.setError(getString(R
.string
.alert_text_no_proposal
));
693 if (!validateAddresses(mDnsServers
))
695 mDnsServersWrap
.setError(getString(R
.string
.alert_text_no_ips
));
702 * Update the profile object with the data entered by the user
704 private void updateProfileData()
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
))
714 mProfile
.setUsername(getString(mUsername
));
715 mProfile
.setPassword(getString(mPassword
));
717 if (mVpnType
.has(VpnTypeFeature
.CERTIFICATE
))
719 mProfile
.setUserCertificateAlias(mUserCertEntry
.getAlias());
720 mProfile
.setLocalId(mSelectedUserId
);
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
));
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
));
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
));
749 * Load an existing profile if we got an ID
751 * @param savedInstanceState previously saved state
753 private void loadProfileData(Bundle savedInstanceState
)
755 String useralias
= null, local_id
= null, alias
= null;
756 Integer flags
= null;
758 getSupportActionBar().setTitle(R
.string
.add_profile
);
759 if (mId
!= null && mId
!= 0)
761 mProfile
= mDataSource
.getVpnProfile(mId
);
762 if (mProfile
!= null)
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());
791 Log
.e(VpnProfileDetailActivity
.class.getSimpleName(),
792 "VPN profile with id " + mId
+ " not found");
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);
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)
809 UserCertificateLoader loader
= new UserCertificateLoader(this, useralias
);
810 mUserCertLoading
= useralias
;
811 mSelectedUserId
= local_id
;
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);
820 X509Certificate certificate
= TrustedCertificateManager
.getInstance().getCACertificateFromAlias(alias
);
821 if (certificate
!= null)
823 mCertEntry
= new TrustedCertificateEntry(alias
, certificate
);
826 { /* previously selected certificate is not here anymore */
827 showCertificateAlert();
832 mSelectSelectedAppsHandling
.setSelection(mSelectedAppsHandling
.ordinal());
833 if (savedInstanceState
!= null)
835 ArrayList
<String
> selectedApps
= savedInstanceState
.getStringArrayList(VpnProfileDataSource
.KEY_SELECTED_APPS_LIST
);
836 mSelectedApps
= new TreeSet
<>(selectedApps
);
841 * Get the string value in the given text box or null if empty
843 * @param view text box
845 private String
getString(EditText view
)
847 String value
= view
.getText().toString().trim();
848 return value
.isEmpty() ?
null : value
;
852 * Get the integer value in the given text box or null if empty
854 * @param view text box (numeric entry assumed)
856 private Integer
getInteger(EditText view
)
858 String value
= view
.getText().toString().trim();
861 return value
.isEmpty() ?
null : Integer
.valueOf(value
);
863 catch (NumberFormatException e
)
870 * Check that the value in the given text box is a valid integer in the given range
872 * @param view text box (numeric entry assumed)
873 * @param min minimum value (inclusive)
874 * @param max maximum value (inclusive)
876 private boolean validateInteger(EditText view
, Integer min
, Integer max
)
878 String value
= view
.getText().toString().trim();
885 Integer val
= Integer
.valueOf(value
);
886 return min
<= val
&& val
<= max
;
888 catch (NumberFormatException e
)
895 * Check that the value in the given text box is a valid list of subnets/ranges
897 * @param view text box
899 private boolean validateSubnets(EditText view
)
901 String value
= view
.getText().toString().trim();
902 return value
.isEmpty() || IPRangeSet
.fromString(value
) != null;
906 * Check that the value in the given text box is a valid list of IP addresses
908 * @param view text box
910 private boolean validateAddresses(EditText view
)
912 String value
= view
.getText().toString().trim();
917 for (String addr
: value
.split("\\s+"))
921 Utils
.parseInetAddress(addr
);
923 catch (UnknownHostException e
)
932 * Check that the value in the given text box is a valid proposal
934 * @param view text box
936 private boolean validateProposal(EditText view
, boolean ike
)
938 String value
= view
.getText().toString().trim();
939 return value
.isEmpty() || Utils
.isProposalValid(ike
, value
);
942 private class SelectUserCertOnClickListener
implements OnClickListener
, KeyChainAliasCallback
945 public void onClick(View v
)
947 String useralias
= mUserCertEntry
!= null ? mUserCertEntry
.getAlias() : null;
948 KeyChain
.choosePrivateKeyAlias(VpnProfileDetailActivity
.this, this, new String
[] { "RSA" }, null, null, -1, useralias
);
952 public void alias(final String alias
)
955 { /* otherwise the dialog was canceled, the request denied */
958 final X509Certificate
[] chain
= KeyChain
.getCertificateChain(VpnProfileDetailActivity
.this, alias
);
959 /* alias() is not called from our main thread */
960 runOnUiThread(new Runnable() {
964 if (chain
!= null && chain
.length
> 0)
966 mUserCertEntry
= new TrustedCertificateEntry(alias
, chain
[0]);
968 updateCredentialView();
972 catch (KeyChainException
| InterruptedException e
)
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.
985 private class UserCertificateLoader
extends AsyncTask
<Void
, Void
, X509Certificate
>
987 private final Context mContext
;
988 private final String mAlias
;
990 public UserCertificateLoader(Context context
, String alias
)
997 protected X509Certificate
doInBackground(Void
... params
)
999 X509Certificate
[] chain
= null;
1002 chain
= KeyChain
.getCertificateChain(mContext
, mAlias
);
1004 catch (KeyChainException
| InterruptedException e
)
1006 e
.printStackTrace();
1008 if (chain
!= null && chain
.length
> 0)
1016 protected void onPostExecute(X509Certificate result
)
1020 mUserCertEntry
= new TrustedCertificateEntry(mAlias
, result
);
1023 { /* previously selected certificate is not here anymore */
1024 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text1
)).setError("");
1025 mUserCertEntry
= null;
1027 mUserCertLoading
= null;
1028 updateCredentialView();
1033 * Dialog with notification message if EAP-TNC is used.
1035 public static class TncNoticeDialog
extends AppCompatDialogFragment
1038 public Dialog
onCreateDialog(Bundle savedInstanceState
)
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() {
1045 public void onClick(DialogInterface dialog
, int id
)
1054 * Tokenizer implementation that separates by white-space
1056 public static class SpaceTokenizer
implements MultiAutoCompleteTextView
.Tokenizer
1059 public int findTokenStart(CharSequence text
, int cursor
)
1063 while (i
> 0 && !Character
.isWhitespace(text
.charAt(i
- 1)))
1071 public int findTokenEnd(CharSequence text
, int cursor
)
1074 int len
= text
.length();
1078 if (Character
.isWhitespace(text
.charAt(i
)))
1091 public CharSequence
terminateToken(CharSequence text
)
1093 int i
= text
.length();
1095 if (i
> 0 && Character
.isWhitespace(text
.charAt(i
- 1)))
1101 if (text
instanceof Spanned
)
1103 SpannableString sp
= new SpannableString(text
+ " ");
1104 TextUtils
.copySpansFrom((Spanned
) text
, 0, text
.length(), Object
.class, sp
, 0);