]> git.ipfire.org Git - thirdparty/strongswan.git/blob
def0b88a5d61ad10022fec07864fb9255787d0dc
[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.annotation.TargetApi;
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.activity.result.ActivityResultLauncher;
41 import androidx.activity.result.contract.ActivityResultContracts;
42 import androidx.appcompat.app.AlertDialog;
43 import androidx.appcompat.app.AppCompatActivity;
44 import androidx.appcompat.app.AppCompatDialogFragment;
45 import androidx.fragment.app.FragmentTransaction;
46
47 public class TrustedCertificateImportActivity extends AppCompatActivity
48 {
49 private static final String DIALOG_TAG = "Dialog";
50 private Uri mCertificateUri;
51
52 private final ActivityResultLauncher<Intent> mOpenDocument = registerForActivityResult(
53 new ActivityResultContracts.StartActivityForResult(),
54 result -> {
55 if (result.getResultCode() == RESULT_OK && result.getData() != null)
56 {
57 mCertificateUri = result.getData().getData();
58 return;
59 }
60 finish();
61 }
62 );
63
64 @TargetApi(Build.VERSION_CODES.KITKAT)
65 @Override
66 public void onCreate(Bundle savedInstanceState)
67 {
68 super.onCreate(savedInstanceState);
69
70 if (savedInstanceState != null)
71 { /* do nothing when we are restoring */
72 return;
73 }
74
75 Intent intent = getIntent();
76 String action = intent.getAction();
77 if (Intent.ACTION_VIEW.equals(action))
78 {
79 importCertificate(intent.getData());
80 }
81 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
82 {
83 Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
84 openIntent.setType("*/*");
85 try
86 {
87 mOpenDocument.launch(openIntent);
88 }
89 catch (ActivityNotFoundException e)
90 { /* some devices are unable to browse for files */
91 finish();
92 return;
93 }
94 }
95 }
96
97 @Override
98 protected void onPostResume()
99 {
100 super.onPostResume();
101 if (mCertificateUri != null)
102 {
103 importCertificate(mCertificateUri);
104 mCertificateUri = null;
105 }
106 }
107
108 /**
109 * Import the file pointed to by the given URI as a certificate.
110 *
111 * @param uri
112 */
113 private void importCertificate(Uri uri)
114 {
115 X509Certificate certificate = parseCertificate(uri);
116 if (certificate == null)
117 {
118 Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show();
119 finish();
120 return;
121 }
122 /* Ask the user whether to import the certificate. This is particularly
123 * necessary because the import activity can be triggered by any app on
124 * the system. Also, if our app is the only one that is registered to
125 * open certificate files by MIME type the user would have no idea really
126 * where the file was imported just by reading the Toast we display. */
127 ConfirmImportDialog dialog = new ConfirmImportDialog();
128 Bundle args = new Bundle();
129 args.putSerializable(VpnProfileDataSource.KEY_CERTIFICATE, certificate);
130 dialog.setArguments(args);
131 FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
132 ft.add(dialog, DIALOG_TAG);
133 ft.commit();
134 }
135
136 /**
137 * Load the file from the given URI and try to parse it as X.509 certificate.
138 *
139 * @param uri
140 * @return certificate or null
141 */
142 private X509Certificate parseCertificate(Uri uri)
143 {
144 X509Certificate certificate = null;
145 try
146 {
147 CertificateFactory factory = CertificateFactory.getInstance("X.509");
148 InputStream in = getContentResolver().openInputStream(uri);
149 certificate = (X509Certificate)factory.generateCertificate(in);
150 /* we don't check whether it's actually a CA certificate or not */
151 }
152 catch (CertificateException e)
153 {
154 e.printStackTrace();
155 }
156 catch (FileNotFoundException e)
157 {
158 e.printStackTrace();
159 }
160 return certificate;
161 }
162
163
164 /**
165 * Try to store the given certificate in the KeyStore.
166 *
167 * @param certificate
168 * @return whether it was successfully stored
169 */
170 private boolean storeCertificate(X509Certificate certificate)
171 {
172 try
173 {
174 KeyStore store = KeyStore.getInstance("LocalCertificateStore");
175 store.load(null, null);
176 store.setCertificateEntry(null, certificate);
177 TrustedCertificateManager.getInstance().reset();
178 return true;
179 }
180 catch (Exception e)
181 {
182 e.printStackTrace();
183 return false;
184 }
185 }
186
187 /**
188 * Class that displays a confirmation dialog when a certificate should get
189 * imported. If the user confirms the import we try to store it.
190 */
191 public static class ConfirmImportDialog extends AppCompatDialogFragment
192 {
193 @Override
194 public Dialog onCreateDialog(Bundle savedInstanceState)
195 {
196 final X509Certificate certificate;
197
198 certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE);
199
200 return new AlertDialog.Builder(getActivity())
201 .setIcon(R.mipmap.ic_app)
202 .setTitle(R.string.import_certificate)
203 .setMessage(certificate.getSubjectDN().toString())
204 .setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener()
205 {
206 @Override
207 public void onClick(DialogInterface dialog, int whichButton)
208 {
209 TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity();
210 if (activity.storeCertificate(certificate))
211 {
212 Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show();
213 getActivity().setResult(RESULT_OK);
214 }
215 else
216 {
217 Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show();
218 }
219 getActivity().finish();
220 }
221 })
222 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
223 {
224 @Override
225 public void onClick(DialogInterface dialog, int which)
226 {
227 getActivity().finish();
228 }
229 }).create();
230 }
231
232 @Override
233 public void onCancel(DialogInterface dialog)
234 {
235 getActivity().finish();
236 }
237 }
238 }