238 lines
6.7 KiB
Java
238 lines
6.7 KiB
Java
/*
|
|
* Copyright (C) 2014 Tobias Brunner
|
|
* HSR Hochschule fuer Technik Rapperswil
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
package org.strongswan.android.ui;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.app.Dialog;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.widget.Toast;
|
|
|
|
import org.strongswan.android.R;
|
|
import org.strongswan.android.data.VpnProfileDataSource;
|
|
import org.strongswan.android.logic.TrustedCertificateManager;
|
|
|
|
import java.io.FileNotFoundException;
|
|
import java.io.InputStream;
|
|
import java.security.KeyStore;
|
|
import java.security.cert.CertificateException;
|
|
import java.security.cert.CertificateFactory;
|
|
import java.security.cert.X509Certificate;
|
|
|
|
import androidx.activity.result.ActivityResultLauncher;
|
|
import androidx.activity.result.contract.ActivityResultContracts;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.appcompat.app.AppCompatDialogFragment;
|
|
import androidx.fragment.app.FragmentTransaction;
|
|
|
|
public class TrustedCertificateImportActivity extends AppCompatActivity
|
|
{
|
|
private static final String DIALOG_TAG = "Dialog";
|
|
private Uri mCertificateUri;
|
|
|
|
private final ActivityResultLauncher<Intent> mOpenDocument = registerForActivityResult(
|
|
new ActivityResultContracts.StartActivityForResult(),
|
|
result -> {
|
|
if (result.getResultCode() == RESULT_OK && result.getData() != null)
|
|
{
|
|
mCertificateUri = result.getData().getData();
|
|
return;
|
|
}
|
|
finish();
|
|
}
|
|
);
|
|
|
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState)
|
|
{
|
|
super.onCreate(savedInstanceState);
|
|
|
|
if (savedInstanceState != null)
|
|
{ /* do nothing when we are restoring */
|
|
return;
|
|
}
|
|
|
|
Intent intent = getIntent();
|
|
String action = intent.getAction();
|
|
if (Intent.ACTION_VIEW.equals(action))
|
|
{
|
|
importCertificate(intent.getData());
|
|
}
|
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
|
{
|
|
Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
|
openIntent.setType("*/*");
|
|
try
|
|
{
|
|
mOpenDocument.launch(openIntent);
|
|
}
|
|
catch (ActivityNotFoundException e)
|
|
{ /* some devices are unable to browse for files */
|
|
finish();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostResume()
|
|
{
|
|
super.onPostResume();
|
|
if (mCertificateUri != null)
|
|
{
|
|
importCertificate(mCertificateUri);
|
|
mCertificateUri = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import the file pointed to by the given URI as a certificate.
|
|
*
|
|
* @param uri
|
|
*/
|
|
private void importCertificate(Uri uri)
|
|
{
|
|
X509Certificate certificate = parseCertificate(uri);
|
|
if (certificate == null)
|
|
{
|
|
Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show();
|
|
finish();
|
|
return;
|
|
}
|
|
/* Ask the user whether to import the certificate. This is particularly
|
|
* necessary because the import activity can be triggered by any app on
|
|
* the system. Also, if our app is the only one that is registered to
|
|
* open certificate files by MIME type the user would have no idea really
|
|
* where the file was imported just by reading the Toast we display. */
|
|
ConfirmImportDialog dialog = new ConfirmImportDialog();
|
|
Bundle args = new Bundle();
|
|
args.putSerializable(VpnProfileDataSource.KEY_CERTIFICATE, certificate);
|
|
dialog.setArguments(args);
|
|
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
|
ft.add(dialog, DIALOG_TAG);
|
|
ft.commit();
|
|
}
|
|
|
|
/**
|
|
* Load the file from the given URI and try to parse it as X.509 certificate.
|
|
*
|
|
* @param uri
|
|
* @return certificate or null
|
|
*/
|
|
private X509Certificate parseCertificate(Uri uri)
|
|
{
|
|
X509Certificate certificate = null;
|
|
try
|
|
{
|
|
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
|
InputStream in = getContentResolver().openInputStream(uri);
|
|
certificate = (X509Certificate)factory.generateCertificate(in);
|
|
/* we don't check whether it's actually a CA certificate or not */
|
|
}
|
|
catch (CertificateException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
return certificate;
|
|
}
|
|
|
|
|
|
/**
|
|
* Try to store the given certificate in the KeyStore.
|
|
*
|
|
* @param certificate
|
|
* @return whether it was successfully stored
|
|
*/
|
|
private boolean storeCertificate(X509Certificate certificate)
|
|
{
|
|
try
|
|
{
|
|
KeyStore store = KeyStore.getInstance("LocalCertificateStore");
|
|
store.load(null, null);
|
|
store.setCertificateEntry(null, certificate);
|
|
TrustedCertificateManager.getInstance().reset();
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
e.printStackTrace();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class that displays a confirmation dialog when a certificate should get
|
|
* imported. If the user confirms the import we try to store it.
|
|
*/
|
|
public static class ConfirmImportDialog extends AppCompatDialogFragment
|
|
{
|
|
@Override
|
|
public Dialog onCreateDialog(Bundle savedInstanceState)
|
|
{
|
|
final X509Certificate certificate;
|
|
|
|
certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE);
|
|
|
|
return new AlertDialog.Builder(getActivity())
|
|
.setIcon(R.mipmap.ic_app)
|
|
.setTitle(R.string.import_certificate)
|
|
.setMessage(certificate.getSubjectDN().toString())
|
|
.setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int whichButton)
|
|
{
|
|
TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity();
|
|
if (activity.storeCertificate(certificate))
|
|
{
|
|
Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show();
|
|
getActivity().setResult(RESULT_OK);
|
|
}
|
|
else
|
|
{
|
|
Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show();
|
|
}
|
|
getActivity().finish();
|
|
}
|
|
})
|
|
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which)
|
|
{
|
|
getActivity().finish();
|
|
}
|
|
}).create();
|
|
}
|
|
|
|
@Override
|
|
public void onCancel(DialogInterface dialog)
|
|
{
|
|
getActivity().finish();
|
|
}
|
|
}
|
|
}
|