package org.anhonesteffort.flock; import android.app.NotificationManager; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.NotificationCompat; import android.util.Log; import org.anhonesteffort.flock.util.guava.Optional; import org.anhonesteffort.flock.auth.DavAccount; import org.anhonesteffort.flock.crypto.KeyHelper; import org.anhonesteffort.flock.crypto.KeyStore; import org.anhonesteffort.flock.crypto.KeyUtil; import org.anhonesteffort.flock.registration.RegistrationApi; import org.anhonesteffort.flock.registration.RegistrationApiException; import org.anhonesteffort.flock.sync.key.DavKeyCollection; import org.anhonesteffort.flock.sync.key.DavKeyStore; import org.anhonesteffort.flock.webdav.PropertyParseException; import org.apache.jackrabbit.webdav.DavException; import java.io.IOException; import java.security.GeneralSecurityException; /** * Created by rhodey. */ public class ChangeEncryptionPasswordService extends Service { private static final String TAG = "org.anhonesteffort.flock.ChangeEncryptionPasswordService"; private static final String KEY_INTENT = "ChangeEncryptionPasswordService.KEY_INTENT"; protected static final String KEY_MESSENGER = "ChangeEncryptionPasswordService.KEY_MESSENGER"; protected static final String KEY_OLD_MASTER_PASSPHRASE = "ChangeEncryptionPasswordService.KEY_OLD_MASTER_PASSPHRASE"; protected static final String KEY_NEW_MASTER_PASSPHRASE = "ChangeEncryptionPasswordService.KEY_NEW_MASTER_PASSPHRASE"; protected static final String KEY_ACCOUNT = "ChangeEncryptionPasswordService.KEY_ACCOUNT"; private Looper serviceLooper; private ServiceHandler serviceHandler; private NotificationManager notifyManager; private NotificationCompat.Builder notificationBuilder; private Messenger messenger; private String oldMasterPassphrase; private String newMasterPassphrase; private DavAccount account; private int resultCode; private boolean remoteActivityIsAlive = true; private void handleInitializeNotification() { Log.d(TAG, "handleInitializeNotification()"); notificationBuilder.setContentTitle(getString(R.string.title_change_encryption_password)) .setContentText(getString(R.string.updating_encryption_secrets)) .setProgress(0, 0, true) .setSmallIcon(R.drawable.flock_actionbar_icon); startForeground(9002, notificationBuilder.build()); } @Override public void onDestroy() { Log.d(TAG, "onDestroy()"); if (remoteActivityIsAlive || resultCode == ErrorToaster.CODE_SUCCESS) return; Bundle errorBundler = new Bundle(); errorBundler.putInt(ErrorToaster.KEY_STATUS_CODE, resultCode); ErrorToaster.handleDisplayToastBundledError(getBaseContext(), errorBundler); notificationBuilder .setProgress(0, 0, false) .setContentText(getString(R.string.password_change_failed)); notifyManager.notify(9002, notificationBuilder.build()); } private void handleChangeComplete() { Log.d(TAG, "handleChangeComplete()"); if (remoteActivityIsAlive || resultCode == ErrorToaster.CODE_SUCCESS) stopForeground(true); else stopForeground(false); stopSelf(); } private void handleChangeOwsAuthToken(Bundle result, String passphrase) { Log.d(TAG, "handleChangeOwsAuthToken()"); try { RegistrationApi registrationApi = new RegistrationApi(getBaseContext()); String newAuthToken = KeyUtil.getAuthTokenForPassphrase(passphrase); registrationApi.setAccountPassword(account, newAuthToken); DavAccountHelper.setAccountPassword(getBaseContext(), newAuthToken); NotificationDrawer.disableAuthNotificationsForRunningAdapters(getBaseContext(), account.getOsAccount()); account = new DavAccount(account.getUserId(), newAuthToken, account.getDavHostHREF()); result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS); } catch (RegistrationApiException e) { ErrorToaster.handleBundleError(e, result); } catch (IOException e) { ErrorToaster.handleBundleError(e, result); } catch (GeneralSecurityException e) { ErrorToaster.handleBundleError(e, result); } } private String handleUpdateMasterPassphrase(Bundle result) { Log.d(TAG, "handleUpdateMasterPassphrase()"); Optional<String> oldEncryptedKeyMaterial = KeyStore.getEncryptedKeyMaterial(getBaseContext()); if (!oldEncryptedKeyMaterial.isPresent()) { Log.e(TAG, "old encrypted key material is missing"); result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_CRYPTO_ERROR); return null; } KeyStore.saveMasterPassphrase(getBaseContext(), newMasterPassphrase); try { Optional<String> encryptedKeyMaterial = KeyHelper.buildEncryptedKeyMaterial(getBaseContext()); if (encryptedKeyMaterial.isPresent()) { KeyStore.saveEncryptedKeyMaterial(getBaseContext(), encryptedKeyMaterial.get()); result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS); return encryptedKeyMaterial.get(); } else { Log.e(TAG, "new encrypted key material is missing"); result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_CRYPTO_ERROR); } } catch (GeneralSecurityException e) { ErrorToaster.handleBundleError(e, result); } catch (IOException e) { ErrorToaster.handleBundleError(e, result); } if (result.getInt(ErrorToaster.KEY_STATUS_CODE) != ErrorToaster.CODE_SUCCESS) { Log.w(TAG, "something went wrong, reverting to old passphrase"); KeyStore.saveMasterPassphrase(getBaseContext(), oldMasterPassphrase); KeyStore.saveEncryptedKeyMaterial(getBaseContext(), oldEncryptedKeyMaterial.get()); } return null; } private void handleUpdateRemoteKeyMaterial(Bundle result, String encryptedKeyMaterial) { Log.d(TAG, "handleUpdateRemoteKeyMaterial()"); try { DavKeyStore davKeyStore = DavAccountHelper.getDavKeyStore(getBaseContext(), account); Optional<DavKeyCollection> keyCollection = davKeyStore.getCollection(); if (!keyCollection.isPresent()) { Log.e(TAG, "key collection is missing!"); result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_CRYPTO_ERROR); return; } keyCollection.get().setEncryptedKeyMaterial(encryptedKeyMaterial); davKeyStore.closeHttpConnection(); result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS); } catch (PropertyParseException e) { ErrorToaster.handleBundleError(e, result); } catch (DavException e) { ErrorToaster.handleBundleError(e, result); } catch (IOException e) { ErrorToaster.handleBundleError(e, result); } } private void handleRevertLocalKeyMaterial(Bundle result) { Log.w(TAG, "handleRevertLocalKeyMaterial()"); KeyStore.saveMasterPassphrase(getBaseContext(), oldMasterPassphrase); try { Optional<String> encryptedKeyMaterial = KeyHelper.buildEncryptedKeyMaterial(getBaseContext()); if (encryptedKeyMaterial.isPresent()) KeyStore.saveEncryptedKeyMaterial(getBaseContext(), encryptedKeyMaterial.get()); else { Log.e(TAG, "old, reverted encrypted key material is missing!!! XXX :("); result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_CRYPTO_ERROR); DavAccountHelper.invalidateAccount(getBaseContext()); KeyStore.invalidateKeyMaterial(getBaseContext()); } } catch (GeneralSecurityException e) { ErrorToaster.handleBundleError(e, result); } catch (IOException e) { ErrorToaster.handleBundleError(e, result); } } private void handleRevertOwsAuthToken(Bundle result) { Log.w(TAG, "handleRevertOwsAuthToken()"); int statusSave = result.getInt(ErrorToaster.KEY_STATUS_CODE); handleChangeOwsAuthToken(result, oldMasterPassphrase); if (result.getInt(ErrorToaster.KEY_STATUS_CODE) != ErrorToaster.CODE_SUCCESS) { Log.e(TAG, "unable to revert OWS auth token!!! XXX :("); DavAccountHelper.invalidateAccount(getBaseContext()); KeyStore.invalidateKeyMaterial(getBaseContext()); } result.putInt(ErrorToaster.KEY_STATUS_CODE, statusSave); } private void handleStartChangeEncryptionPassword() { Log.d(TAG, "handleStartChangeEncryptionPassword()"); Bundle result = new Bundle(); handleInitializeNotification(); if (DavAccountHelper.isUsingOurServers(account)) { handleChangeOwsAuthToken(result, newMasterPassphrase); if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS) { String encryptedKeyMaterial = handleUpdateMasterPassphrase(result); if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS) { handleUpdateRemoteKeyMaterial(result, encryptedKeyMaterial); if (result.getInt(ErrorToaster.KEY_STATUS_CODE) != ErrorToaster.CODE_SUCCESS) { handleRevertOwsAuthToken(result); handleRevertLocalKeyMaterial(result); } } else handleRevertOwsAuthToken(result); } } else { String encryptedKeyMaterial = handleUpdateMasterPassphrase(result); if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS) { handleUpdateRemoteKeyMaterial(result, encryptedKeyMaterial); if (result.getInt(ErrorToaster.KEY_STATUS_CODE) != ErrorToaster.CODE_SUCCESS) handleRevertLocalKeyMaterial(result); } } Message message = Message.obtain(); message.arg1 = result.getInt(ErrorToaster.KEY_STATUS_CODE); resultCode = result.getInt(ErrorToaster.KEY_STATUS_CODE); try { messenger.send(message); } catch (RemoteException e) { Log.e(TAG, "caught exception while sending message to activity >> ", e); remoteActivityIsAlive = false; } handleChangeComplete(); } @Override public void onCreate() { HandlerThread thread = new HandlerThread("ChangeEncryptionPasswordService", HandlerThread.NORM_PRIORITY); thread.start(); serviceLooper = thread.getLooper(); serviceHandler = new ServiceHandler(serviceLooper); notifyManager = (NotificationManager)getBaseContext().getSystemService(Context.NOTIFICATION_SERVICE); notificationBuilder = new NotificationCompat.Builder(getBaseContext()); } private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { Log.d(TAG, "handleMessage()"); Intent intent = msg.getData().getParcelable(KEY_INTENT); if (intent != null) { if (intent.getExtras() != null && intent.getExtras().get(KEY_MESSENGER) != null && intent.getExtras().getString(KEY_OLD_MASTER_PASSPHRASE) != null && intent.getExtras().getString(KEY_NEW_MASTER_PASSPHRASE) != null && intent.getExtras().getBundle(KEY_ACCOUNT) != null) { if (!DavAccount.build(intent.getExtras().getBundle(KEY_ACCOUNT)).isPresent()) { Log.e(TAG, "received bad account bundle"); return; } messenger = (Messenger) intent.getExtras().get(KEY_MESSENGER); oldMasterPassphrase = intent.getExtras().getString(KEY_OLD_MASTER_PASSPHRASE); newMasterPassphrase = intent.getExtras().getString(KEY_NEW_MASTER_PASSPHRASE); account = DavAccount.build(intent.getExtras().getBundle(KEY_ACCOUNT)).get(); handleStartChangeEncryptionPassword(); } else Log.e(TAG, "received intent without messenger, old or new master passphrase, or account"); } else Log.e(TAG, "received message with null intent"); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { Message msg = serviceHandler.obtainMessage(); msg.getData().putParcelable(KEY_INTENT, intent); serviceHandler.sendMessage(msg); return START_STICKY; } @Override public IBinder onBind(Intent intent) { Log.d(TAG, "onBind()"); return null; } }