/* * 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 . */ 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 static final String KEY_CREATED = "KEY_CREATED"; private static final String KEY_EXISTING_USED = "KEY_EXISTING_USED"; private static final String KEY_FAILED = "KEY_FAILED"; 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 String keyResult; /** * 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) { switch (keyResult) { case KEY_CREATED: Log_OC.d(TAG, "New keys generated and stored."); dialog.dismiss(); Intent intentCreated = new Intent(); intentCreated.putExtra(SUCCESS, true); intentCreated.putExtra(ARG_POSITION, getArguments().getInt(ARG_POSITION)); getTargetFragment().onActivityResult(getTargetRequestCode(), SETUP_ENCRYPTION_RESULT_CODE, intentCreated); break; case KEY_EXISTING_USED: 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 intentExisting = new Intent(); intentExisting.putExtra(SUCCESS, true); intentExisting.putExtra(ARG_POSITION, getArguments().getInt(ARG_POSITION)); getTargetFragment().onActivityResult(getTargetRequestCode(), SETUP_ENCRYPTION_RESULT_CODE, intentExisting); } catch (Exception e) { textView.setText(R.string.end_to_end_encryption_wrong_password); Log_OC.d(TAG, "Error while decrypting private key: " + e.getMessage()); } break; default: dialog.dismiss(); break; } } }); } }); task = new DownloadKeysAsyncTask(); task.execute(); return dialog; } private class DownloadKeysAsyncTask extends AsyncTask { @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); keyResult = KEY_EXISTING_USED; 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 { private ArrayList 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; // Create public/private key pair KeyPair keyPair = EncryptionUtils.generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); // create CSR String urlEncoded = CsrHelper.generateCsrPemEncodedString(keyPair, account.name); 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 { keyResult = KEY_FAILED; return ""; } 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 privatePemKeyString = EncryptionUtils.privateKeyToPEM(privateKey); String encryptedPrivateKey = EncryptionUtils.encryptPrivateKey(privatePemKeyString, 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); keyResult = KEY_CREATED; return (String) storePrivateKeyResult.getData().get(0); } } catch (Exception e) { Log_OC.e(TAG, e.getMessage()); e.printStackTrace(); } keyResult = KEY_FAILED; return ""; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); if (s.isEmpty()) { keyResult = KEY_FAILED; getDialog().setTitle(R.string.common_error); textView.setText(R.string.end_to_end_encryption_unsuccessful); positiveButton.setText(R.string.end_to_end_encryption_dialog_close); positiveButton.setVisibility(View.VISIBLE); } else { 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); } } } }