2 * Copyright (C) 2016-2018 Tobias Brunner
3 * HSR Hochschule fuer Technik Rapperswil
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 package org
.strongswan
.android
.ui
;
18 import android
.app
.Activity
;
19 import android
.app
.LoaderManager
;
20 import android
.content
.ActivityNotFoundException
;
21 import android
.content
.AsyncTaskLoader
;
22 import android
.content
.ContentResolver
;
23 import android
.content
.Context
;
24 import android
.content
.Intent
;
25 import android
.content
.Loader
;
26 import android
.net
.Uri
;
27 import android
.os
.Build
;
28 import android
.os
.Bundle
;
29 import android
.security
.KeyChain
;
30 import android
.security
.KeyChainAliasCallback
;
31 import android
.security
.KeyChainException
;
32 import android
.support
.v4
.content
.LocalBroadcastManager
;
33 import android
.support
.v7
.app
.AppCompatActivity
;
34 import android
.text
.TextUtils
;
35 import android
.util
.Base64
;
36 import android
.view
.Menu
;
37 import android
.view
.MenuInflater
;
38 import android
.view
.MenuItem
;
39 import android
.view
.View
;
40 import android
.view
.ViewGroup
;
41 import android
.widget
.Button
;
42 import android
.widget
.EditText
;
43 import android
.widget
.RelativeLayout
;
44 import android
.widget
.TextView
;
45 import android
.widget
.Toast
;
47 import org
.json
.JSONArray
;
48 import org
.json
.JSONException
;
49 import org
.json
.JSONObject
;
50 import org
.strongswan
.android
.R
;
51 import org
.strongswan
.android
.data
.VpnProfile
;
52 import org
.strongswan
.android
.data
.VpnProfile
.SelectedAppsHandling
;
53 import org
.strongswan
.android
.data
.VpnProfileDataSource
;
54 import org
.strongswan
.android
.data
.VpnType
;
55 import org
.strongswan
.android
.data
.VpnType
.VpnTypeFeature
;
56 import org
.strongswan
.android
.logic
.TrustedCertificateManager
;
57 import org
.strongswan
.android
.security
.TrustedCertificateEntry
;
58 import org
.strongswan
.android
.ui
.widget
.TextInputLayoutHelper
;
59 import org
.strongswan
.android
.utils
.Constants
;
60 import org
.strongswan
.android
.utils
.IPRangeSet
;
61 import org
.strongswan
.android
.utils
.Utils
;
63 import java
.io
.ByteArrayInputStream
;
64 import java
.io
.ByteArrayOutputStream
;
65 import java
.io
.FileNotFoundException
;
66 import java
.io
.IOException
;
67 import java
.io
.InputStream
;
69 import java
.net
.UnknownHostException
;
70 import java
.security
.KeyStore
;
71 import java
.security
.KeyStoreException
;
72 import java
.security
.NoSuchAlgorithmException
;
73 import java
.security
.cert
.CertificateException
;
74 import java
.security
.cert
.CertificateFactory
;
75 import java
.security
.cert
.X509Certificate
;
76 import java
.util
.ArrayList
;
77 import java
.util
.UUID
;
79 import javax
.net
.ssl
.SSLHandshakeException
;
81 public class VpnProfileImportActivity
extends AppCompatActivity
83 private static final String PKCS12_INSTALLED
= "PKCS12_INSTALLED";
84 private static final String PROFILE_URI
= "PROFILE_URI";
85 private static final int INSTALL_PKCS12
= 0;
86 private static final int OPEN_DOCUMENT
= 1;
87 private static final int PROFILE_LOADER
= 0;
88 private static final int USER_CERT_LOADER
= 1;
90 private VpnProfileDataSource mDataSource
;
91 private ParsedVpnProfile mProfile
;
92 private VpnProfile mExisting
;
93 private TrustedCertificateEntry mCertEntry
;
94 private TrustedCertificateEntry mUserCertEntry
;
95 private String mUserCertLoading
;
96 private boolean mHideImport
;
97 private android
.support
.v4
.widget
.ContentLoadingProgressBar mProgressBar
;
98 private TextView mExistsWarning
;
99 private ViewGroup mBasicDataGroup
;
100 private TextView mName
;
101 private TextView mGateway
;
102 private TextView mSelectVpnType
;
103 private ViewGroup mUsernamePassword
;
104 private EditText mUsername
;
105 private TextInputLayoutHelper mUsernameWrap
;
106 private EditText mPassword
;
107 private ViewGroup mUserCertificate
;
108 private RelativeLayout mSelectUserCert
;
109 private Button mImportUserCert
;
110 private ViewGroup mRemoteCertificate
;
111 private RelativeLayout mRemoteCert
;
113 private LoaderManager
.LoaderCallbacks
<ProfileLoadResult
> mProfileLoaderCallbacks
= new LoaderManager
.LoaderCallbacks
<ProfileLoadResult
>()
116 public Loader
<ProfileLoadResult
> onCreateLoader(int id
, Bundle args
)
118 return new ProfileLoader(VpnProfileImportActivity
.this, (Uri
)args
.getParcelable(PROFILE_URI
));
122 public void onLoadFinished(Loader
<ProfileLoadResult
> loader
, ProfileLoadResult data
)
128 public void onLoaderReset(Loader
<ProfileLoadResult
> loader
)
134 private LoaderManager
.LoaderCallbacks
<TrustedCertificateEntry
> mUserCertificateLoaderCallbacks
= new LoaderManager
.LoaderCallbacks
<TrustedCertificateEntry
>()
137 public Loader
<TrustedCertificateEntry
> onCreateLoader(int id
, Bundle args
)
139 return new UserCertificateLoader(VpnProfileImportActivity
.this, mUserCertLoading
);
143 public void onLoadFinished(Loader
<TrustedCertificateEntry
> loader
, TrustedCertificateEntry data
)
145 handleUserCertificate(data
);
149 public void onLoaderReset(Loader
<TrustedCertificateEntry
> loader
)
156 public void onCreate(Bundle savedInstanceState
)
158 super.onCreate(savedInstanceState
);
160 getSupportActionBar().setHomeAsUpIndicator(R
.drawable
.ic_close_white_24dp
);
161 getSupportActionBar().setDisplayHomeAsUpEnabled(true);
163 mDataSource
= new VpnProfileDataSource(this);
166 setContentView(R
.layout
.profile_import_view
);
168 mProgressBar
= findViewById(R
.id
.progress_bar
);
169 mExistsWarning
= (TextView
)findViewById(R
.id
.exists_warning
);
170 mBasicDataGroup
= (ViewGroup
)findViewById(R
.id
.basic_data_group
);
171 mName
= (TextView
)findViewById(R
.id
.name
);
172 mGateway
= (TextView
)findViewById(R
.id
.gateway
);
173 mSelectVpnType
= (TextView
)findViewById(R
.id
.vpn_type
);
175 mUsernamePassword
= (ViewGroup
)findViewById(R
.id
.username_password_group
);
176 mUsername
= (EditText
)findViewById(R
.id
.username
);
177 mUsernameWrap
= (TextInputLayoutHelper
) findViewById(R
.id
.username_wrap
);
178 mPassword
= (EditText
)findViewById(R
.id
.password
);
180 mUserCertificate
= (ViewGroup
)findViewById(R
.id
.user_certificate_group
);
181 mSelectUserCert
= (RelativeLayout
)findViewById(R
.id
.select_user_certificate
);
182 mImportUserCert
= (Button
)findViewById(R
.id
.import_user_certificate
);
184 mRemoteCertificate
= (ViewGroup
)findViewById(R
.id
.remote_certificate_group
);
185 mRemoteCert
= (RelativeLayout
)findViewById(R
.id
.remote_certificate
);
187 mExistsWarning
.setVisibility(View
.GONE
);
188 mBasicDataGroup
.setVisibility(View
.GONE
);
189 mUsernamePassword
.setVisibility(View
.GONE
);
190 mUserCertificate
.setVisibility(View
.GONE
);
191 mRemoteCertificate
.setVisibility(View
.GONE
);
193 mSelectUserCert
.setOnClickListener(new SelectUserCertOnClickListener());
194 mImportUserCert
.setOnClickListener(new View
.OnClickListener() {
196 public void onClick(View v
)
198 Intent intent
= KeyChain
.createInstallIntent();
199 intent
.putExtra(KeyChain
.EXTRA_NAME
, getString(R
.string
.profile_cert_alias
, mProfile
.getName()));
200 intent
.putExtra(KeyChain
.EXTRA_PKCS12
, mProfile
.PKCS12
);
201 startActivityForResult(intent
, INSTALL_PKCS12
);
205 Intent intent
= getIntent();
206 String action
= intent
.getAction();
207 if (Intent
.ACTION_VIEW
.equals(action
))
209 loadProfile(getIntent().getData());
211 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.KITKAT
)
213 Intent openIntent
= new Intent(Intent
.ACTION_OPEN_DOCUMENT
);
214 openIntent
.setType("*/*");
217 startActivityForResult(openIntent
, OPEN_DOCUMENT
);
219 catch (ActivityNotFoundException e
)
220 { /* some devices are unable to browse for files */
226 if (savedInstanceState
!= null)
228 mUserCertLoading
= savedInstanceState
.getString(VpnProfileDataSource
.KEY_USER_CERTIFICATE
);
229 if (mUserCertLoading
!= null)
231 getLoaderManager().initLoader(USER_CERT_LOADER
, null, mUserCertificateLoaderCallbacks
);
233 mImportUserCert
.setEnabled(!savedInstanceState
.getBoolean(PKCS12_INSTALLED
));
238 protected void onDestroy()
245 protected void onSaveInstanceState(Bundle outState
)
247 super.onSaveInstanceState(outState
);
248 if (mUserCertEntry
!= null)
250 outState
.putString(VpnProfileDataSource
.KEY_USER_CERTIFICATE
, mUserCertEntry
.getAlias());
252 outState
.putBoolean(PKCS12_INSTALLED
, !mImportUserCert
.isEnabled());
256 public boolean onCreateOptionsMenu(Menu menu
)
258 MenuInflater inflater
= getMenuInflater();
259 inflater
.inflate(R
.menu
.profile_import
, menu
);
262 MenuItem item
= menu
.findItem(R
.id
.menu_accept
);
263 item
.setVisible(false);
269 public boolean onOptionsItemSelected(MenuItem item
)
271 switch (item
.getItemId())
273 case android
.R
.id
.home
:
276 case R
.id
.menu_accept
:
280 return super.onOptionsItemSelected(item
);
285 protected void onActivityResult(int requestCode
, int resultCode
, Intent data
)
287 super.onActivityResult(requestCode
, resultCode
, data
);
291 if (resultCode
== Activity
.RESULT_OK
)
292 { /* no need to import twice */
293 mImportUserCert
.setEnabled(false);
294 mSelectUserCert
.performClick();
298 if (resultCode
== Activity
.RESULT_OK
&& data
!= null)
300 loadProfile(data
.getData());
308 private void loadProfile(Uri uri
)
312 Bundle args
= new Bundle();
313 args
.putParcelable(PROFILE_URI
, uri
);
314 getLoaderManager().initLoader(PROFILE_LOADER
, args
, mProfileLoaderCallbacks
);
317 public void handleProfile(ProfileLoadResult data
)
322 if (data
!= null && data
.ThrownException
== null)
326 JSONObject obj
= new JSONObject(data
.Profile
);
327 mProfile
= parseProfile(obj
);
329 catch (JSONException e
)
331 mExistsWarning
.setVisibility(View
.VISIBLE
);
332 mExistsWarning
.setText(e
.getLocalizedMessage());
334 invalidateOptionsMenu();
338 if (mProfile
== null)
341 if (data
.ThrownException
!= null)
345 throw data
.ThrownException
;
347 catch (FileNotFoundException e
)
349 error
= getString(R
.string
.profile_import_failed_not_found
);
351 catch (UnknownHostException e
)
353 error
= getString(R
.string
.profile_import_failed_host
);
355 catch (SSLHandshakeException e
)
357 error
= getString(R
.string
.profile_import_failed_tls
);
366 Toast
.makeText(this, getString(R
.string
.profile_import_failed_detail
, error
), Toast
.LENGTH_LONG
).show();
370 Toast
.makeText(this, R
.string
.profile_import_failed
, Toast
.LENGTH_LONG
).show();
375 mExisting
= mDataSource
.getVpnProfile(mProfile
.getUUID());
376 mExistsWarning
.setVisibility(mExisting
!= null ? View
.VISIBLE
: View
.GONE
);
378 mBasicDataGroup
.setVisibility(View
.VISIBLE
);
379 mName
.setText(mProfile
.getName());
380 mGateway
.setText(mProfile
.getGateway());
381 mSelectVpnType
.setText(getResources().getStringArray(R
.array
.vpn_types
)[mProfile
.getVpnType().ordinal()]);
383 mUsernamePassword
.setVisibility(mProfile
.getVpnType().has(VpnTypeFeature
.USER_PASS
) ? View
.VISIBLE
: View
.GONE
);
384 if (mProfile
.getVpnType().has(VpnTypeFeature
.USER_PASS
))
386 mUsername
.setText(mProfile
.getUsername());
387 if (mProfile
.getUsername() != null && !mProfile
.getUsername().isEmpty())
389 mUsername
.setEnabled(false);
393 mUserCertificate
.setVisibility(mProfile
.getVpnType().has(VpnTypeFeature
.CERTIFICATE
) ? View
.VISIBLE
: View
.GONE
);
394 mRemoteCertificate
.setVisibility(mProfile
.Certificate
!= null ? View
.VISIBLE
: View
.GONE
);
395 mImportUserCert
.setVisibility(mProfile
.PKCS12
!= null ? View
.VISIBLE
: View
.GONE
);
397 if (mProfile
.getVpnType().has(VpnTypeFeature
.CERTIFICATE
))
398 { /* try to load an existing certificate with the default name */
399 if (mUserCertLoading
== null)
401 mUserCertLoading
= getString(R
.string
.profile_cert_alias
, mProfile
.getName());
402 getLoaderManager().initLoader(USER_CERT_LOADER
, null, mUserCertificateLoaderCallbacks
);
404 updateUserCertView();
407 if (mProfile
.Certificate
!= null)
411 CertificateFactory factory
= CertificateFactory
.getInstance("X.509");
412 X509Certificate certificate
= (X509Certificate
)factory
.generateCertificate(new ByteArrayInputStream(mProfile
.Certificate
));
413 KeyStore store
= KeyStore
.getInstance("LocalCertificateStore");
414 store
.load(null, null);
415 String alias
= store
.getCertificateAlias(certificate
);
416 mCertEntry
= new TrustedCertificateEntry(alias
, certificate
);
417 ((TextView
)mRemoteCert
.findViewById(android
.R
.id
.text1
)).setText(mCertEntry
.getSubjectPrimary());
418 ((TextView
)mRemoteCert
.findViewById(android
.R
.id
.text2
)).setText(mCertEntry
.getSubjectSecondary());
420 catch (CertificateException
| NoSuchAlgorithmException
| KeyStoreException
| IOException e
)
423 mRemoteCertificate
.setVisibility(View
.GONE
);
428 private void handleUserCertificate(TrustedCertificateEntry data
)
430 mUserCertEntry
= data
;
431 mUserCertLoading
= null;
432 updateUserCertView();
435 private void updateUserCertView()
437 if (mUserCertLoading
!= null)
439 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text1
)).setText(mUserCertLoading
);
440 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text2
)).setText(R
.string
.loading
);
442 else if (mUserCertEntry
!= null)
443 { /* clear any errors and set the new data */
444 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text1
)).setError(null);
445 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text1
)).setText(mUserCertEntry
.getAlias());
446 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text2
)).setText(mUserCertEntry
.getCertificate().getSubjectDN().toString());
450 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text1
)).setText(R
.string
.profile_user_select_certificate_label
);
451 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text2
)).setText(R
.string
.profile_user_select_certificate
);
455 private ParsedVpnProfile
parseProfile(JSONObject obj
) throws JSONException
460 uuid
= UUID
.fromString(obj
.getString("uuid"));
462 catch (IllegalArgumentException e
)
467 ParsedVpnProfile profile
= new ParsedVpnProfile();
470 profile
.setUUID(uuid
);
471 profile
.setName(obj
.getString("name"));
472 VpnType type
= VpnType
.fromIdentifier(obj
.getString("type"));
473 profile
.setVpnType(type
);
475 JSONObject remote
= obj
.getJSONObject("remote");
476 profile
.setGateway(remote
.getString("addr"));
477 profile
.setPort(getInteger(remote
, "port", 1, 65535));
478 profile
.setRemoteId(remote
.optString("id", null));
479 profile
.Certificate
= decodeBase64(remote
.optString("cert", null));
481 if (!remote
.optBoolean("certreq", true))
483 flags
|= VpnProfile
.FLAGS_SUPPRESS_CERT_REQS
;
486 JSONObject revocation
= remote
.optJSONObject("revocation");
487 if (revocation
!= null)
489 if (!revocation
.optBoolean("crl", true))
491 flags
|= VpnProfile
.FLAGS_DISABLE_CRL
;
493 if (!revocation
.optBoolean("ocsp", true))
495 flags
|= VpnProfile
.FLAGS_DISABLE_OCSP
;
497 if (revocation
.optBoolean("strict", false))
499 flags
|= VpnProfile
.FLAGS_STRICT_REVOCATION
;
503 JSONObject local
= obj
.optJSONObject("local");
506 if (type
.has(VpnTypeFeature
.USER_PASS
))
508 profile
.setUsername(local
.optString("eap_id", null));
511 if (type
.has(VpnTypeFeature
.CERTIFICATE
))
513 profile
.setLocalId(local
.optString("id", null));
514 profile
.PKCS12
= decodeBase64(local
.optString("p12", null));
516 if (local
.optBoolean("rsa-pss", false))
518 flags
|= VpnProfile
.FLAGS_RSA_PSS
;
523 profile
.setIkeProposal(getProposal(obj
, "ike-proposal", true));
524 profile
.setEspProposal(getProposal(obj
, "esp-proposal", false));
525 profile
.setMTU(getInteger(obj
, "mtu", Constants
.MTU_MIN
, Constants
.MTU_MAX
));
526 profile
.setNATKeepAlive(getInteger(obj
, "nat-keepalive", Constants
.NAT_KEEPALIVE_MIN
, Constants
.NAT_KEEPALIVE_MAX
));
527 JSONObject split
= obj
.optJSONObject("split-tunneling");
530 String included
= getSubnets(split
, "subnets");
531 profile
.setIncludedSubnets(included
!= null ? included
: null);
532 String excluded
= getSubnets(split
, "excluded");
533 profile
.setExcludedSubnets(excluded
!= null ? excluded
: null);
535 st
|= split
.optBoolean("block-ipv4") ? VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV4
: 0;
536 st
|= split
.optBoolean("block-ipv6") ? VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV6
: 0;
537 profile
.setSplitTunneling(st
== 0 ?
null : st
);
539 /* only one of these can be set, prefer specific apps */
540 String selectedApps
= getApps(obj
.optJSONArray("apps"));
541 String excludedApps
= getApps(obj
.optJSONArray("excluded-apps"));
542 if (!TextUtils
.isEmpty(selectedApps
))
544 profile
.setSelectedApps(selectedApps
);
545 profile
.setSelectedAppsHandling(SelectedAppsHandling
.SELECTED_APPS_ONLY
);
547 else if (!TextUtils
.isEmpty(excludedApps
))
549 profile
.setSelectedApps(excludedApps
);
550 profile
.setSelectedAppsHandling(SelectedAppsHandling
.SELECTED_APPS_EXCLUDE
);
552 profile
.setFlags(flags
);
556 private Integer
getInteger(JSONObject obj
, String key
, int min
, int max
)
558 Integer res
= obj
.optInt(key
);
559 return res
< min
|| res
> max ?
null : res
;
562 private String
getProposal(JSONObject obj
, String key
, boolean ike
) throws JSONException
564 String value
= obj
.optString(key
, null);
565 if (!TextUtils
.isEmpty(value
))
567 if (!Utils
.isProposalValid(ike
, value
))
569 throw new JSONException(getString(R
.string
.profile_import_failed_value
, key
));
575 private String
getSubnets(JSONObject split
, String key
) throws JSONException
577 ArrayList
<String
> subnets
= new ArrayList
<>();
578 JSONArray arr
= split
.optJSONArray(key
);
581 for (int i
= 0; i
< arr
.length(); i
++)
582 { /* replace all spaces, e.g. in "192.168.1.1 - 192.168.1.10" */
583 subnets
.add(arr
.getString(i
).replace(" ", ""));
588 String value
= split
.optString(key
, null);
589 if (!TextUtils
.isEmpty(value
))
594 if (subnets
.size() > 0)
596 String joined
= TextUtils
.join(" ", subnets
);
597 IPRangeSet ranges
= IPRangeSet
.fromString(joined
);
600 throw new JSONException(getString(R
.string
.profile_import_failed_value
,
601 "split-tunneling." + key
));
603 return ranges
.toString();
608 private String
getApps(JSONArray arr
) throws JSONException
610 ArrayList
<String
> apps
= new ArrayList
<>();
613 for (int i
= 0; i
< arr
.length(); i
++)
615 apps
.add(arr
.getString(i
));
618 return TextUtils
.join(" ", apps
);
622 * Save or update the profile depending on whether we actually have a
623 * profile object or not (this was created in updateProfileData)
625 private void saveProfile()
630 if (mExisting
!= null)
632 mProfile
.setId(mExisting
.getId());
633 mDataSource
.updateVpnProfile(mProfile
);
637 mDataSource
.insertProfile(mProfile
);
639 if (mCertEntry
!= null)
642 { /* store the CA/server certificate */
643 KeyStore store
= KeyStore
.getInstance("LocalCertificateStore");
644 store
.load(null, null);
645 store
.setCertificateEntry(null, mCertEntry
.getCertificate());
646 TrustedCertificateManager
.getInstance().reset();
648 catch (KeyStoreException
| CertificateException
| NoSuchAlgorithmException
| IOException e
)
653 Intent intent
= new Intent(Constants
.VPN_PROFILES_CHANGED
);
654 intent
.putExtra(Constants
.VPN_PROFILES_SINGLE
, mProfile
.getId());
655 LocalBroadcastManager
.getInstance(this).sendBroadcast(intent
);
657 intent
= new Intent(this, MainActivity
.class);
658 intent
.setFlags(Intent
.FLAG_ACTIVITY_NEW_TASK
);
659 startActivity(intent
);
661 setResult(RESULT_OK
, new Intent().putExtra(VpnProfileDataSource
.KEY_ID
, mProfile
.getId()));
667 * Verify the user input and display error messages.
668 * @return true if the input is valid
670 private boolean verifyInput()
672 boolean valid
= true;
673 if (mProfile
.getVpnType().has(VpnTypeFeature
.USER_PASS
))
675 if (mUsername
.getText().toString().trim().isEmpty())
677 mUsernameWrap
.setError(getString(R
.string
.alert_text_no_input_username
));
681 if (mProfile
.getVpnType().has(VpnTypeFeature
.CERTIFICATE
) && mUserCertEntry
== null)
682 { /* let's show an error icon */
683 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text1
)).setError("");
690 * Update the profile object with the data entered by the user
692 private void updateProfileData()
694 if (mProfile
.getVpnType().has(VpnTypeFeature
.USER_PASS
))
696 mProfile
.setUsername(mUsername
.getText().toString().trim());
697 String password
= mPassword
.getText().toString().trim();
698 password
= password
.isEmpty() ?
null : password
;
699 mProfile
.setPassword(password
);
701 if (mProfile
.getVpnType().has(VpnTypeFeature
.CERTIFICATE
))
703 mProfile
.setUserCertificateAlias(mUserCertEntry
.getAlias());
705 if (mCertEntry
!= null)
707 mProfile
.setCertificateAlias(mCertEntry
.getAlias());
712 * Load the JSON-encoded VPN profile from the given URI
714 private static class ProfileLoader
extends AsyncTaskLoader
<ProfileLoadResult
>
716 private final Uri mUri
;
717 private ProfileLoadResult mData
;
719 public ProfileLoader(Context context
, Uri uri
)
726 public ProfileLoadResult
loadInBackground()
728 ProfileLoadResult result
= new ProfileLoadResult();
729 InputStream in
= null;
731 if (ContentResolver
.SCHEME_CONTENT
.equals(mUri
.getScheme()) ||
732 ContentResolver
.SCHEME_FILE
.equals(mUri
.getScheme()))
736 in
= getContext().getContentResolver().openInputStream(mUri
);
738 catch (FileNotFoundException e
)
740 result
.ThrownException
= e
;
747 URL url
= new URL(mUri
.toString());
748 in
= url
.openStream();
750 catch (IOException e
)
752 result
.ThrownException
= e
;
759 result
.Profile
= streamToString(in
);
761 catch (OutOfMemoryError e
)
762 { /* just use a generic exception */
763 result
.ThrownException
= new RuntimeException();
770 protected void onStartLoading()
773 { /* if we have data ready, deliver it directly */
774 deliverResult(mData
);
776 if (takeContentChanged() || mData
== null)
783 public void deliverResult(ProfileLoadResult data
)
791 { /* if it is started we deliver the data directly,
792 * otherwise this is handled in onStartLoading */
793 super.deliverResult(data
);
798 protected void onReset()
804 private String
streamToString(InputStream in
)
806 ByteArrayOutputStream out
= new ByteArrayOutputStream();
807 byte[] buf
= new byte[1024];
812 while ((len
= in
.read(buf
)) != -1)
814 out
.write(buf
, 0, len
);
816 return out
.toString("UTF-8");
818 catch (IOException e
)
826 private static class ProfileLoadResult
828 public String Profile
;
829 public Exception ThrownException
;
833 * Ask the user to select an available certificate.
835 private class SelectUserCertOnClickListener
implements View
.OnClickListener
, KeyChainAliasCallback
838 public void onClick(View v
)
841 if (mUserCertEntry
!= null)
843 alias
= mUserCertEntry
.getAlias();
844 mUserCertEntry
= null;
846 else if (mProfile
!= null)
848 alias
= getString(R
.string
.profile_cert_alias
, mProfile
.getName());
850 KeyChain
.choosePrivateKeyAlias(VpnProfileImportActivity
.this, this, new String
[] { "RSA" }, null, null, -1, alias
);
854 public void alias(final String alias
)
856 /* alias() is not called from our main thread */
857 runOnUiThread(new Runnable() {
861 mUserCertLoading
= alias
;
862 updateUserCertView();
864 { /* otherwise the dialog was canceled, the request denied */
865 getLoaderManager().restartLoader(USER_CERT_LOADER
, null, mUserCertificateLoaderCallbacks
);
873 * Load the selected user certificate asynchronously. This cannot be done
874 * from the main thread as getCertificateChain() calls back to our main
875 * thread to bind to the KeyChain service resulting in a deadlock.
877 private static class UserCertificateLoader
extends AsyncTaskLoader
<TrustedCertificateEntry
>
879 private final String mAlias
;
880 private TrustedCertificateEntry mData
;
882 public UserCertificateLoader(Context context
, String alias
)
889 public TrustedCertificateEntry
loadInBackground()
891 X509Certificate
[] chain
= null;
894 chain
= KeyChain
.getCertificateChain(getContext(), mAlias
);
896 catch (KeyChainException
| InterruptedException e
)
900 if (chain
!= null && chain
.length
> 0)
902 return new TrustedCertificateEntry(mAlias
, chain
[0]);
908 protected void onStartLoading()
911 { /* if we have data ready, deliver it directly */
912 deliverResult(mData
);
914 if (takeContentChanged() || mData
== null)
921 public void deliverResult(TrustedCertificateEntry data
)
929 { /* if it is started we deliver the data directly,
930 * otherwise this is handled in onStartLoading */
931 super.deliverResult(data
);
936 protected void onReset()
943 private byte[] decodeBase64(String encoded
)
945 if (encoded
== null || encoded
.isEmpty())
952 data
= Base64
.decode(encoded
, Base64
.DEFAULT
);
954 catch (IllegalArgumentException e
)
961 private class ParsedVpnProfile
extends VpnProfile
963 public byte[] Certificate
;
964 public byte[] PKCS12
;