]> git.ipfire.org Git - thirdparty/strongswan.git/blob
4581d44274a4731461b644742646db19b4785e06
[thirdparty/strongswan.git] /
1 /*
2 * Copyright (C) 2014 Tobias Brunner
3 * HSR 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.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;
28
29 import org.strongswan.android.R;
30 import org.strongswan.android.data.VpnProfileDataSource;
31 import org.strongswan.android.logic.TrustedCertificateManager;
32
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;
39
40 import androidx.appcompat.app.AlertDialog;
41 import androidx.appcompat.app.AppCompatActivity;
42 import androidx.appcompat.app.AppCompatDialogFragment;
43 import androidx.fragment.app.FragmentTransaction;
44
45 public class TrustedCertificateImportActivity extends AppCompatActivity
46 {
47 private static final int OPEN_DOCUMENT = 0;
48 private static final String DIALOG_TAG = "Dialog";
49 private Uri mCertificateUri;
50
51 @TargetApi(Build.VERSION_CODES.KITKAT)
52 @Override
53 public void onCreate(Bundle savedInstanceState)
54 {
55 super.onCreate(savedInstanceState);
56
57 if (savedInstanceState != null)
58 { /* do nothing when we are restoring */
59 return;
60 }
61
62 Intent intent = getIntent();
63 String action = intent.getAction();
64 if (Intent.ACTION_VIEW.equals(action))
65 {
66 importCertificate(intent.getData());
67 }
68 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
69 {
70 Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
71 openIntent.setType("*/*");
72 try
73 {
74 startActivityForResult(openIntent, OPEN_DOCUMENT);
75 }
76 catch (ActivityNotFoundException e)
77 { /* some devices are unable to browse for files */
78 finish();
79 return;
80 }
81 }
82 }
83
84 @Override
85 protected void onActivityResult(int requestCode, int resultCode, Intent data)
86 {
87 super.onActivityResult(requestCode, resultCode, data);
88 switch (requestCode)
89 {
90 case OPEN_DOCUMENT:
91 if (resultCode == Activity.RESULT_OK && data != null)
92 {
93 mCertificateUri = data.getData();
94 return;
95 }
96 finish();
97 return;
98 }
99 }
100
101 @Override
102 protected void onPostResume()
103 {
104 super.onPostResume();
105 if (mCertificateUri != null)
106 {
107 importCertificate(mCertificateUri);
108 mCertificateUri = null;
109 }
110 }
111
112 /**
113 * Import the file pointed to by the given URI as a certificate.
114 *
115 * @param uri
116 */
117 private void importCertificate(Uri uri)
118 {
119 X509Certificate certificate = parseCertificate(uri);
120 if (certificate == null)
121 {
122 Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show();
123 finish();
124 return;
125 }
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);
137 ft.commit();
138 }
139
140 /**
141 * Load the file from the given URI and try to parse it as X.509 certificate.
142 *
143 * @param uri
144 * @return certificate or null
145 */
146 private X509Certificate parseCertificate(Uri uri)
147 {
148 X509Certificate certificate = null;
149 try
150 {
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 */
155 }
156 catch (CertificateException e)
157 {
158 e.printStackTrace();
159 }
160 catch (FileNotFoundException e)
161 {
162 e.printStackTrace();
163 }
164 return certificate;
165 }
166
167
168 /**
169 * Try to store the given certificate in the KeyStore.
170 *
171 * @param certificate
172 * @return whether it was successfully stored
173 */
174 private boolean storeCertificate(X509Certificate certificate)
175 {
176 try
177 {
178 KeyStore store = KeyStore.getInstance("LocalCertificateStore");
179 store.load(null, null);
180 store.setCertificateEntry(null, certificate);
181 TrustedCertificateManager.getInstance().reset();
182 return true;
183 }
184 catch (Exception e)
185 {
186 e.printStackTrace();
187 return false;
188 }
189 }
190
191 /**
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.
194 */
195 public static class ConfirmImportDialog extends AppCompatDialogFragment
196 {
197 @Override
198 public Dialog onCreateDialog(Bundle savedInstanceState)
199 {
200 final X509Certificate certificate;
201
202 certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE);
203
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()
209 {
210 @Override
211 public void onClick(DialogInterface dialog, int whichButton)
212 {
213 TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity();
214 if (activity.storeCertificate(certificate))
215 {
216 Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show();
217 getActivity().setResult(Activity.RESULT_OK);
218 }
219 else
220 {
221 Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show();
222 }
223 getActivity().finish();
224 }
225 })
226 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
227 {
228 @Override
229 public void onClick(DialogInterface dialog, int which)
230 {
231 getActivity().finish();
232 }
233 }).create();
234 }
235
236 @Override
237 public void onCancel(DialogInterface dialog)
238 {
239 getActivity().finish();
240 }
241 }
242 }