2 * Copyright (C) 2016-2017 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
.app
.ProgressDialog
;
21 import android
.content
.AsyncTaskLoader
;
22 import android
.content
.ContentResolver
;
23 import android
.content
.Context
;
24 import android
.content
.DialogInterface
;
25 import android
.content
.Intent
;
26 import android
.content
.Loader
;
27 import android
.net
.Uri
;
28 import android
.os
.Build
;
29 import android
.os
.Bundle
;
30 import android
.security
.KeyChain
;
31 import android
.security
.KeyChainAliasCallback
;
32 import android
.security
.KeyChainException
;
33 import android
.support
.v4
.content
.LocalBroadcastManager
;
34 import android
.support
.v7
.app
.AppCompatActivity
;
35 import android
.text
.TextUtils
;
36 import android
.util
.Base64
;
37 import android
.view
.Menu
;
38 import android
.view
.MenuInflater
;
39 import android
.view
.MenuItem
;
40 import android
.view
.View
;
41 import android
.view
.ViewGroup
;
42 import android
.widget
.Button
;
43 import android
.widget
.EditText
;
44 import android
.widget
.RelativeLayout
;
45 import android
.widget
.TextView
;
46 import android
.widget
.Toast
;
48 import org
.json
.JSONArray
;
49 import org
.json
.JSONException
;
50 import org
.json
.JSONObject
;
51 import org
.strongswan
.android
.R
;
52 import org
.strongswan
.android
.data
.VpnProfile
;
53 import org
.strongswan
.android
.data
.VpnProfile
.SelectedAppsHandling
;
54 import org
.strongswan
.android
.data
.VpnProfileDataSource
;
55 import org
.strongswan
.android
.data
.VpnType
;
56 import org
.strongswan
.android
.data
.VpnType
.VpnTypeFeature
;
57 import org
.strongswan
.android
.logic
.TrustedCertificateManager
;
58 import org
.strongswan
.android
.security
.TrustedCertificateEntry
;
59 import org
.strongswan
.android
.ui
.widget
.TextInputLayoutHelper
;
60 import org
.strongswan
.android
.utils
.Constants
;
61 import org
.strongswan
.android
.utils
.IPRangeSet
;
62 import org
.strongswan
.android
.utils
.Utils
;
64 import java
.io
.ByteArrayInputStream
;
65 import java
.io
.ByteArrayOutputStream
;
66 import java
.io
.FileNotFoundException
;
67 import java
.io
.IOException
;
68 import java
.io
.InputStream
;
69 import java
.lang
.OutOfMemoryError
;
71 import java
.net
.UnknownHostException
;
72 import java
.security
.KeyStore
;
73 import java
.security
.KeyStoreException
;
74 import java
.security
.NoSuchAlgorithmException
;
75 import java
.security
.cert
.CertificateException
;
76 import java
.security
.cert
.CertificateFactory
;
77 import java
.security
.cert
.X509Certificate
;
78 import java
.util
.ArrayList
;
79 import java
.util
.UUID
;
81 import javax
.net
.ssl
.SSLHandshakeException
;
83 public class VpnProfileImportActivity
extends AppCompatActivity
85 private static final String PKCS12_INSTALLED
= "PKCS12_INSTALLED";
86 private static final String PROFILE_URI
= "PROFILE_URI";
87 private static final int INSTALL_PKCS12
= 0;
88 private static final int OPEN_DOCUMENT
= 1;
89 private static final int PROFILE_LOADER
= 0;
90 private static final int USER_CERT_LOADER
= 1;
92 private VpnProfileDataSource mDataSource
;
93 private ParsedVpnProfile mProfile
;
94 private VpnProfile mExisting
;
95 private TrustedCertificateEntry mCertEntry
;
96 private TrustedCertificateEntry mUserCertEntry
;
97 private String mUserCertLoading
;
98 private boolean mHideImport
;
99 private ProgressDialog mProgress
;
100 private TextView mExistsWarning
;
101 private ViewGroup mBasicDataGroup
;
102 private TextView mName
;
103 private TextView mGateway
;
104 private TextView mSelectVpnType
;
105 private ViewGroup mUsernamePassword
;
106 private EditText mUsername
;
107 private TextInputLayoutHelper mUsernameWrap
;
108 private EditText mPassword
;
109 private ViewGroup mUserCertificate
;
110 private RelativeLayout mSelectUserCert
;
111 private Button mImportUserCert
;
112 private ViewGroup mRemoteCertificate
;
113 private RelativeLayout mRemoteCert
;
115 private LoaderManager
.LoaderCallbacks
<ProfileLoadResult
> mProfileLoaderCallbacks
= new LoaderManager
.LoaderCallbacks
<ProfileLoadResult
>()
118 public Loader
<ProfileLoadResult
> onCreateLoader(int id
, Bundle args
)
120 return new ProfileLoader(VpnProfileImportActivity
.this, (Uri
)args
.getParcelable(PROFILE_URI
));
124 public void onLoadFinished(Loader
<ProfileLoadResult
> loader
, ProfileLoadResult data
)
130 public void onLoaderReset(Loader
<ProfileLoadResult
> loader
)
136 private LoaderManager
.LoaderCallbacks
<TrustedCertificateEntry
> mUserCertificateLoaderCallbacks
= new LoaderManager
.LoaderCallbacks
<TrustedCertificateEntry
>()
139 public Loader
<TrustedCertificateEntry
> onCreateLoader(int id
, Bundle args
)
141 return new UserCertificateLoader(VpnProfileImportActivity
.this, mUserCertLoading
);
145 public void onLoadFinished(Loader
<TrustedCertificateEntry
> loader
, TrustedCertificateEntry data
)
147 handleUserCertificate(data
);
151 public void onLoaderReset(Loader
<TrustedCertificateEntry
> loader
)
158 public void onCreate(Bundle savedInstanceState
)
160 super.onCreate(savedInstanceState
);
162 getSupportActionBar().setHomeAsUpIndicator(R
.drawable
.ic_close_white_24dp
);
163 getSupportActionBar().setDisplayHomeAsUpEnabled(true);
165 mDataSource
= new VpnProfileDataSource(this);
168 setContentView(R
.layout
.profile_import_view
);
170 mExistsWarning
= (TextView
)findViewById(R
.id
.exists_warning
);
171 mBasicDataGroup
= (ViewGroup
)findViewById(R
.id
.basic_data_group
);
172 mName
= (TextView
)findViewById(R
.id
.name
);
173 mGateway
= (TextView
)findViewById(R
.id
.gateway
);
174 mSelectVpnType
= (TextView
)findViewById(R
.id
.vpn_type
);
176 mUsernamePassword
= (ViewGroup
)findViewById(R
.id
.username_password_group
);
177 mUsername
= (EditText
)findViewById(R
.id
.username
);
178 mUsernameWrap
= (TextInputLayoutHelper
) findViewById(R
.id
.username_wrap
);
179 mPassword
= (EditText
)findViewById(R
.id
.password
);
181 mUserCertificate
= (ViewGroup
)findViewById(R
.id
.user_certificate_group
);
182 mSelectUserCert
= (RelativeLayout
)findViewById(R
.id
.select_user_certificate
);
183 mImportUserCert
= (Button
)findViewById(R
.id
.import_user_certificate
);
185 mRemoteCertificate
= (ViewGroup
)findViewById(R
.id
.remote_certificate_group
);
186 mRemoteCert
= (RelativeLayout
)findViewById(R
.id
.remote_certificate
);
188 mExistsWarning
.setVisibility(View
.GONE
);
189 mBasicDataGroup
.setVisibility(View
.GONE
);
190 mUsernamePassword
.setVisibility(View
.GONE
);
191 mUserCertificate
.setVisibility(View
.GONE
);
192 mRemoteCertificate
.setVisibility(View
.GONE
);
194 mSelectUserCert
.setOnClickListener(new SelectUserCertOnClickListener());
195 mImportUserCert
.setOnClickListener(new View
.OnClickListener() {
197 public void onClick(View v
)
199 Intent intent
= KeyChain
.createInstallIntent();
200 intent
.putExtra(KeyChain
.EXTRA_NAME
, getString(R
.string
.profile_cert_alias
, mProfile
.getName()));
201 intent
.putExtra(KeyChain
.EXTRA_PKCS12
, mProfile
.PKCS12
);
202 startActivityForResult(intent
, INSTALL_PKCS12
);
206 Intent intent
= getIntent();
207 String action
= intent
.getAction();
208 if (Intent
.ACTION_VIEW
.equals(action
))
210 loadProfile(getIntent().getData());
212 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.KITKAT
)
214 Intent openIntent
= new Intent(Intent
.ACTION_OPEN_DOCUMENT
);
215 openIntent
.setType("*/*");
216 startActivityForResult(openIntent
, OPEN_DOCUMENT
);
219 if (savedInstanceState
!= null)
221 mUserCertLoading
= savedInstanceState
.getString(VpnProfileDataSource
.KEY_USER_CERTIFICATE
);
222 if (mUserCertLoading
!= null)
224 getLoaderManager().initLoader(USER_CERT_LOADER
, null, mUserCertificateLoaderCallbacks
);
226 mImportUserCert
.setEnabled(!savedInstanceState
.getBoolean(PKCS12_INSTALLED
));
231 protected void onDestroy()
238 protected void onSaveInstanceState(Bundle outState
)
240 super.onSaveInstanceState(outState
);
241 if (mUserCertEntry
!= null)
243 outState
.putString(VpnProfileDataSource
.KEY_USER_CERTIFICATE
, mUserCertEntry
.getAlias());
245 outState
.putBoolean(PKCS12_INSTALLED
, !mImportUserCert
.isEnabled());
249 public boolean onCreateOptionsMenu(Menu menu
)
251 MenuInflater inflater
= getMenuInflater();
252 inflater
.inflate(R
.menu
.profile_import
, menu
);
255 MenuItem item
= menu
.findItem(R
.id
.menu_accept
);
256 item
.setVisible(false);
262 public boolean onOptionsItemSelected(MenuItem item
)
264 switch (item
.getItemId())
266 case android
.R
.id
.home
:
269 case R
.id
.menu_accept
:
273 return super.onOptionsItemSelected(item
);
278 protected void onActivityResult(int requestCode
, int resultCode
, Intent data
)
280 super.onActivityResult(requestCode
, resultCode
, data
);
284 if (resultCode
== Activity
.RESULT_OK
)
285 { /* no need to import twice */
286 mImportUserCert
.setEnabled(false);
287 mSelectUserCert
.performClick();
291 if (resultCode
== Activity
.RESULT_OK
&& data
!= null)
293 loadProfile(data
.getData());
301 private void loadProfile(Uri uri
)
303 mProgress
= ProgressDialog
.show(this, null, getString(R
.string
.loading
),
304 true, true, new DialogInterface
.OnCancelListener() {
306 public void onCancel(DialogInterface dialog
)
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", false))
483 flags
|= VpnProfile
.FLAGS_SUPPRESS_CERT_REQS
;
486 JSONObject local
= obj
.optJSONObject("local");
489 if (type
.has(VpnTypeFeature
.USER_PASS
))
491 profile
.setUsername(local
.optString("eap_id", null));
494 if (type
.has(VpnTypeFeature
.CERTIFICATE
))
496 profile
.setLocalId(local
.optString("id", null));
497 profile
.PKCS12
= decodeBase64(local
.optString("p12", null));
501 profile
.setIkeProposal(getProposal(obj
, "ike-proposal", true));
502 profile
.setEspProposal(getProposal(obj
, "esp-proposal", false));
503 profile
.setMTU(getInteger(obj
, "mtu", Constants
.MTU_MIN
, Constants
.MTU_MAX
));
504 profile
.setNATKeepAlive(getInteger(obj
, "nat-keepalive", Constants
.NAT_KEEPALIVE_MIN
, Constants
.NAT_KEEPALIVE_MAX
));
505 JSONObject split
= obj
.optJSONObject("split-tunneling");
508 String included
= getSubnets(split
, "subnets");
509 profile
.setIncludedSubnets(included
!= null ? included
: null);
510 String excluded
= getSubnets(split
, "excluded");
511 profile
.setExcludedSubnets(excluded
!= null ? excluded
: null);
513 st
|= split
.optBoolean("block-ipv4") ? VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV4
: 0;
514 st
|= split
.optBoolean("block-ipv6") ? VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV6
: 0;
515 profile
.setSplitTunneling(st
== 0 ?
null : st
);
517 /* only one of these can be set, prefer specific apps */
518 String selectedApps
= getApps(obj
.optJSONArray("apps"));
519 String excludedApps
= getApps(obj
.optJSONArray("excluded-apps"));
520 if (!TextUtils
.isEmpty(selectedApps
))
522 profile
.setSelectedApps(selectedApps
);
523 profile
.setSelectedAppsHandling(SelectedAppsHandling
.SELECTED_APPS_ONLY
);
525 else if (!TextUtils
.isEmpty(excludedApps
))
527 profile
.setSelectedApps(excludedApps
);
528 profile
.setSelectedAppsHandling(SelectedAppsHandling
.SELECTED_APPS_EXCLUDE
);
530 profile
.setFlags(flags
);
534 private Integer
getInteger(JSONObject obj
, String key
, int min
, int max
)
536 Integer res
= obj
.optInt(key
);
537 return res
< min
|| res
> max ?
null : res
;
540 private String
getProposal(JSONObject obj
, String key
, boolean ike
) throws JSONException
542 String value
= obj
.optString(key
, null);
543 if (!TextUtils
.isEmpty(value
))
545 if (!Utils
.isProposalValid(ike
, value
))
547 throw new JSONException(getString(R
.string
.profile_import_failed_value
, key
));
553 private String
getSubnets(JSONObject split
, String key
) throws JSONException
555 ArrayList
<String
> subnets
= new ArrayList
<>();
556 JSONArray arr
= split
.optJSONArray(key
);
559 for (int i
= 0; i
< arr
.length(); i
++)
560 { /* replace all spaces, e.g. in "192.168.1.1 - 192.168.1.10" */
561 subnets
.add(arr
.getString(i
).replace(" ", ""));
566 String value
= split
.optString(key
, null);
567 if (!TextUtils
.isEmpty(value
))
572 if (subnets
.size() > 0)
574 String joined
= TextUtils
.join(" ", subnets
);
575 IPRangeSet ranges
= IPRangeSet
.fromString(joined
);
578 throw new JSONException(getString(R
.string
.profile_import_failed_value
,
579 "split-tunneling." + key
));
581 return ranges
.toString();
586 private String
getApps(JSONArray arr
) throws JSONException
588 ArrayList
<String
> apps
= new ArrayList
<>();
591 for (int i
= 0; i
< arr
.length(); i
++)
593 apps
.add(arr
.getString(i
));
596 return TextUtils
.join(" ", apps
);
600 * Save or update the profile depending on whether we actually have a
601 * profile object or not (this was created in updateProfileData)
603 private void saveProfile()
608 if (mExisting
!= null)
610 mProfile
.setId(mExisting
.getId());
611 mDataSource
.updateVpnProfile(mProfile
);
615 mDataSource
.insertProfile(mProfile
);
617 if (mCertEntry
!= null)
620 { /* store the CA/server certificate */
621 KeyStore store
= KeyStore
.getInstance("LocalCertificateStore");
622 store
.load(null, null);
623 store
.setCertificateEntry(null, mCertEntry
.getCertificate());
624 TrustedCertificateManager
.getInstance().reset();
626 catch (KeyStoreException
| CertificateException
| NoSuchAlgorithmException
| IOException e
)
631 Intent intent
= new Intent(Constants
.VPN_PROFILES_CHANGED
);
632 intent
.putExtra(Constants
.VPN_PROFILES_SINGLE
, mProfile
.getId());
633 LocalBroadcastManager
.getInstance(this).sendBroadcast(intent
);
635 intent
= new Intent(this, MainActivity
.class);
636 intent
.setFlags(Intent
.FLAG_ACTIVITY_NEW_TASK
);
637 startActivity(intent
);
639 setResult(RESULT_OK
, new Intent().putExtra(VpnProfileDataSource
.KEY_ID
, mProfile
.getId()));
645 * Verify the user input and display error messages.
646 * @return true if the input is valid
648 private boolean verifyInput()
650 boolean valid
= true;
651 if (mProfile
.getVpnType().has(VpnTypeFeature
.USER_PASS
))
653 if (mUsername
.getText().toString().trim().isEmpty())
655 mUsernameWrap
.setError(getString(R
.string
.alert_text_no_input_username
));
659 if (mProfile
.getVpnType().has(VpnTypeFeature
.CERTIFICATE
) && mUserCertEntry
== null)
660 { /* let's show an error icon */
661 ((TextView
)mSelectUserCert
.findViewById(android
.R
.id
.text1
)).setError("");
668 * Update the profile object with the data entered by the user
670 private void updateProfileData()
672 if (mProfile
.getVpnType().has(VpnTypeFeature
.USER_PASS
))
674 mProfile
.setUsername(mUsername
.getText().toString().trim());
675 String password
= mPassword
.getText().toString().trim();
676 password
= password
.isEmpty() ?
null : password
;
677 mProfile
.setPassword(password
);
679 if (mProfile
.getVpnType().has(VpnTypeFeature
.CERTIFICATE
))
681 mProfile
.setUserCertificateAlias(mUserCertEntry
.getAlias());
683 if (mCertEntry
!= null)
685 mProfile
.setCertificateAlias(mCertEntry
.getAlias());
690 * Load the JSON-encoded VPN profile from the given URI
692 private static class ProfileLoader
extends AsyncTaskLoader
<ProfileLoadResult
>
694 private final Uri mUri
;
695 private ProfileLoadResult mData
;
697 public ProfileLoader(Context context
, Uri uri
)
704 public ProfileLoadResult
loadInBackground()
706 ProfileLoadResult result
= new ProfileLoadResult();
707 InputStream in
= null;
709 if (ContentResolver
.SCHEME_CONTENT
.equals(mUri
.getScheme()) ||
710 ContentResolver
.SCHEME_FILE
.equals(mUri
.getScheme()))
714 in
= getContext().getContentResolver().openInputStream(mUri
);
716 catch (FileNotFoundException e
)
718 result
.ThrownException
= e
;
725 URL url
= new URL(mUri
.toString());
726 in
= url
.openStream();
728 catch (IOException e
)
730 result
.ThrownException
= e
;
737 result
.Profile
= streamToString(in
);
739 catch (OutOfMemoryError e
)
740 { /* just use a generic exception */
741 result
.ThrownException
= new RuntimeException();
748 protected void onStartLoading()
751 { /* if we have data ready, deliver it directly */
752 deliverResult(mData
);
754 if (takeContentChanged() || mData
== null)
761 public void deliverResult(ProfileLoadResult data
)
769 { /* if it is started we deliver the data directly,
770 * otherwise this is handled in onStartLoading */
771 super.deliverResult(data
);
776 protected void onReset()
782 private String
streamToString(InputStream in
)
784 ByteArrayOutputStream out
= new ByteArrayOutputStream();
785 byte[] buf
= new byte[1024];
790 while ((len
= in
.read(buf
)) != -1)
792 out
.write(buf
, 0, len
);
794 return out
.toString("UTF-8");
796 catch (IOException e
)
804 private static class ProfileLoadResult
806 public String Profile
;
807 public Exception ThrownException
;
811 * Ask the user to select an available certificate.
813 private class SelectUserCertOnClickListener
implements View
.OnClickListener
, KeyChainAliasCallback
816 public void onClick(View v
)
819 if (mUserCertEntry
!= null)
821 alias
= mUserCertEntry
.getAlias();
822 mUserCertEntry
= null;
824 else if (mProfile
!= null)
826 alias
= getString(R
.string
.profile_cert_alias
, mProfile
.getName());
828 KeyChain
.choosePrivateKeyAlias(VpnProfileImportActivity
.this, this, new String
[] { "RSA" }, null, null, -1, alias
);
832 public void alias(final String alias
)
834 /* alias() is not called from our main thread */
835 runOnUiThread(new Runnable() {
839 mUserCertLoading
= alias
;
840 updateUserCertView();
842 { /* otherwise the dialog was canceled, the request denied */
843 getLoaderManager().restartLoader(USER_CERT_LOADER
, null, mUserCertificateLoaderCallbacks
);
851 * Load the selected user certificate asynchronously. This cannot be done
852 * from the main thread as getCertificateChain() calls back to our main
853 * thread to bind to the KeyChain service resulting in a deadlock.
855 private static class UserCertificateLoader
extends AsyncTaskLoader
<TrustedCertificateEntry
>
857 private final String mAlias
;
858 private TrustedCertificateEntry mData
;
860 public UserCertificateLoader(Context context
, String alias
)
867 public TrustedCertificateEntry
loadInBackground()
869 X509Certificate
[] chain
= null;
872 chain
= KeyChain
.getCertificateChain(getContext(), mAlias
);
874 catch (KeyChainException
| InterruptedException e
)
878 if (chain
!= null && chain
.length
> 0)
880 return new TrustedCertificateEntry(mAlias
, chain
[0]);
886 protected void onStartLoading()
889 { /* if we have data ready, deliver it directly */
890 deliverResult(mData
);
892 if (takeContentChanged() || mData
== null)
899 public void deliverResult(TrustedCertificateEntry data
)
907 { /* if it is started we deliver the data directly,
908 * otherwise this is handled in onStartLoading */
909 super.deliverResult(data
);
914 protected void onReset()
921 private byte[] decodeBase64(String encoded
)
923 if (encoded
== null || encoded
.isEmpty())
930 data
= Base64
.decode(encoded
, Base64
.DEFAULT
);
932 catch (IllegalArgumentException e
)
939 private class ParsedVpnProfile
extends VpnProfile
941 public byte[] Certificate
;
942 public byte[] PKCS12
;