completly new PasswordCacheService (more energy efficient), reworked Password dialogs

This commit is contained in:
Dominik 2012-09-11 19:56:54 +02:00
parent be49597882
commit 4b8400685a
19 changed files with 370 additions and 354 deletions

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
Licensed under the Apache License, Version 2.0 (the "License");
@ -49,7 +50,7 @@
android:theme="@style/Theme.Sherlock.Light.ForceOverflow" >
<activity
android:name=".ui.MainActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -59,7 +60,7 @@
</activity>
<activity
android:name=".ui.PublicKeyListActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_managePublicKeys"
android:launchMode="singleTop"
android:uiOptions="splitActionBarWhenNarrow" >
@ -73,7 +74,7 @@
</activity>
<activity
android:name=".ui.SecretKeyListActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_manageSecretKeys"
android:launchMode="singleTop"
android:uiOptions="splitActionBarWhenNarrow" >
@ -87,7 +88,7 @@
</activity>
<activity
android:name=".ui.EditKeyActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_editKey"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="stateHidden" >
@ -100,7 +101,7 @@
</activity>
<activity
android:name=".ui.SelectPublicKeyListActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_selectRecipients"
android:launchMode="singleTop"
android:uiOptions="splitActionBarWhenNarrow" >
@ -119,7 +120,7 @@
</activity>
<activity
android:name=".ui.SelectSecretKeyListActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_selectSignature"
android:launchMode="singleTop" >
<intent-filter>
@ -137,7 +138,7 @@
</activity>
<activity
android:name=".ui.EncryptActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_encrypt"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="stateHidden" >
@ -154,7 +155,7 @@
</activity>
<activity
android:name=".ui.DecryptActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_decrypt"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="stateHidden" >
@ -170,7 +171,7 @@
</activity>
<activity
android:name=".deprecated.GeneralActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/app_name"
android:theme="@android:style/Theme.Dialog" >
<intent-filter>
@ -202,39 +203,39 @@
</activity>
<activity
android:name=".ui.MailListActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_mailInbox" />
<activity
android:name=".ui.KeyServerQueryActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_keyServerQuery" />
<activity
android:name=".ui.KeyServerUploadActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_sendKey" />
<activity
android:name=".ui.PreferencesActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_preferences" />
<activity
android:name=".ui.PreferencesKeyServerActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_keyServerPreference"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".ui.SignKeyActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_signKey" />
<activity
android:name=".ui.ImportFromQRCodeActivity"
android:configChanges="keyboardHidden|orientation|keyboard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_importFromQRCode" />
<activity
android:name=".ui.HelpActivity"
android:label="@string/title_help" />
<service android:name=".service.password.PassphraseCacheService" />
<service android:name=".service.PassphraseCacheService" />
<service android:name=".service.ApgService" />
<!-- TODO: need to be moved into new service model -->

View File

@ -33,6 +33,7 @@
android:id="@+id/passphrase_passphrase"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:padding="4dp" />

View File

@ -54,6 +54,7 @@
android:id="@+id/passphrase_passphrase_again"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:padding="4dp" />
</TableRow>

View File

@ -38,7 +38,8 @@ public class ApgApplication extends Application {
super.onCreate();
/* Start passphrase cache service */
PassphraseCacheService.startCacheService(this);
// TODO: not needed anymore!
// PassphraseCacheService.startCacheService(this);
// TODO: Do it better than this!
// this initializes the database to be used in PGPMain

View File

@ -1,156 +0,0 @@
/*
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thialfihar.android.apg.deprecated;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.thialfihar.android.apg.Id;
import org.thialfihar.android.apg.R;
import org.thialfihar.android.apg.helper.PGPHelper;
import org.thialfihar.android.apg.helper.PGPMain;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import org.thialfihar.android.apg.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
/**
* TODO:
*
* - Use new PassphraseDialogFragment!
*
*
*/
public class AskForPassphrase {
public static interface PassPhraseCallbackInterface {
void passPhraseCallback(long keyId, String passPhrase);
}
public static Dialog createDialog(Activity context, long secretKeyId,
PassPhraseCallbackInterface callback) {
AlertDialog.Builder alert = new AlertDialog.Builder(context);
alert.setTitle(R.string.title_authentication);
final PGPSecretKey secretKey;
final Activity activity = context;
if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) {
secretKey = null;
alert.setMessage(context.getString(R.string.passPhraseForSymmetricEncryption));
} else {
secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
if (secretKey == null) {
alert.setTitle(R.string.title_keyNotFound);
alert.setMessage(context.getString(R.string.keyNotFound, secretKeyId));
alert.setPositiveButton(android.R.string.ok, new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
activity.removeDialog(Id.dialog.pass_phrase);
}
});
alert.setCancelable(false);
return alert.create();
}
String userId = PGPHelper.getMainUserIdSafe(context, secretKey);
alert.setMessage(context.getString(R.string.passPhraseFor, userId));
}
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.passphrase, null);
final EditText input = (EditText) view.findViewById(R.id.passphrase_passphrase);
final TextView labelNotUsed = (TextView) view
.findViewById(R.id.passphrase_label_passphrase_again);
labelNotUsed.setVisibility(View.GONE);
final EditText inputNotUsed = (EditText) view
.findViewById(R.id.passphrase_passphrase_again);
inputNotUsed.setVisibility(View.GONE);
alert.setView(view);
final PassPhraseCallbackInterface cb = callback;
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
activity.removeDialog(Id.dialog.pass_phrase);
String passPhrase = input.getText().toString();
long keyId;
if (secretKey != null) {
try {
PGPPrivateKey testKey = secretKey.extractPrivateKey(
passPhrase.toCharArray(), new BouncyCastleProvider());
if (testKey == null) {
Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey,
Toast.LENGTH_SHORT).show();
return;
}
} catch (PGPException e) {
Toast.makeText(activity, R.string.wrongPassPhrase, Toast.LENGTH_SHORT)
.show();
return;
}
keyId = secretKey.getKeyID();
} else {
keyId = Id.key.symmetric;
}
// cache again
PGPMain.setCachedPassPhrase(keyId, passPhrase);
// return by callback
cb.passPhraseCallback(keyId, passPhrase);
}
});
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
activity.removeDialog(Id.dialog.pass_phrase);
}
});
// check if the key has no passphrase
if (secretKey != null) {
try {
Log.d("APG", "check if key has no passphrase...");
PGPPrivateKey testKey = secretKey.extractPrivateKey("".toCharArray(),
new BouncyCastleProvider());
if (testKey != null) {
Log.d("APG", "Key has no passphrase!");
// cache null
PGPMain.setCachedPassPhrase(secretKey.getKeyID(), null);
// return by callback
cb.passPhraseCallback(secretKey.getKeyID(), null);
return null;
}
} catch (PGPException e) {
}
}
return alert.create();
}
}

View File

@ -42,7 +42,7 @@ import android.os.Handler;
import android.os.Message;
public class BaseActivity extends SherlockFragmentActivity implements Runnable,
ProgressDialogUpdater, AskForPassphrase.PassPhraseCallbackInterface {
ProgressDialogUpdater {
private ProgressDialog mProgressDialog = null;
// private PausableThread mRunningThread = null;
@ -363,11 +363,11 @@ public class BaseActivity extends SherlockFragmentActivity implements Runnable,
//
// Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
// }
public void passPhraseCallback(long keyId, String passPhrase) {
// TODO: Not needed anymore, now implemented in AskForSecretKeyPass
PGPMain.setCachedPassPhrase(keyId, passPhrase);
}
//
// public void passPhraseCallback(long keyId, String passPhrase) {
// // TODO: Not needed anymore, now implemented in AskForSecretKeyPass
// PGPMain.setCachedPassPhrase(keyId, passPhrase);
// }
// public void sendMessage(Message msg) {
// mHandler.sendMessage(msg);

View File

@ -78,7 +78,6 @@ import org.thialfihar.android.apg.provider.KeyRings;
import org.thialfihar.android.apg.provider.Keys;
import org.thialfihar.android.apg.provider.UserIds;
import org.thialfihar.android.apg.service.ApgService;
import org.thialfihar.android.apg.service.CachedPassphrase;
import org.thialfihar.android.apg.util.HkpKeyServer;
import org.thialfihar.android.apg.util.InputData;
import org.thialfihar.android.apg.util.PositionAwareInputStream;
@ -121,9 +120,7 @@ import java.security.Security;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Pattern;
@ -185,7 +182,6 @@ public class PGPMain {
".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*",
Pattern.DOTALL);
private static HashMap<Long, CachedPassphrase> mPassPhraseCache = new HashMap<Long, CachedPassphrase>();
private static String mEditPassPhrase = null;
private static Database mDatabase = null;
@ -224,58 +220,6 @@ public class PGPMain {
return mEditPassPhrase;
}
public static void setCachedPassPhrase(long keyId, String passPhrase) {
mPassPhraseCache.put(keyId, new CachedPassphrase(new Date().getTime(), passPhrase));
}
public static String getCachedPassPhrase(long keyId) {
long realId = keyId;
if (realId != Id.key.symmetric) {
PGPSecretKeyRing keyRing = getSecretKeyRing(keyId);
if (keyRing == null) {
return null;
}
PGPSecretKey masterKey = PGPHelper.getMasterKey(keyRing);
if (masterKey == null) {
return null;
}
realId = masterKey.getKeyID();
}
CachedPassphrase cpp = mPassPhraseCache.get(realId);
if (cpp == null) {
return null;
}
// set it again to reset the cache life cycle
setCachedPassPhrase(realId, cpp.passPhrase);
return cpp.passPhrase;
}
public static int cleanUpCache(int ttl, int initialDelay) {
int delay = initialDelay;
long realTtl = ttl * 1000;
long now = new Date().getTime();
Vector<Long> oldKeys = new Vector<Long>();
for (Map.Entry<Long, CachedPassphrase> pair : mPassPhraseCache.entrySet()) {
long lived = now - pair.getValue().timestamp;
if (lived >= realTtl) {
oldKeys.add(pair.getKey());
} else {
// see, whether the remaining time for this cache entry improves our
// check delay
long nextCheck = realTtl - lived + 1000;
if (nextCheck < delay) {
delay = (int) nextCheck;
}
}
}
for (long keyId : oldKeys) {
mPassPhraseCache.remove(keyId);
}
return delay;
}
/**
* Creates new secret key. The returned PGPSecretKeyRing contains only one newly generated key
* when this key is the new masterkey. If a masterkey is supplied in the parameters
@ -1268,11 +1212,10 @@ public class PGPMain {
progress.setProgress(R.string.progress_done, 100, 100);
}
public static PGPPublicKeyRing signKey(Context context, long masterKeyId, long pubKeyId)
throws GeneralException, NoSuchAlgorithmException, NoSuchProviderException,
PGPException, SignatureException {
String signaturePassPhrase = PGPMain.getCachedPassPhrase(masterKeyId);
if (signaturePassPhrase == null || signaturePassPhrase.length() <= 0) {
public static PGPPublicKeyRing signKey(Context context, long masterKeyId, long pubKeyId,
String passphrase) throws GeneralException, NoSuchAlgorithmException,
NoSuchProviderException, PGPException, SignatureException {
if (passphrase == null || passphrase.length() <= 0) {
throw new GeneralException("Unable to obtain passphrase");
} else {
PGPPublicKeyRing pubring = PGPMain.getPublicKeyRing(pubKeyId);
@ -1283,7 +1226,7 @@ public class PGPMain {
}
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassPhrase.toCharArray());
BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) {
throw new GeneralException(

View File

@ -70,6 +70,25 @@ public class ApgService extends IntentService implements ProgressDialogUpdater {
public static final String EXTRA_ACTION = "action";
public static final String EXTRA_DATA = "data";
/* possible EXTRA_ACTIONs */
public static final int ACTION_ENCRYPT_SIGN = 10;
public static final int ACTION_DECRYPT_VERIFY = 20;
public static final int ACTION_SAVE_KEYRING = 30;
public static final int ACTION_GENERATE_KEY = 31;
public static final int ACTION_GENERATE_DEFAULT_RSA_KEYS = 32;
public static final int ACTION_DELETE_FILE_SECURELY = 40;
public static final int ACTION_IMPORT_KEY = 50;
public static final int ACTION_EXPORT_KEY = 51;
public static final int ACTION_UPLOAD_KEY = 60;
public static final int ACTION_QUERY_KEY = 61;
public static final int ACTION_SIGN_KEY = 70;
/* keys for data bundle */
// encrypt, decrypt, import export
@ -143,25 +162,6 @@ public class ApgService extends IntentService implements ProgressDialogUpdater {
public static final String SIGN_KEY_MASTER_KEY_ID = "signKeyMasterKeyId";
public static final String SIGN_KEY_PUB_KEY_ID = "signKeyPubKeyId";
/* possible EXTRA_ACTIONs */
public static final int ACTION_ENCRYPT_SIGN = 10;
public static final int ACTION_DECRYPT_VERIFY = 20;
public static final int ACTION_SAVE_KEYRING = 30;
public static final int ACTION_GENERATE_KEY = 31;
public static final int ACTION_GENERATE_DEFAULT_RSA_KEYS = 32;
public static final int ACTION_DELETE_FILE_SECURELY = 40;
public static final int ACTION_IMPORT_KEY = 50;
public static final int ACTION_EXPORT_KEY = 51;
public static final int ACTION_UPLOAD_KEY = 60;
public static final int ACTION_QUERY_KEY = 61;
public static final int ACTION_SIGN_KEY = 70;
/* possible data keys as result send over messenger */
// keys
public static final String RESULT_NEW_KEY = "newKey";
@ -320,19 +320,21 @@ public class ApgService extends IntentService implements ProgressDialogUpdater {
if (generateSignature) {
Log.d(Constants.TAG, "generating signature...");
PGPMain.generateSignature(this, inputData, outStream, useAsciiArmour, false,
secretKeyId, PGPMain.getCachedPassPhrase(secretKeyId), Preferences
.getPreferences(this).getDefaultHashAlgorithm(), Preferences
.getPreferences(this).getForceV3Signatures(), this);
} else if (signOnly) {
Log.d(Constants.TAG, "sign only...");
PGPMain.signText(this, inputData, outStream, secretKeyId, PGPMain
.getCachedPassPhrase(secretKeyId), Preferences.getPreferences(this)
secretKeyId, PassphraseCacheService.getCachedPassphrase(this,
secretKeyId), Preferences.getPreferences(this)
.getDefaultHashAlgorithm(), Preferences.getPreferences(this)
.getForceV3Signatures(), this);
} else if (signOnly) {
Log.d(Constants.TAG, "sign only...");
PGPMain.signText(this, inputData, outStream, secretKeyId,
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences
.getPreferences(this).getForceV3Signatures(), this);
} else {
Log.d(Constants.TAG, "encrypt...");
PGPMain.encrypt(this, inputData, outStream, useAsciiArmour, encryptionKeyIds,
signatureKeyId, PGPMain.getCachedPassPhrase(signatureKeyId), this,
signatureKeyId,
PassphraseCacheService.getCachedPassphrase(this, signatureKeyId), this,
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm(),
Preferences.getPreferences(this).getDefaultHashAlgorithm(),
compressionId, Preferences.getPreferences(this).getForceV3Signatures(),
@ -478,7 +480,7 @@ public class ApgService extends IntentService implements ProgressDialogUpdater {
this);
} else {
resultData = PGPMain.decrypt(this, inputData, outStream,
PGPMain.getCachedPassPhrase(secretKeyId), this,
PassphraseCacheService.getCachedPassphrase(this, secretKeyId), this,
assumeSymmetricEncryption);
}
@ -539,7 +541,7 @@ public class ApgService extends IntentService implements ProgressDialogUpdater {
/* Operation */
PGPMain.buildSecretKey(this, userIds, keys, keysUsages, masterKeyId, oldPassPhrase,
newPassPhrase, this);
PGPMain.setCachedPassPhrase(masterKeyId, newPassPhrase);
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);
/* Output */
sendMessageToHandler(ApgServiceHandler.MESSAGE_OKAY);
@ -798,7 +800,11 @@ public class ApgService extends IntentService implements ProgressDialogUpdater {
long pubKeyId = data.getLong(SIGN_KEY_PUB_KEY_ID);
/* Operation */
PGPPublicKeyRing signedPubKeyRing = PGPMain.signKey(this, masterKeyId, pubKeyId);
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
masterKeyId);
PGPPublicKeyRing signedPubKeyRing = PGPMain.signKey(this, masterKeyId, pubKeyId,
signaturePassPhrase);
// store the signed key in our local cache
int retval = PGPMain.storeKeyRingInCache(signedPubKeyRing);

View File

@ -15,19 +15,27 @@
package org.thialfihar.android.apg.service;
public class CachedPassphrase {
public final long timestamp;
public final String passPhrase;
private final long timestamp;
private final String passphrase;
public CachedPassphrase(long timestamp, String passPhrase) {
super();
this.timestamp = timestamp;
this.passPhrase = passPhrase;
this.passphrase = passPhrase;
}
public long getTimestamp() {
return timestamp;
}
public String getPassphrase() {
return passphrase;
}
@Override
public int hashCode() {
int hc1 = (int) (this.timestamp & 0xffffffff);
int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode());
int hc2 = (this.passphrase == null ? 0 : this.passphrase.hashCode());
return (hc1 + hc2) * hc2 + hc1;
}
@ -42,12 +50,12 @@ public class CachedPassphrase {
return false;
}
if (passPhrase != o.passPhrase) {
if (passPhrase == null || o.passPhrase == null) {
if (passphrase != o.passphrase) {
if (passphrase == null || o.passphrase == null) {
return false;
}
if (!passPhrase.equals(o.passPhrase)) {
if (!passphrase.equals(o.passphrase)) {
return false;
}
}

View File

@ -1,4 +1,6 @@
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@ -14,90 +16,211 @@
package org.thialfihar.android.apg.service;
import java.util.HashMap;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.thialfihar.android.apg.Constants;
import org.thialfihar.android.apg.Id;
import org.thialfihar.android.apg.helper.PGPHelper;
import org.thialfihar.android.apg.helper.PGPMain;
import org.thialfihar.android.apg.helper.Preferences;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
public class PassphraseCacheService extends Service {
private final IBinder mBinder = new LocalBinder();
public static final String BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE = Constants.INTENT_PREFIX
+ "PASSPHRASE_CACHE_SERVICE";
public static final String EXTRA_TTL = "ttl";
public static final String EXTRA_KEY_ID = "keyId";
public static final String EXTRA_PASSPHRASE = "passphrase";
private static final int REQUEST_ID = 0;
private static final long DEFAULT_TTL = 15;
private BroadcastReceiver mIntentReceiver;
// TODO: This is static to be easily retrieved by getCachedPassphrase()
// To avoid static we would need a messenger from the service back to the activity?
private static HashMap<Long, CachedPassphrase> mPassphraseCache = new HashMap<Long, CachedPassphrase>();
/**
* This caches a new passphrase by sending a new command to the service. An android service is
* only run once. Thus when it is already started new commands just add new BroadcastReceivers
* for cached passphrases
*
* @param context
* @param keyId
* @param passphrase
*/
public static void addCachedPassphrase(Context context, long keyId, String passphrase) {
Log.d(Constants.TAG, "cacheNewPassphrase() for " + keyId);
public static void startCacheService(Context context) {
Intent intent = new Intent(context, PassphraseCacheService.class);
intent.putExtra(PassphraseCacheService.EXTRA_TTL, Preferences.getPreferences(context).getPassPhraseCacheTtl());
intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassPhraseCacheTtl());
intent.putExtra(EXTRA_PASSPHRASE, passphrase);
intent.putExtra(EXTRA_KEY_ID, keyId);
context.startService(intent);
}
private int mPassPhraseCacheTtl = 15;
private Handler mCacheHandler = new Handler();
private Runnable mCacheTask = new Runnable() {
public void run() {
// check every ttl/2 seconds, which shouldn't be heavy on the device (even if ttl = 15),
// and makes sure the longest a pass phrase survives in the cache is 1.5 * ttl
int delay = mPassPhraseCacheTtl * 1000 / 2;
// also make sure the delay is not longer than one minute
if (delay > 60000) {
delay = 60000;
/**
* Gets a cached passphrase from memory
*
* @param context
* @param keyId
* @return
*/
public static String getCachedPassphrase(Context context, long keyId) {
// try to get real key id
long realId = keyId;
if (realId != Id.key.symmetric) {
PGPSecretKeyRing keyRing = PGPMain.getSecretKeyRing(keyId);
if (keyRing == null) {
return null;
}
PGPSecretKey masterKey = PGPHelper.getMasterKey(keyRing);
if (masterKey == null) {
return null;
}
realId = masterKey.getKeyID();
}
delay = PGPMain.cleanUpCache(mPassPhraseCacheTtl, delay);
// don't check too often, even if we were close
if (delay < 5000) {
delay = 5000;
// get cached passphrase
CachedPassphrase cpp = mPassphraseCache.get(realId);
if (cpp == null) {
return null;
}
// set it again to reset the cache life cycle
addCachedPassphrase(context, realId, cpp.getPassphrase());
return cpp.getPassphrase();
}
mCacheHandler.postDelayed(this, delay);
/**
* Register BroadcastReceiver that is unregistered when service is destroyed. This
* BroadcastReceiver hears on intents with ACTION_PASSPHRASE_CACHE_SERVICE to timeout
* passphrases in memory.
*/
private void registerReceiver() {
if (mIntentReceiver == null) {
mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) {
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
timeout(context, keyId);
}
}
};
static private boolean mIsRunning = false;
IntentFilter filter = new IntentFilter();
filter.addAction(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
LocalBroadcastManager.getInstance(this).registerReceiver(mIntentReceiver, filter);
}
}
/**
* Build pending intent that is executed by alarm manager when one passphrase times out
*
* @param context
* @param keyId
* @return
*/
private static PendingIntent buildIntent(Context context, long keyId) {
Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
intent.putExtra(EXTRA_KEY_ID, keyId);
PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_ID, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
return sender;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(Constants.TAG, "PassphraseCacheService created!");
}
mIsRunning = true;
/**
* Executed when service is started by intent
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(Constants.TAG, "PassphraseCacheService started");
// register broadcastreceiver
registerReceiver();
if (intent != null) {
long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE);
Log.d(Constants.TAG, "received intent with keyId: " + keyId + ", ttl: " + ttl);
// add keyId and passphrase to memory
mPassphraseCache.put(keyId,
new CachedPassphrase(System.currentTimeMillis(), passphrase));
// register new alarm with keyId for this passphrase
long triggerTime = System.currentTimeMillis() + ttl;
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId));
}
return START_STICKY;
}
/**
* Called when one specific passphrase for keyId timed out
*
* @param context
* @param keyId
*/
private void timeout(Context context, long keyId) {
Log.d(Constants.TAG, "Timeout of " + keyId);
// remove passphrase corresponding to keyId from memory
mPassphraseCache.remove(keyId);
// stop whole service if no cached passphrases remaining
if (mPassphraseCache.isEmpty()) {
Log.d(Constants.TAG, "No passphrases remaining in memory, stopping service!");
stopSelf();
}
}
@Override
public void onDestroy() {
super.onDestroy();
mIsRunning = false;
Log.d(Constants.TAG, "PassphraseCacheService destroyed!");
LocalBroadcastManager.getInstance(this).unregisterReceiver(mIntentReceiver);
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
if (intent != null) {
mPassPhraseCacheTtl = intent.getIntExtra(EXTRA_TTL, 15);
}
if (mPassPhraseCacheTtl < 15) {
mPassPhraseCacheTtl = 15;
}
mCacheHandler.removeCallbacks(mCacheTask);
mCacheHandler.postDelayed(mCacheTask, 1000);
}
static public boolean isRunning() {
return mIsRunning;
}
public class LocalBinder extends Binder {
PassphraseCacheService getService() {
public class PassphraseCacheBinder extends Binder {
public PassphraseCacheService getService() {
return PassphraseCacheService.this;
}
}
private final IBinder mBinder = new PassphraseCacheBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}

View File

@ -25,6 +25,7 @@ import org.thialfihar.android.apg.helper.PGPHelper;
import org.thialfihar.android.apg.helper.PGPMain;
import org.thialfihar.android.apg.service.ApgServiceHandler;
import org.thialfihar.android.apg.service.ApgService;
import org.thialfihar.android.apg.service.PassphraseCacheService;
import org.thialfihar.android.apg.ui.dialog.DeleteFileDialogFragment;
import org.thialfihar.android.apg.ui.dialog.FileDialogFragment;
import org.thialfihar.android.apg.ui.dialog.LookupUnknownKeyDialogFragment;
@ -513,7 +514,7 @@ public class DecryptActivity extends SherlockFragmentActivity {
// if we need a symmetric passphrase or a passphrase to use a secret key ask for it
if (getSecretKeyId() == Id.key.symmetric
|| PGPMain.getCachedPassPhrase(getSecretKeyId()) == null) {
|| PassphraseCacheService.getCachedPassphrase(this, getSecretKeyId()) == null) {
showPassphraseDialog();
} else {
if (mDecryptTarget == Id.target.file) {
@ -548,7 +549,7 @@ public class DecryptActivity extends SherlockFragmentActivity {
Messenger messenger = new Messenger(returnHandler);
try {
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
messenger, mSecretKeyId);
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");

View File

@ -30,6 +30,7 @@ import org.thialfihar.android.apg.helper.PGPMain;
import org.thialfihar.android.apg.helper.Preferences;
import org.thialfihar.android.apg.service.ApgServiceHandler;
import org.thialfihar.android.apg.service.ApgService;
import org.thialfihar.android.apg.service.PassphraseCacheService;
import org.thialfihar.android.apg.ui.dialog.DeleteFileDialogFragment;
import org.thialfihar.android.apg.ui.dialog.FileDialogFragment;
import org.thialfihar.android.apg.ui.dialog.PassphraseDialogFragment;
@ -659,7 +660,8 @@ public class EncryptActivity extends SherlockFragmentActivity {
return;
}
if (getSecretKeyId() != 0 && PGPMain.getCachedPassPhrase(getSecretKeyId()) == null) {
if (getSecretKeyId() != 0
&& PassphraseCacheService.getCachedPassphrase(this, getSecretKeyId()) == null) {
showPassphraseDialog();
return;
@ -697,7 +699,7 @@ public class EncryptActivity extends SherlockFragmentActivity {
try {
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(
messenger, mSecretKeyId);
EncryptActivity.this, messenger, mSecretKeyId);
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
} catch (PGPMain.GeneralException e) {

View File

@ -69,7 +69,8 @@ public class PreferencesActivity extends SherlockPreferenceActivity {
mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString()));
// restart cache service with new ttl
PassphraseCacheService.startCacheService(PreferencesActivity.this);
// TODO: not needed anymore!
// PassphraseCacheService.startCacheService(PreferencesActivity.this);
return false;
}
});

View File

@ -21,6 +21,7 @@ import org.thialfihar.android.apg.Constants;
import org.thialfihar.android.apg.Id;
import org.thialfihar.android.apg.helper.PGPHelper;
import org.thialfihar.android.apg.helper.PGPMain;
import org.thialfihar.android.apg.service.PassphraseCacheService;
import org.thialfihar.android.apg.ui.dialog.PassphraseDialogFragment;
import org.thialfihar.android.apg.util.Log;
@ -137,7 +138,7 @@ public class SecretKeyListActivity extends KeyListActivity implements OnChildCli
public void checkPassPhraseAndEdit() {
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
String passPhrase = PGPMain.getCachedPassPhrase(keyId);
String passPhrase = PassphraseCacheService.getCachedPassphrase(this, keyId);
if (passPhrase == null) {
showPassphraseDialog(keyId);
} else {
@ -152,7 +153,8 @@ public class SecretKeyListActivity extends KeyListActivity implements OnChildCli
@Override
public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
String passPhrase = PGPMain.getCachedPassPhrase(secretKeyId);
String passPhrase = PassphraseCacheService.getCachedPassphrase(
SecretKeyListActivity.this, secretKeyId);
PGPMain.setEditPassPhrase(passPhrase);
editKey();
}
@ -164,7 +166,7 @@ public class SecretKeyListActivity extends KeyListActivity implements OnChildCli
try {
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(
messenger, secretKeyId);
SecretKeyListActivity.this, messenger, secretKeyId);
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
} catch (PGPMain.GeneralException e) {

View File

@ -27,6 +27,7 @@ import org.thialfihar.android.apg.helper.PGPMain;
import org.thialfihar.android.apg.helper.Preferences;
import org.thialfihar.android.apg.service.ApgService;
import org.thialfihar.android.apg.service.ApgServiceHandler;
import org.thialfihar.android.apg.service.PassphraseCacheService;
import org.thialfihar.android.apg.ui.dialog.PassphraseDialogFragment;
import com.actionbarsherlock.app.ActionBar;
@ -141,7 +142,7 @@ public class SignKeyActivity extends SherlockFragmentActivity {
Messenger messenger = new Messenger(returnHandler);
try {
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
messenger, secretKeyId);
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
@ -175,7 +176,7 @@ public class SignKeyActivity extends SherlockFragmentActivity {
/*
* get the user's passphrase for this key (if required)
*/
String passphrase = PGPMain.getCachedPassPhrase(mMasterKeyId);
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
if (passphrase == null) {
showPassphraseDialog(mMasterKeyId);
return; // bail out; need to wait until the user has entered the passphrase

View File

@ -31,6 +31,7 @@ import org.thialfihar.android.apg.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
@ -38,13 +39,22 @@ import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import org.thialfihar.android.apg.service.PassphraseCacheService;
import org.thialfihar.android.apg.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
public class PassphraseDialogFragment extends DialogFragment {
public class PassphraseDialogFragment extends DialogFragment implements OnEditorActionListener {
private Messenger mMessenger;
@ -53,6 +63,8 @@ public class PassphraseDialogFragment extends DialogFragment {
public static final int MESSAGE_OKAY = 1;
private EditText mPassphraseEditText;
/**
* Creates new instance of this dialog fragment
*
@ -63,11 +75,11 @@ public class PassphraseDialogFragment extends DialogFragment {
* @return
* @throws GeneralException
*/
public static PassphraseDialogFragment newInstance(Messenger messenger, long secretKeyId)
throws GeneralException {
public static PassphraseDialogFragment newInstance(Context context, Messenger messenger,
long secretKeyId) throws GeneralException {
// check if secret key has a passphrase
if (!(secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none)) {
if (!hasPassphrase(secretKeyId)) {
if (!hasPassphrase(context, secretKeyId)) {
throw new PGPMain.GeneralException("No passphrase! No passphrase dialog needed!");
}
}
@ -88,7 +100,7 @@ public class PassphraseDialogFragment extends DialogFragment {
* @param secretKeyId
* @return true if it has a passphrase
*/
private static boolean hasPassphrase(long secretKeyId) {
private static boolean hasPassphrase(Context context, long secretKeyId) {
// check if the key has no passphrase
try {
PGPSecretKey secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
@ -101,7 +113,7 @@ public class PassphraseDialogFragment extends DialogFragment {
Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
// cache empty passphrase
PGPMain.setCachedPassPhrase(secretKey.getKeyID(), "");
PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
return false;
}
@ -112,6 +124,11 @@ public class PassphraseDialogFragment extends DialogFragment {
return true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
/**
* Creates dialog
*/
@ -154,13 +171,13 @@ public class PassphraseDialogFragment extends DialogFragment {
View view = inflater.inflate(R.layout.passphrase, null);
alert.setView(view);
final EditText input = (EditText) view.findViewById(R.id.passphrase_passphrase);
mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dismiss();
String passPhrase = input.getText().toString();
String passPhrase = mPassphraseEditText.getText().toString();
long keyId;
if (secretKey != null) {
try {
@ -185,7 +202,7 @@ public class PassphraseDialogFragment extends DialogFragment {
// cache the new passphrase
Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
PGPMain.setCachedPassPhrase(keyId, passPhrase);
PassphraseCacheService.addCachedPassphrase(activity, keyId, passPhrase);
sendMessageToHandler(MESSAGE_OKAY);
}
@ -200,6 +217,32 @@ public class PassphraseDialogFragment extends DialogFragment {
return alert.create();
}
@Override
public void onActivityCreated(Bundle arg0) {
super.onActivityCreated(arg0);
// request focus and open soft keyboard
mPassphraseEditText.requestFocus();
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
mPassphraseEditText.setOnEditorActionListener(this);
}
/**
* Associate the "done" button on the soft keyboard with the okay button in the view
*/
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (EditorInfo.IME_ACTION_DONE == actionId) {
AlertDialog dialog = ((AlertDialog) getDialog());
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
bt.performClick();
return true;
}
return false;
}
/**
* Send message back to handler which is initialized in a activity
*
@ -218,4 +261,5 @@ public class PassphraseDialogFragment extends DialogFragment {
Log.w(Constants.TAG, "Messenger is null!", e);
}
}
}

View File

@ -29,12 +29,19 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import org.thialfihar.android.apg.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.TextView.OnEditorActionListener;
public class SetPassphraseDialogFragment extends DialogFragment {
public class SetPassphraseDialogFragment extends DialogFragment implements OnEditorActionListener {
private Messenger mMessenger;
private static final String ARG_MESSENGER = "messenger";
@ -44,6 +51,9 @@ public class SetPassphraseDialogFragment extends DialogFragment {
public static final String MESSAGE_NEW_PASSPHRASE = "new_passphrase";
private EditText mPassphraseEditText;
private EditText mPassphraseAgainEditText;
/**
* Creates new instance of this dialog fragment
*
@ -81,17 +91,17 @@ public class SetPassphraseDialogFragment extends DialogFragment {
LayoutInflater inflater = activity.getLayoutInflater();
View view = inflater.inflate(R.layout.passphrase_repeat, null);
final EditText input1 = (EditText) view.findViewById(R.id.passphrase_passphrase);
final EditText input2 = (EditText) view.findViewById(R.id.passphrase_passphrase_again);
alert.setView(view);
mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again);
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dismiss();
String passPhrase1 = input1.getText().toString();
String passPhrase2 = input2.getText().toString();
String passPhrase1 = mPassphraseEditText.getText().toString();
String passPhrase2 = mPassphraseAgainEditText.getText().toString();
if (!passPhrase1.equals(passPhrase2)) {
Toast.makeText(
activity,
@ -127,6 +137,32 @@ public class SetPassphraseDialogFragment extends DialogFragment {
return alert.create();
}
@Override
public void onActivityCreated(Bundle arg0) {
super.onActivityCreated(arg0);
// request focus and open soft keyboard
mPassphraseEditText.requestFocus();
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
mPassphraseAgainEditText.setOnEditorActionListener(this);
}
/**
* Associate the "done" button on the soft keyboard with the okay button in the view
*/
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (EditorInfo.IME_ACTION_DONE == actionId) {
AlertDialog dialog = ((AlertDialog) getDialog());
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
bt.performClick();
return true;
}
return false;
}
/**
* Send message back to handler which is initialized in a activity
*

View File

@ -23,6 +23,7 @@ import org.thialfihar.android.apg.helper.PGPMain;
import org.thialfihar.android.apg.helper.PGPConversionHelper;
import org.thialfihar.android.apg.service.ApgServiceHandler;
import org.thialfihar.android.apg.service.ApgService;
import org.thialfihar.android.apg.service.PassphraseCacheService;
import org.thialfihar.android.apg.ui.dialog.ProgressDialogFragment;
import org.thialfihar.android.apg.ui.widget.Editor.EditorListener;
import org.thialfihar.android.apg.util.Choice;
@ -259,7 +260,8 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
String passPhrase;
if (mEditors.getChildCount() > 0) {
PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
passPhrase = PGPMain.getCachedPassPhrase(masterKey.getKeyID());
passPhrase = PassphraseCacheService
.getCachedPassphrase(mActivity, masterKey.getKeyID());
data.putByteArray(ApgService.MASTER_KEY,
PGPConversionHelper.PGPSecretKeyToBytes(masterKey));

View File

@ -20,7 +20,6 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Vector;
import android.os.Parcel;
import android.os.Parcelable;