6b8eb2ee8a44bba75945c4db820452c3c5c5a195
[strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / ui / TrustedCertificateImportActivity.java
1 /*
2 * Copyright (C) 2014 Tobias Brunner
3 * Hochschule fuer Technik Rapperswil
4 *
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>.
9 *
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
13 * for more details.
14 */
15
16 package org.strongswan.android.ui;
17
18 import android.annotation.TargetApi;
19 import android.app.Activity;
20 import android.app.Dialog;
21 import android.content.DialogInterface;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.os.Build;
25 import android.os.Bundle;
26 import android.support.v4.app.FragmentTransaction;
27 import android.support.v7.app.AlertDialog;
28 import android.support.v7.app.AppCompatActivity;
29 import android.support.v7.app.AppCompatDialogFragment;
30 import android.widget.Toast;
31
32 import org.strongswan.android.R;
33 import org.strongswan.android.data.VpnProfileDataSource;
34 import org.strongswan.android.logic.TrustedCertificateManager;
35
36 import java.io.FileNotFoundException;
37 import java.io.InputStream;
38 import java.security.KeyStore;
39 import java.security.cert.CertificateException;
40 import java.security.cert.CertificateFactory;
41 import java.security.cert.X509Certificate;
42
43 public class TrustedCertificateImportActivity extends AppCompatActivity
44 {
45 private static final int OPEN_DOCUMENT = 0;
46 private static final String DIALOG_TAG = "Dialog";
47
48 /* same as those listed in the manifest */
49 private static final String[] ACCEPTED_MIME_TYPES = {
50 "application/x-x509-ca-cert",
51 "application/x-x509-server-cert",
52 "application/x-pem-file",
53 "application/pkix-cert"
54 };
55
56 @TargetApi(Build.VERSION_CODES.KITKAT)
57 @Override
58 public void onCreate(Bundle savedInstanceState)
59 {
60 super.onCreate(savedInstanceState);
61
62 if (savedInstanceState != null)
63 { /* do nothing when we are restoring */
64 return;
65 }
66
67 Intent intent = getIntent();
68 String action = intent.getAction();
69 if (Intent.ACTION_VIEW.equals(action))
70 {
71 importCertificate(intent.getData());
72 }
73 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
74 {
75 Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
76 openIntent.setType("*/*");
77 openIntent.putExtra(Intent.EXTRA_MIME_TYPES, ACCEPTED_MIME_TYPES);
78 startActivityForResult(openIntent, OPEN_DOCUMENT);
79 }
80 }
81
82 @Override
83 protected void onActivityResult(int requestCode, int resultCode, Intent data)
84 {
85 switch (requestCode)
86 {
87 case OPEN_DOCUMENT:
88 if (resultCode == Activity.RESULT_OK && data != null)
89 {
90 importCertificate(data.getData());
91 return;
92 }
93 finish();
94 return;
95 }
96 super.onActivityResult(requestCode, resultCode, data);
97 }
98
99 /**
100 * Import the file pointed to by the given URI as a certificate.
101 *
102 * @param uri
103 */
104 private void importCertificate(Uri uri)
105 {
106 X509Certificate certificate = parseCertificate(uri);
107 if (certificate == null)
108 {
109 Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show();
110 finish();
111 return;
112 }
113 /* Ask the user whether to import the certificate. This is particularly
114 * necessary because the import activity can be triggered by any app on
115 * the system. Also, if our app is the only one that is registered to
116 * open certificate files by MIME type the user would have no idea really
117 * where the file was imported just by reading the Toast we display. */
118 ConfirmImportDialog dialog = new ConfirmImportDialog();
119 Bundle args = new Bundle();
120 args.putSerializable(VpnProfileDataSource.KEY_CERTIFICATE, certificate);
121 dialog.setArguments(args);
122 FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
123 ft.add(dialog, DIALOG_TAG);
124 ft.commit();
125 }
126
127 /**
128 * Load the file from the given URI and try to parse it as X.509 certificate.
129 *
130 * @param uri
131 * @return certificate or null
132 */
133 private X509Certificate parseCertificate(Uri uri)
134 {
135 X509Certificate certificate = null;
136 try
137 {
138 CertificateFactory factory = CertificateFactory.getInstance("X.509");
139 InputStream in = getContentResolver().openInputStream(uri);
140 certificate = (X509Certificate)factory.generateCertificate(in);
141 /* we don't check whether it's actually a CA certificate or not */
142 }
143 catch (CertificateException e)
144 {
145 e.printStackTrace();
146 }
147 catch (FileNotFoundException e)
148 {
149 e.printStackTrace();
150 }
151 return certificate;
152 }
153
154
155 /**
156 * Try to store the given certificate in the KeyStore.
157 *
158 * @param certificate
159 * @return whether it was successfully stored
160 */
161 private boolean storeCertificate(X509Certificate certificate)
162 {
163 try
164 {
165 KeyStore store = KeyStore.getInstance("LocalCertificateStore");
166 store.load(null, null);
167 store.setCertificateEntry(null, certificate);
168 TrustedCertificateManager.getInstance().reset();
169 return true;
170 }
171 catch (Exception e)
172 {
173 e.printStackTrace();
174 return false;
175 }
176 }
177
178 /**
179 * Class that displays a confirmation dialog when a certificate should get
180 * imported. If the user confirms the import we try to store it.
181 */
182 public static class ConfirmImportDialog extends AppCompatDialogFragment
183 {
184 @Override
185 public Dialog onCreateDialog(Bundle savedInstanceState)
186 {
187 final X509Certificate certificate;
188
189 certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE);
190
191 return new AlertDialog.Builder(getActivity())
192 .setIcon(R.drawable.ic_launcher)
193 .setTitle(R.string.import_certificate)
194 .setMessage(certificate.getSubjectDN().toString())
195 .setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener()
196 {
197 @Override
198 public void onClick(DialogInterface dialog, int whichButton)
199 {
200 TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity();
201 if (activity.storeCertificate(certificate))
202 {
203 Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show();
204 getActivity().setResult(Activity.RESULT_OK);
205 }
206 else
207 {
208 Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show();
209 }
210 getActivity().finish();
211 }
212 })
213 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
214 {
215 @Override
216 public void onClick(DialogInterface dialog, int which)
217 {
218 getActivity().finish();
219 }
220 }).create();
221 }
222
223 @Override
224 public void onCancel(DialogInterface dialog)
225 {
226 getActivity().finish();
227 }
228 }
229 }