]> git.ipfire.org Git - thirdparty/strongswan.git/blob
60e57b03b3e3dab072c1b7706e0b5d415ca3964b
[thirdparty/strongswan.git] /
1 /*
2 * Copyright (C) 2014 Tobias Brunner
3 *
4 * Copyright (C) secunet Security Networks AG
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 package org.strongswan.android.ui;
18
19 import android.app.Dialog;
20 import android.content.ActivityNotFoundException;
21 import android.content.DialogInterface;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.widget.Toast;
26
27 import org.strongswan.android.R;
28 import org.strongswan.android.data.VpnProfileDataSource;
29 import org.strongswan.android.logic.TrustedCertificateManager;
30
31 import java.io.FileNotFoundException;
32 import java.io.InputStream;
33 import java.security.KeyStore;
34 import java.security.cert.CertificateException;
35 import java.security.cert.CertificateFactory;
36 import java.security.cert.X509Certificate;
37
38 import androidx.activity.result.ActivityResultLauncher;
39 import androidx.activity.result.contract.ActivityResultContracts;
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 String DIALOG_TAG = "Dialog";
48 private Uri mCertificateUri;
49
50 private final ActivityResultLauncher<Intent> mOpenDocument = registerForActivityResult(
51 new ActivityResultContracts.StartActivityForResult(),
52 result -> {
53 if (result.getResultCode() == RESULT_OK && result.getData() != null)
54 {
55 mCertificateUri = result.getData().getData();
56 return;
57 }
58 finish();
59 }
60 );
61
62 @Override
63 public void onCreate(Bundle savedInstanceState)
64 {
65 super.onCreate(savedInstanceState);
66
67 if (savedInstanceState != null)
68 { /* do nothing when we are restoring */
69 return;
70 }
71
72 Intent intent = getIntent();
73 String action = intent.getAction();
74 if (Intent.ACTION_VIEW.equals(action))
75 {
76 importCertificate(intent.getData());
77 }
78 else
79 {
80 Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
81 openIntent.setType("*/*");
82 try
83 {
84 mOpenDocument.launch(openIntent);
85 }
86 catch (ActivityNotFoundException e)
87 { /* some devices are unable to browse for files */
88 finish();
89 }
90 }
91 }
92
93 @Override
94 protected void onPostResume()
95 {
96 super.onPostResume();
97 if (mCertificateUri != null)
98 {
99 importCertificate(mCertificateUri);
100 mCertificateUri = null;
101 }
102 }
103
104 /**
105 * Import the file pointed to by the given URI as a certificate.
106 *
107 * @param uri
108 */
109 private void importCertificate(Uri uri)
110 {
111 X509Certificate certificate = parseCertificate(uri);
112 if (certificate == null)
113 {
114 Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show();
115 finish();
116 return;
117 }
118 /* Ask the user whether to import the certificate. This is particularly
119 * necessary because the import activity can be triggered by any app on
120 * the system. Also, if our app is the only one that is registered to
121 * open certificate files by MIME type the user would have no idea really
122 * where the file was imported just by reading the Toast we display. */
123 ConfirmImportDialog dialog = new ConfirmImportDialog();
124 Bundle args = new Bundle();
125 args.putSerializable(VpnProfileDataSource.KEY_CERTIFICATE, certificate);
126 dialog.setArguments(args);
127 FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
128 ft.add(dialog, DIALOG_TAG);
129 ft.commit();
130 }
131
132 /**
133 * Load the file from the given URI and try to parse it as X.509 certificate.
134 *
135 * @param uri
136 * @return certificate or null
137 */
138 private X509Certificate parseCertificate(Uri uri)
139 {
140 X509Certificate certificate = null;
141 try
142 {
143 CertificateFactory factory = CertificateFactory.getInstance("X.509");
144 InputStream in = getContentResolver().openInputStream(uri);
145 certificate = (X509Certificate)factory.generateCertificate(in);
146 /* we don't check whether it's actually a CA certificate or not */
147 }
148 catch (CertificateException e)
149 {
150 e.printStackTrace();
151 }
152 catch (FileNotFoundException e)
153 {
154 e.printStackTrace();
155 }
156 return certificate;
157 }
158
159
160 /**
161 * Try to store the given certificate in the KeyStore.
162 *
163 * @param certificate
164 * @return whether it was successfully stored
165 */
166 private boolean storeCertificate(X509Certificate certificate)
167 {
168 try
169 {
170 KeyStore store = KeyStore.getInstance("LocalCertificateStore");
171 store.load(null, null);
172 store.setCertificateEntry(null, certificate);
173 TrustedCertificateManager.getInstance().reset();
174 return true;
175 }
176 catch (Exception e)
177 {
178 e.printStackTrace();
179 return false;
180 }
181 }
182
183 /**
184 * Class that displays a confirmation dialog when a certificate should get
185 * imported. If the user confirms the import we try to store it.
186 */
187 public static class ConfirmImportDialog extends AppCompatDialogFragment
188 {
189 @Override
190 public Dialog onCreateDialog(Bundle savedInstanceState)
191 {
192 final X509Certificate certificate;
193
194 certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE);
195
196 return new AlertDialog.Builder(getActivity())
197 .setIcon(R.mipmap.ic_app)
198 .setTitle(R.string.import_certificate)
199 .setMessage(certificate.getSubjectDN().toString())
200 .setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener()
201 {
202 @Override
203 public void onClick(DialogInterface dialog, int whichButton)
204 {
205 TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity();
206 if (activity.storeCertificate(certificate))
207 {
208 Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show();
209 getActivity().setResult(RESULT_OK);
210 }
211 else
212 {
213 Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show();
214 }
215 getActivity().finish();
216 }
217 })
218 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
219 {
220 @Override
221 public void onClick(DialogInterface dialog, int which)
222 {
223 getActivity().finish();
224 }
225 }).create();
226 }
227
228 @Override
229 public void onCancel(DialogInterface dialog)
230 {
231 getActivity().finish();
232 }
233 }
234 }