353 lines
14 KiB
Java
353 lines
14 KiB
Java
/*
|
|
* Nextcloud Android client application
|
|
*
|
|
* @author Tobias Kaminsky
|
|
* Copyright (C) 2017 Tobias Kaminsky
|
|
* Copyright (C) 2017 Nextcloud GmbH.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* at your option) any later version.
|
|
*
|
|
* 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package com.owncloud.android.ui.dialog;
|
|
|
|
import android.accounts.Account;
|
|
import android.app.Dialog;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.support.design.widget.TextInputEditText;
|
|
import android.support.design.widget.TextInputLayout;
|
|
import android.support.v4.app.DialogFragment;
|
|
import android.support.v4.graphics.drawable.DrawableCompat;
|
|
import android.support.v7.app.AlertDialog;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.widget.Button;
|
|
import android.widget.TextView;
|
|
|
|
import com.owncloud.android.R;
|
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
|
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
|
import com.owncloud.android.lib.resources.users.GetPrivateKeyOperation;
|
|
import com.owncloud.android.lib.resources.users.GetPublicKeyOperation;
|
|
import com.owncloud.android.lib.resources.users.SendCSROperation;
|
|
import com.owncloud.android.lib.resources.users.StorePrivateKeyOperation;
|
|
import com.owncloud.android.utils.CsrHelper;
|
|
import com.owncloud.android.utils.EncryptionUtils;
|
|
import com.owncloud.android.utils.ThemeUtils;
|
|
|
|
import java.security.KeyPair;
|
|
import java.security.PrivateKey;
|
|
import java.util.ArrayList;
|
|
|
|
/*
|
|
* Dialog to setup encryption
|
|
*/
|
|
|
|
public class SetupEncryptionDialogFragment extends DialogFragment {
|
|
|
|
public static final String SUCCESS = "SUCCESS";
|
|
public static final int SETUP_ENCRYPTION_RESULT_CODE = 101;
|
|
public static final int SETUP_ENCRYPTION_REQUEST_CODE = 100;
|
|
public static String SETUP_ENCRYPTION_DIALOG_TAG = "SETUP_ENCRYPTION_DIALOG_TAG";
|
|
public static final String ARG_POSITION = "ARG_POSITION";
|
|
|
|
private static String ARG_ACCOUNT = "ARG_ACCOUNT";
|
|
private static String TAG = SetupEncryptionDialogFragment.class.getSimpleName();
|
|
|
|
private Account account;
|
|
private TextView textView;
|
|
private TextView passphraseTextView;
|
|
private ArbitraryDataProvider arbitraryDataProvider;
|
|
private Button positiveButton;
|
|
private TextInputLayout passwordLayout;
|
|
private DownloadKeysAsyncTask task;
|
|
private TextInputEditText passwordField;
|
|
private boolean keyCreated;
|
|
|
|
/**
|
|
* Public factory method to create new SetupEncryptionDialogFragment instance
|
|
*
|
|
* @return Dialog ready to show.
|
|
*/
|
|
public static SetupEncryptionDialogFragment newInstance(Account account, int position) {
|
|
SetupEncryptionDialogFragment fragment = new SetupEncryptionDialogFragment();
|
|
Bundle args = new Bundle();
|
|
args.putParcelable(ARG_ACCOUNT, account);
|
|
args.putInt(ARG_POSITION, position);
|
|
fragment.setArguments(args);
|
|
return fragment;
|
|
}
|
|
|
|
@Override
|
|
public void onStart() {
|
|
super.onStart();
|
|
|
|
int color = ThemeUtils.primaryAccentColor();
|
|
|
|
AlertDialog alertDialog = (AlertDialog) getDialog();
|
|
|
|
positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
|
positiveButton.setTextColor(color);
|
|
positiveButton.setVisibility(View.INVISIBLE);
|
|
}
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
int accentColor = ThemeUtils.primaryAccentColor();
|
|
account = getArguments().getParcelable(ARG_ACCOUNT);
|
|
|
|
arbitraryDataProvider = new ArbitraryDataProvider(getContext().getContentResolver());
|
|
|
|
// Inflate the layout for the dialog
|
|
LayoutInflater inflater = getActivity().getLayoutInflater();
|
|
|
|
// Setup layout
|
|
View v = inflater.inflate(R.layout.setup_encryption_dialog, null);
|
|
textView = (TextView) v.findViewById(R.id.encryption_status);
|
|
passphraseTextView = (TextView) v.findViewById(R.id.encryption_passphrase);
|
|
passwordLayout = (TextInputLayout) v.findViewById(R.id.encryption_passwordLayout);
|
|
passwordField = (TextInputEditText) v.findViewById(R.id.encryption_passwordInput);
|
|
passwordField.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_ATOP);
|
|
|
|
Drawable wrappedDrawable = DrawableCompat.wrap(passwordField.getBackground());
|
|
DrawableCompat.setTint(wrappedDrawable, accentColor);
|
|
passwordField.setBackgroundDrawable(wrappedDrawable);
|
|
|
|
// Build the dialog
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
|
builder.setView(v).setPositiveButton(R.string.common_ok, null)
|
|
.setTitle(ThemeUtils.getColoredTitle(getString(R.string.end_to_end_encryption_title), accentColor));
|
|
|
|
Dialog dialog = builder.create();
|
|
dialog.setCanceledOnTouchOutside(false);
|
|
|
|
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
|
|
|
@Override
|
|
public void onShow(final DialogInterface dialog) {
|
|
|
|
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
|
button.setOnClickListener(new View.OnClickListener() {
|
|
|
|
@Override
|
|
public void onClick(View view) {
|
|
if (keyCreated) {
|
|
Log_OC.d(TAG, "New keys generated and stored.");
|
|
|
|
dialog.dismiss();
|
|
|
|
Intent intent = new Intent();
|
|
intent.putExtra(SUCCESS, true);
|
|
intent.putExtra(ARG_POSITION, getArguments().getInt(ARG_POSITION));
|
|
getTargetFragment().onActivityResult(getTargetRequestCode(), SETUP_ENCRYPTION_RESULT_CODE,
|
|
intent);
|
|
} else {
|
|
Log_OC.d(TAG, "Decrypt private key");
|
|
|
|
textView.setText(R.string.end_to_end_encryption_decrypting);
|
|
|
|
try {
|
|
String privateKey = task.get();
|
|
String decryptedPrivateKey = EncryptionUtils.decryptPrivateKey(privateKey,
|
|
passwordField.getText().toString());
|
|
|
|
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PRIVATE_KEY,
|
|
decryptedPrivateKey);
|
|
|
|
dialog.dismiss();
|
|
Log_OC.d(TAG, "Private key successfully decrypted and stored");
|
|
|
|
Intent intent = new Intent();
|
|
intent.putExtra(SUCCESS, true);
|
|
intent.putExtra(ARG_POSITION, getArguments().getInt(ARG_POSITION));
|
|
getTargetFragment().onActivityResult(getTargetRequestCode(),
|
|
SETUP_ENCRYPTION_RESULT_CODE, intent);
|
|
|
|
} catch (Exception e) {
|
|
textView.setText(R.string.end_to_end_encryption_wrong_password);
|
|
Log_OC.d(TAG, "Error while decrypting private key: " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
task = new DownloadKeysAsyncTask();
|
|
task.execute();
|
|
|
|
return dialog;
|
|
}
|
|
|
|
private class DownloadKeysAsyncTask extends AsyncTask<Void, Void, String> {
|
|
@Override
|
|
protected void onPreExecute() {
|
|
super.onPreExecute();
|
|
|
|
textView.setText(R.string.end_to_end_encryption_retrieving_keys);
|
|
}
|
|
|
|
@Override
|
|
protected String doInBackground(Void... voids) {
|
|
// fetch private/public key
|
|
// if available
|
|
// - store public key
|
|
// - decrypt private key, store unencrypted private key in database
|
|
|
|
GetPublicKeyOperation publicKeyOperation = new GetPublicKeyOperation();
|
|
RemoteOperationResult publicKeyResult = publicKeyOperation.execute(account, getContext());
|
|
|
|
if (publicKeyResult.isSuccess()) {
|
|
Log_OC.d(TAG, "public key successful downloaded for " + account.name);
|
|
|
|
String publicKeyFromServer = (String) publicKeyResult.getData().get(0);
|
|
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PUBLIC_KEY,
|
|
publicKeyFromServer);
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
GetPrivateKeyOperation privateKeyOperation = new GetPrivateKeyOperation();
|
|
RemoteOperationResult privateKeyResult = privateKeyOperation.execute(account, getContext());
|
|
|
|
if (privateKeyResult.isSuccess()) {
|
|
Log_OC.d(TAG, "private key successful downloaded for " + account.name);
|
|
|
|
keyCreated = false;
|
|
return (String) privateKeyResult.getData().get(0);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(String privateKey) {
|
|
super.onPostExecute(privateKey);
|
|
|
|
if (privateKey == null) {
|
|
// no public/private key available, generate new
|
|
GenerateNewKeysAsyncTask newKeysTask = new GenerateNewKeysAsyncTask();
|
|
|
|
newKeysTask.execute();
|
|
|
|
} else if (!privateKey.isEmpty()) {
|
|
textView.setText(R.string.end_to_end_encryption_enter_password);
|
|
passwordLayout.setVisibility(View.VISIBLE);
|
|
positiveButton.setVisibility(View.VISIBLE);
|
|
} else {
|
|
Log_OC.e(TAG, "Got empty private key string");
|
|
}
|
|
}
|
|
}
|
|
|
|
private class GenerateNewKeysAsyncTask extends AsyncTask<Void, Void, String> {
|
|
private ArrayList<String> keyWords;
|
|
|
|
@Override
|
|
protected void onPreExecute() {
|
|
super.onPreExecute();
|
|
|
|
textView.setText(R.string.end_to_end_encryption_generating_keys);
|
|
}
|
|
|
|
@Override
|
|
protected String doInBackground(Void... voids) {
|
|
// - create CSR, push to server, store returned public key in database
|
|
// - encrypt private key, push key to server, store unencrypted private key in database
|
|
|
|
try {
|
|
String publicKey;
|
|
keyCreated = true;
|
|
|
|
// Create public/private key pair
|
|
KeyPair keyPair = EncryptionUtils.generateKeyPair();
|
|
PrivateKey privateKey = keyPair.getPrivate();
|
|
|
|
// create CSR
|
|
String urlEncoded = CsrHelper.generateCsrPemEncodedString(keyPair);
|
|
|
|
SendCSROperation operation = new SendCSROperation(urlEncoded);
|
|
RemoteOperationResult result = operation.execute(account, getContext());
|
|
|
|
if (result.isSuccess()) {
|
|
Log_OC.d(TAG, "public key success");
|
|
|
|
publicKey = (String) result.getData().get(0);
|
|
} else {
|
|
throw new Exception("Public key not stored!");
|
|
}
|
|
|
|
keyWords = EncryptionUtils.getRandomWords(12, getContext());
|
|
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
for (String string: keyWords) {
|
|
stringBuilder.append(string);
|
|
}
|
|
String keyPhrase = stringBuilder.toString();
|
|
|
|
String privateKeyString = EncryptionUtils.encodeBytesToBase64String(privateKey.getEncoded());
|
|
String encryptedPrivateKey = EncryptionUtils.encryptPrivateKey(privateKeyString, keyPhrase);
|
|
|
|
// upload encryptedPrivateKey
|
|
StorePrivateKeyOperation storePrivateKeyOperation = new StorePrivateKeyOperation(encryptedPrivateKey);
|
|
RemoteOperationResult storePrivateKeyResult = storePrivateKeyOperation.execute(account, getContext());
|
|
|
|
if (storePrivateKeyResult.isSuccess()) {
|
|
Log_OC.d(TAG, "private key success");
|
|
|
|
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PRIVATE_KEY,
|
|
privateKeyString);
|
|
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PUBLIC_KEY, publicKey);
|
|
|
|
return (String) storePrivateKeyResult.getData().get(0);
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log_OC.e(TAG, e.getMessage());
|
|
e.printStackTrace();
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(String s) {
|
|
super.onPostExecute(s);
|
|
|
|
if (!s.isEmpty()) {
|
|
getDialog().setTitle(R.string.end_to_end_encryption_passphrase_title);
|
|
|
|
textView.setText(R.string.end_to_end_encryption_keywords_description);
|
|
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
for (String string: keyWords) {
|
|
stringBuilder.append(string).append(" ");
|
|
}
|
|
String keys = stringBuilder.toString();
|
|
|
|
passphraseTextView.setText(keys);
|
|
|
|
passphraseTextView.setVisibility(View.VISIBLE);
|
|
positiveButton.setText(R.string.end_to_end_encryption_confirm_button);
|
|
positiveButton.setVisibility(View.VISIBLE);
|
|
}
|
|
}
|
|
}
|
|
}
|