2 * Copyright (C) 2014 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.annotation.TargetApi;
19 import android.app.Activity;
20 import android.app.Dialog;
21 import android.content.ActivityNotFoundException;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.net.Uri;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.widget.Toast;
29 import org.strongswan.android.R;
30 import org.strongswan.android.data.VpnProfileDataSource;
31 import org.strongswan.android.logic.TrustedCertificateManager;
33 import java.io.FileNotFoundException;
34 import java.io.InputStream;
35 import java.security.KeyStore;
36 import java.security.cert.CertificateException;
37 import java.security.cert.CertificateFactory;
38 import java.security.cert.X509Certificate;
40 import androidx.appcompat.app.AlertDialog;
41 import androidx.appcompat.app.AppCompatActivity;
42 import androidx.appcompat.app.AppCompatDialogFragment;
43 import androidx.fragment.app.FragmentTransaction;
45 public class TrustedCertificateImportActivity extends AppCompatActivity
47 private static final int OPEN_DOCUMENT = 0;
48 private static final String DIALOG_TAG = "Dialog";
49 private Uri mCertificateUri;
51 @TargetApi(Build.VERSION_CODES.KITKAT)
53 public void onCreate(Bundle savedInstanceState)
55 super.onCreate(savedInstanceState);
57 if (savedInstanceState != null)
58 { /* do nothing when we are restoring */
62 Intent intent = getIntent();
63 String action = intent.getAction();
64 if (Intent.ACTION_VIEW.equals(action))
66 importCertificate(intent.getData());
68 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
70 Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
71 openIntent.setType("*/*");
74 startActivityForResult(openIntent, OPEN_DOCUMENT);
76 catch (ActivityNotFoundException e)
77 { /* some devices are unable to browse for files */
85 protected void onActivityResult(int requestCode, int resultCode, Intent data)
87 super.onActivityResult(requestCode, resultCode, data);
91 if (resultCode == Activity.RESULT_OK && data != null)
93 mCertificateUri = data.getData();
102 protected void onPostResume()
104 super.onPostResume();
105 if (mCertificateUri != null)
107 importCertificate(mCertificateUri);
108 mCertificateUri = null;
113 * Import the file pointed to by the given URI as a certificate.
117 private void importCertificate(Uri uri)
119 X509Certificate certificate = parseCertificate(uri);
120 if (certificate == null)
122 Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show();
126 /* Ask the user whether to import the certificate. This is particularly
127 * necessary because the import activity can be triggered by any app on
128 * the system. Also, if our app is the only one that is registered to
129 * open certificate files by MIME type the user would have no idea really
130 * where the file was imported just by reading the Toast we display. */
131 ConfirmImportDialog dialog = new ConfirmImportDialog();
132 Bundle args = new Bundle();
133 args.putSerializable(VpnProfileDataSource.KEY_CERTIFICATE, certificate);
134 dialog.setArguments(args);
135 FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
136 ft.add(dialog, DIALOG_TAG);
141 * Load the file from the given URI and try to parse it as X.509 certificate.
144 * @return certificate or null
146 private X509Certificate parseCertificate(Uri uri)
148 X509Certificate certificate = null;
151 CertificateFactory factory = CertificateFactory.getInstance("X.509");
152 InputStream in = getContentResolver().openInputStream(uri);
153 certificate = (X509Certificate)factory.generateCertificate(in);
154 /* we don't check whether it's actually a CA certificate or not */
156 catch (CertificateException e)
160 catch (FileNotFoundException e)
169 * Try to store the given certificate in the KeyStore.
172 * @return whether it was successfully stored
174 private boolean storeCertificate(X509Certificate certificate)
178 KeyStore store = KeyStore.getInstance("LocalCertificateStore");
179 store.load(null, null);
180 store.setCertificateEntry(null, certificate);
181 TrustedCertificateManager.getInstance().reset();
192 * Class that displays a confirmation dialog when a certificate should get
193 * imported. If the user confirms the import we try to store it.
195 public static class ConfirmImportDialog extends AppCompatDialogFragment
198 public Dialog onCreateDialog(Bundle savedInstanceState)
200 final X509Certificate certificate;
202 certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE);
204 return new AlertDialog.Builder(getActivity())
205 .setIcon(R.drawable.ic_launcher)
206 .setTitle(R.string.import_certificate)
207 .setMessage(certificate.getSubjectDN().toString())
208 .setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener()
211 public void onClick(DialogInterface dialog, int whichButton)
213 TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity();
214 if (activity.storeCertificate(certificate))
216 Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show();
217 getActivity().setResult(Activity.RESULT_OK);
221 Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show();
223 getActivity().finish();
226 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
229 public void onClick(DialogInterface dialog, int which)
231 getActivity().finish();
237 public void onCancel(DialogInterface dialog)
239 getActivity().finish();