Merge branch 'master' into okhttp3

This commit is contained in:
Michal Kepkowski
2016-04-06 19:16:10 +02:00
52 changed files with 992 additions and 915 deletions

View File

@@ -49,7 +49,7 @@ dependencies {
compile 'com.getbase:floatingactionbutton:1.10.1'
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final'
compile 'com.splitwise:tokenautocomplete:2.0.5@aar'
compile 'com.splitwise:tokenautocomplete:2.0.7@aar'
compile 'com.github.pinball83:masked-edittext:1.0.3'
compile 'se.emilsjolander:stickylistheaders:2.7.0'
compile 'org.sufficientlysecure:html-textview:1.3'
@@ -100,7 +100,7 @@ dependencyVerification {
'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240',
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13',
'org.ocpsoft.prettytime:prettytime:ef7098d973ae78b57d1a22dc37d3b8a771bf030301300e24055d676b6cdc5e75',
'com.splitwise:tokenautocomplete:f86b8cd80e8fa1336f47d86b8ce862e1a40bdc8e206ac38c56c8e41a3d05a331',
'com.splitwise:tokenautocomplete:f56239588390f103b270b7c12361d99b06313a5a0410dc7f66e241ac4baf9baa',
'com.github.pinball83:masked-edittext:b1913d86482c7066ebb7831696773ac131865dc441cf8a3fc41d3b7d5691724e',
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
'org.sufficientlysecure:donations:96f8197bab26dfe41900d824f10f8f1914519cd62eedb77bdac5b223eccdf0a6',
@@ -145,8 +145,8 @@ android {
defaultConfig {
minSdkVersion 15
targetSdkVersion 23
versionCode 39200
versionName "3.9.2"
versionCode 39400
versionName "3.9.4"
applicationId "org.sufficientlysecure.keychain"
// the androidjunitrunner is broken regarding coverage, see here:
// https://code.google.com/p/android/issues/detail?id=170607

View File

@@ -76,6 +76,7 @@
<!-- other group (for free) -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
@@ -89,6 +90,15 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Keychain.Light">
<!-- broadcast receiver for Wi-Fi Connection -->
<receiver
android:name=".receiver.NetworkReceiver"
android:enabled="false"
android:exported="true" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
<!-- singleTop for NFC dispatch, see SecurityTokenOperationActivity -->
<activity
android:name=".ui.MainActivity"

View File

@@ -118,6 +118,7 @@ public final class Constants {
// keyserver sync settings
public static final String SYNC_CONTACTS = "syncContacts";
public static final String SYNC_KEYSERVER = "syncKeyserver";
public static final String ENABLE_WIFI_SYNC_ONLY = "enableWifiSyncOnly";
// other settings
public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm";
public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities";

View File

@@ -100,6 +100,11 @@ public class KeychainApplication extends Application {
// Add OpenKeychain account to Android to link contacts with keys and keyserver sync
createAccountIfNecessary(this);
if (Preferences.getKeyserverSyncEnabled(this)) {
// will update a keyserver sync if the interval has changed
KeyserverSyncAdapterService.enableKeyserverSync(this);
}
// if first time, enable keyserver and contact sync
if (Preferences.getPreferences(this).isFirstTime()) {
KeyserverSyncAdapterService.enableKeyserverSync(this);

View File

@@ -566,8 +566,6 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_size),
MSG_MF_ERROR_BAD_SECURITY_TOKEN_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_stripped),
MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master),
MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin),
MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty),
MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase),
MSG_MF_PIN (LogLevel.INFO, R.string.msg_mf_pin),
MSG_MF_ADMIN_PIN (LogLevel.INFO, R.string.msg_mf_admin_pin),

View File

@@ -18,6 +18,23 @@
package org.sufficientlysecure.keychain.pgp;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.sig.Features;
@@ -57,13 +74,12 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -71,22 +87,6 @@ import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Primes;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class is the single place where ALL operations that actually modify a PGP public or secret
* key take place.
@@ -1058,8 +1058,8 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_PASSPHRASE, indent);
indent += 1;
sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey,
cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent);
sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(),
saveParcel.mNewUnlock.mNewPassphrase, log, indent);
if (sKR == null) {
// The error has been logged above, just return a bad state
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
@@ -1191,76 +1191,6 @@ public class PgpKeyOperation {
}
private static PGPSecretKeyRing applyNewUnlock(
PGPSecretKeyRing sKR,
PGPPublicKey masterPublicKey,
PGPPrivateKey masterPrivateKey,
Passphrase passphrase,
ChangeUnlockParcel newUnlock,
OperationLog log, int indent) throws PGPException {
if (newUnlock.mNewPassphrase != null) {
sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent);
// if there is any old packet with notation data
if (hasNotationData(sKR)) {
log.add(LogType.MSG_MF_NOTATION_EMPTY, indent);
// add packet with EMPTY notation data (updates old one, but will be stripped later)
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
{ // set subpackets
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
hashedPacketsGen.setExportable(false, false);
sGen.setHashedSubpackets(hashedPacketsGen.generate());
}
sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey);
PGPSignature emptySig = sGen.generateCertification(masterPublicKey);
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR,
PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey));
}
return sKR;
}
if (newUnlock.mNewPin != null) {
sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent);
log.add(LogType.MSG_MF_NOTATION_PIN, indent);
// add packet with "pin" notation data
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
{ // set subpackets
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
hashedPacketsGen.setExportable(false, false);
hashedPacketsGen.setNotationData(false, true, "unlock.pin@sufficientlysecure.org", "1");
sGen.setHashedSubpackets(hashedPacketsGen.generate());
}
sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey);
PGPSignature emptySig = sGen.generateCertification(masterPublicKey);
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR,
PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey));
return sKR;
}
throw new UnsupportedOperationException("PIN passphrases not yet implemented!");
}
/** This method returns true iff the provided keyring has a local direct key signature
* with notation data.
*/
@@ -1294,8 +1224,7 @@ public class PgpKeyOperation {
PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
// noinspection unchecked
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
for (PGPSecretKey sKey : new IterableIterator<>(sKR.getSecretKeys())) {
log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent,
KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID()));

View File

@@ -35,6 +35,8 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import android.support.annotation.VisibleForTesting;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.SignatureSubpacketTags;
@@ -42,15 +44,22 @@ import org.bouncycastle.bcpg.UserAttributeSubpacketTags;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@@ -1310,4 +1319,37 @@ public class UncachedKeyRing {
|| algorithm == PGPPublicKey.ECDH;
}
// ONLY TO BE USED FOR TESTING!!
@VisibleForTesting
public static UncachedKeyRing forTestingOnlyAddDummyLocalSignature(
UncachedKeyRing uncachedKeyRing, String passphrase) throws Exception {
PGPSecretKeyRing sKR = (PGPSecretKeyRing) uncachedKeyRing.mRing;
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPPrivateKey masterPrivateKey = sKR.getSecretKey().extractPrivateKey(keyDecryptor);
PGPPublicKey masterPublicKey = uncachedKeyRing.mRing.getPublicKey();
// add packet with "pin" notation data
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
{ // set subpackets
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
hashedPacketsGen.setExportable(false, false);
hashedPacketsGen.setNotationData(false, true, "dummynotationdata", "some data");
sGen.setHashedSubpackets(hashedPacketsGen.generate());
}
sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey);
PGPSignature emptySig = sGen.generateCertification(masterPublicKey);
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR,
PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey));
return new UncachedKeyRing(sKR);
}
}

View File

@@ -54,7 +54,7 @@ import java.io.IOException;
*/
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db";
private static final int DATABASE_VERSION = 15;
private static final int DATABASE_VERSION = 16;
static Boolean apgHack = false;
private Context mContext;
@@ -279,40 +279,45 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER");
db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB");
case 7:
// consolidate
case 8:
// new table for allowed key ids in API
try {
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
} catch (Exception e) {
// never mind, the column probably already existed
}
case 9:
case 8:
// tbale name for user_ids changed to user_packets
db.execSQL("DROP TABLE IF EXISTS certs");
db.execSQL("DROP TABLE IF EXISTS user_ids");
db.execSQL(CREATE_USER_PACKETS);
db.execSQL(CREATE_CERTS);
case 10:
case 9:
// do nothing here, just consolidate
case 11:
case 10:
// fix problems in database, see #1402 for details
// https://github.com/open-keychain/open-keychain/issues/1402
db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
case 12:
case 11:
db.execSQL(CREATE_UPDATE_KEYS);
case 13:
case 12:
// do nothing here, just consolidate
case 14:
case 13:
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");");
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
+ UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
db.execSQL("CREATE INDEX verified_certs ON certs ("
+ CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
case 15:
case 14:
db.execSQL("ALTER TABLE user_packets ADD COLUMN name TEXT");
db.execSQL("ALTER TABLE user_packets ADD COLUMN email TEXT");
db.execSQL("ALTER TABLE user_packets ADD COLUMN comment TEXT");
case 15:
db.execSQL("CREATE INDEX uids_by_name ON user_packets (name COLLATE NOCASE)");
db.execSQL("CREATE INDEX uids_by_email ON user_packets (email COLLATE NOCASE)");
if (oldVersion == 14) {
// no consolidate necessary
return;
}
}
// always do consolidate after upgrade

View File

@@ -312,12 +312,14 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(KeyRings.EMAIL, Tables.USER_PACKETS + "." + UserPackets.EMAIL);
projectionMap.put(KeyRings.COMMENT, Tables.USER_PACKETS + "." + UserPackets.COMMENT);
projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID,
"(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups"
"(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups"
+ " WHERE dups." + UserPackets.MASTER_KEY_ID
+ " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND dups." + UserPackets.RANK + " = 0"
+ " AND dups." + UserPackets.USER_ID
+ " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID
+ " AND dups." + UserPackets.NAME
+ " = " + Tables.USER_PACKETS + "." + UserPackets.NAME + " COLLATE NOCASE"
+ " AND dups." + UserPackets.EMAIL
+ " = " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " COLLATE NOCASE"
+ ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID);
projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
projectionMap.put(KeyRings.PUBKEY_DATA,

View File

@@ -0,0 +1,52 @@
package org.sufficientlysecure.keychain.receiver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
import org.sufficientlysecure.keychain.util.Log;
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager conn = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = conn.getActiveNetworkInfo();
boolean isTypeWifi = (networkInfo.getType() == ConnectivityManager.TYPE_WIFI);
boolean isConnected = networkInfo.isConnected();
if (isTypeWifi && isConnected) {
// broadcaster receiver disabled
setWifiReceiverComponent(false, context);
Intent serviceIntent = new Intent(context, KeyserverSyncAdapterService.class);
serviceIntent.setAction(KeyserverSyncAdapterService.ACTION_SYNC_NOW);
context.startService(serviceIntent);
}
}
public void setWifiReceiverComponent(Boolean isEnabled, Context context) {
PackageManager pm = context.getPackageManager();
ComponentName compName = new ComponentName(context,
NetworkReceiver.class);
if (isEnabled) {
pm.setComponentEnabledSetting(compName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
Log.d(Constants.TAG, "Wifi Receiver is enabled!");
} else {
pm.setComponentEnabledSetting(compName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
Log.d(Constants.TAG, "Wifi Receiver is disabled!");
}
}
}

View File

@@ -144,12 +144,13 @@ public class ContactSyncAdapterService extends Service {
public static void requestContactsSync() {
// if user has disabled automatic sync, do nothing
if (!ContentResolver.getSyncAutomatically(
new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE),
ContactsContract.AUTHORITY)) {
boolean isSyncEnabled = ContentResolver.getSyncAutomatically(new Account
(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), ContactsContract.AUTHORITY);
if (!isSyncEnabled) {
return;
}
Bundle extras = new Bundle();
// no need to wait, do it immediately
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);

View File

@@ -11,11 +11,14 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.PeriodicSync;
import android.content.SyncResult;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -35,6 +38,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.receiver.NetworkReceiver;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -68,7 +72,7 @@ public class KeyserverSyncAdapterService extends Service {
private static final String ACTION_IGNORE_TOR = "ignore_tor";
private static final String ACTION_UPDATE_ALL = "update_all";
private static final String ACTION_SYNC_NOW = "sync_now";
public static final String ACTION_SYNC_NOW = "sync_now";
private static final String ACTION_DISMISS_NOTIFICATION = "cancel_sync";
private static final String ACTION_START_ORBOT = "start_orbot";
private static final String ACTION_CANCEL = "cancel";
@@ -176,8 +180,25 @@ public class KeyserverSyncAdapterService extends Service {
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
Log.d(Constants.TAG, "Performing a keyserver sync!");
Preferences prefs = Preferences.getPreferences(getContext());
// for a wifi-ONLY sync
if (prefs.getWifiOnlySync()) {
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
boolean isNotOnWifi = !(networkInfo.getType() == ConnectivityManager.TYPE_WIFI);
boolean isNotConnected = !(networkInfo.isConnected());
// if Wi-Fi connection doesn't exist then receiver is enabled
if (isNotOnWifi && isNotConnected) {
new NetworkReceiver().setWifiReceiverComponent(true, getContext());
return;
}
}
Log.d(Constants.TAG, "Performing a keyserver sync!");
PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this
.getSystemService(Context.POWER_SERVICE);
@SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20
@@ -509,6 +530,10 @@ public class KeyserverSyncAdapterService extends Service {
return builder.build();
}
/**
* creates a new sync if one does not exist, or updates an existing sync if the sync interval
* has changed.
*/
public static void enableKeyserverSync(Context context) {
Account account = KeychainApplication.createAccountIfNecessary(context);
@@ -519,12 +544,26 @@ public class KeyserverSyncAdapterService extends Service {
ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true);
ContentResolver.addPeriodicSync(
account,
Constants.PROVIDER_AUTHORITY,
new Bundle(),
SYNC_INTERVAL
);
boolean intervalChanged = false;
boolean syncExists = Preferences.getKeyserverSyncEnabled(context);
if (syncExists) {
long oldInterval = ContentResolver.getPeriodicSyncs(
account, Constants.PROVIDER_AUTHORITY).get(0).period;
if (oldInterval != SYNC_INTERVAL) {
intervalChanged = true;
}
}
if (!syncExists || intervalChanged) {
ContentResolver.addPeriodicSync(
account,
Constants.PROVIDER_AUTHORITY,
new Bundle(),
SYNC_INTERVAL
);
}
}
private boolean isSyncEnabled() {

View File

@@ -356,29 +356,21 @@ public class SaveKeyringParcel implements Parcelable {
// The new passphrase to use
public final Passphrase mNewPassphrase;
// A new pin to use. Must only contain [0-9]+
public final Passphrase mNewPin;
public ChangeUnlockParcel(Passphrase newPassphrase) {
this(newPassphrase, null);
}
public ChangeUnlockParcel(Passphrase newPassphrase, Passphrase newPin) {
if (newPassphrase == null && newPin == null) {
throw new RuntimeException("Cannot set both passphrase and pin. THIS IS A BUG!");
if (newPassphrase == null) {
throw new AssertionError("newPassphrase must be non-null. THIS IS A BUG!");
}
mNewPassphrase = newPassphrase;
mNewPin = newPin;
}
public ChangeUnlockParcel(Parcel source) {
mNewPassphrase = source.readParcelable(Passphrase.class.getClassLoader());
mNewPin = source.readParcelable(Passphrase.class.getClassLoader());
}
@Override
public void writeToParcel(Parcel destination, int flags) {
destination.writeParcelable(mNewPassphrase, flags);
destination.writeParcelable(mNewPin, flags);
}
@Override
@@ -397,9 +389,7 @@ public class SaveKeyringParcel implements Parcelable {
};
public String toString() {
return mNewPassphrase != null
? ("passphrase (" + mNewPassphrase + ")")
: ("pin (" + mNewPin + ")");
return "passphrase (" + mNewPassphrase + ")";
}
}

View File

@@ -43,6 +43,7 @@ import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -237,6 +238,30 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
mCodeEditText.setImeOptions(EditorInfo.IME_ACTION_DONE);
setupEditTextSuccessListener(mCodeEditText);
// prevent selection action mode, partially circumventing text selection bug
mCodeEditText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
});
TextView codeDisplayText = (TextView) view.findViewById(R.id.backup_code_display);
setupAutomaticLinebreak(codeDisplayText);

View File

@@ -28,7 +28,8 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity;
public class CertifyKeyActivity extends BaseActivity {
public static final String EXTRA_RESULT = "operation_result";
public static final String EXTRA_KEY_IDS = "extra_key_ids";
// For sending masterKeyIds to MultiUserIdsFragment to display list of keys
public static final String EXTRA_KEY_IDS = MultiUserIdsFragment.EXTRA_KEY_IDS ;
public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id";
@Override

View File

@@ -62,58 +62,26 @@ import java.util.ArrayList;
import java.util.Date;
public class CertifyKeyFragment
extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult>
implements LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_CHECK_STATES = "check_states";
extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> {
private CheckBox mUploadKeyCheckbox;
ListView mUserIds;
private CertifyKeySpinner mCertifyKeySpinner;
private long[] mPubMasterKeyIds;
public static final String[] USER_IDS_PROJECTION = new String[]{
UserPackets._ID,
UserPackets.MASTER_KEY_ID,
UserPackets.USER_ID,
UserPackets.IS_PRIMARY,
UserPackets.IS_REVOKED
};
private static final int INDEX_MASTER_KEY_ID = 1;
private static final int INDEX_USER_ID = 2;
@SuppressWarnings("unused")
private static final int INDEX_IS_PRIMARY = 3;
@SuppressWarnings("unused")
private static final int INDEX_IS_REVOKED = 4;
private MultiUserIdsAdapter mUserIdsAdapter;
private MultiUserIdsFragment mMultiUserIdsFragment;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS);
if (mPubMasterKeyIds == null) {
Log.e(Constants.TAG, "List of key ids to certify missing!");
getActivity().finish();
return;
}
ArrayList<Boolean> checkedStates;
if (savedInstanceState != null) {
checkedStates = (ArrayList<Boolean>) savedInstanceState.getSerializable(ARG_CHECK_STATES);
// key spinner and the checkbox keep their own state
} else {
checkedStates = null;
if (savedInstanceState == null) {
// preselect certify key id if given
long certifyKeyId = getActivity().getIntent()
.getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
if (certifyKeyId != Constants.key.none) {
try {
CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId);
CachedPublicKeyRing key = (new ProviderHelper(getActivity()))
.getCachedPublicKeyRing(certifyKeyId);
if (key.canCertify()) {
mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId);
}
@@ -121,15 +89,8 @@ public class CertifyKeyFragment
Log.e(Constants.TAG, "certify certify check failed", e);
}
}
}
mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates);
mUserIds.setAdapter(mUserIdsAdapter);
mUserIds.setDividerHeight(0);
getLoaderManager().initLoader(0, null, this);
OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT);
if (result != null) {
// display result from import
@@ -137,22 +98,14 @@ public class CertifyKeyFragment
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
ArrayList<Boolean> states = mUserIdsAdapter.getCheckStates();
// no proper parceling method available :(
outState.putSerializable(ARG_CHECK_STATES, states);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.certify_key_fragment, null);
mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner);
mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox);
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
mMultiUserIdsFragment = (MultiUserIdsFragment)
getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment);
// make certify image gray, like action icons
ImageView vActionCertifyImage =
@@ -183,128 +136,11 @@ public class CertifyKeyFragment
return view;
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri uri = UserPackets.buildUserIdsUri();
String selection, ids[];
{
// generate placeholders and string selection args
ids = new String[mPubMasterKeyIds.length];
StringBuilder placeholders = new StringBuilder("?");
for (int i = 0; i < mPubMasterKeyIds.length; i++) {
ids[i] = Long.toString(mPubMasterKeyIds[i]);
if (i != 0) {
placeholders.append(",?");
}
}
// put together selection string
selection = UserPackets.IS_REVOKED + " = 0" + " AND "
+ Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ " IN (" + placeholders + ")";
}
return new CursorLoader(getActivity(), uri,
USER_IDS_PROJECTION, selection, ids,
Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC"
+ ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC"
);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
MatrixCursor matrix = new MatrixCursor(new String[]{
"_id", "user_data", "grouped"
}) {
@Override
public byte[] getBlob(int column) {
return super.getBlob(column);
}
};
data.moveToFirst();
long lastMasterKeyId = 0;
String lastName = "";
ArrayList<String> uids = new ArrayList<>();
boolean header = true;
// Iterate over all rows
while (!data.isAfterLast()) {
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
String userId = data.getString(INDEX_USER_ID);
KeyRing.UserId pieces = KeyRing.splitUserId(userId);
// Two cases:
boolean grouped = masterKeyId == lastMasterKeyId;
boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name);
// Remember for next loop
lastName = pieces.name;
Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped"));
if (!subGrouped) {
// 1. This name should NOT be grouped with the previous, so we flush the buffer
Parcel p = Parcel.obtain();
p.writeStringList(uids);
byte[] d = p.marshall();
p.recycle();
matrix.addRow(new Object[]{
lastMasterKeyId, d, header ? 1 : 0
});
// indicate that we have a header for this masterKeyId
header = false;
// Now clear the buffer, and add the new user id, for the next round
uids.clear();
}
// 2. This name should be grouped with the previous, just add to buffer
uids.add(userId);
lastMasterKeyId = masterKeyId;
// If this one wasn't grouped, the next one's gotta be a header
if (!grouped) {
header = true;
}
// Regardless of the outcome, move to next entry
data.moveToNext();
}
// If there is anything left in the buffer, flush it one last time
if (!uids.isEmpty()) {
Parcel p = Parcel.obtain();
p.writeStringList(uids);
byte[] d = p.marshall();
p.recycle();
matrix.addRow(new Object[]{
lastMasterKeyId, d, header ? 1 : 0
});
}
mUserIdsAdapter.swapCursor(matrix);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mUserIdsAdapter.swapCursor(null);
}
@Override
public CertifyActionsParcel createOperationInput() {
// Bail out if there is not at least one user id selected
ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions();
ArrayList<CertifyAction> certifyActions = mMultiUserIdsFragment.getSelectedCertifyActions();
if (certifyActions.isEmpty()) {
Notify.create(getActivity(), "No identities selected!",
Notify.Style.ERROR).show();

View File

@@ -278,17 +278,17 @@ public class CreateKeyFinalFragment extends Fragment {
2048, null, KeyFlags.AUTHENTICATION, 0L));
// use empty passphrase
saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null);
saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase());
} else {
saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
4096, null, KeyFlags.CERTIFY_OTHER, 0L));
3072, null, KeyFlags.CERTIFY_OTHER, 0L));
saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
4096, null, KeyFlags.SIGN_DATA, 0L));
3072, null, KeyFlags.SIGN_DATA, 0L));
saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
saveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null
? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null)
? new ChangeUnlockParcel(createKeyActivity.mPassphrase)
: null;
}
String userId = KeyRing.createUserId(

View File

@@ -339,8 +339,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
// cache new returned passphrase!
mSaveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(
(Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE),
null
(Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE)
);
}
}
@@ -562,15 +561,9 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
}
private void addSubkey() {
boolean willBeMasterKey;
if (mSubkeysAdapter != null) {
willBeMasterKey = mSubkeysAdapter.getCount() == 0 && mSubkeysAddedAdapter.getCount() == 0;
} else {
willBeMasterKey = mSubkeysAddedAdapter.getCount() == 0;
}
// new subkey will never be a masterkey, as masterkey cannot be removed
AddSubkeyDialogFragment addSubkeyDialogFragment =
AddSubkeyDialogFragment.newInstance(willBeMasterKey);
AddSubkeyDialogFragment.newInstance(false);
addSubkeyDialogFragment
.setOnAlgorithmSelectedListener(
new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() {

View File

@@ -79,9 +79,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign);
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
mEncryptKeyView.setThreshold(1); // Start working from first character
// TODO: workaround for bug in TokenAutoComplete,
// see https://github.com/open-keychain/open-keychain/issues/1636
mEncryptKeyView.setDeletionStyle(TokenCompleteTextView.TokenDeleteStyle.ToString);
final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon);
mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() {

View File

@@ -0,0 +1,223 @@
package org.sufficientlysecure.keychain.ui;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
public class MultiUserIdsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>{
public static final String ARG_CHECK_STATES = "check_states";
public static final String EXTRA_KEY_IDS = "extra_key_ids";
private boolean checkboxVisibility = true;
ListView mUserIds;
private MultiUserIdsAdapter mUserIdsAdapter;
private long[] mPubMasterKeyIds;
public static final String[] USER_IDS_PROJECTION = new String[]{
KeychainContract.UserPackets._ID,
KeychainContract.UserPackets.MASTER_KEY_ID,
KeychainContract.UserPackets.USER_ID,
KeychainContract.UserPackets.IS_PRIMARY,
KeychainContract.UserPackets.IS_REVOKED
};
private static final int INDEX_MASTER_KEY_ID = 1;
private static final int INDEX_USER_ID = 2;
@SuppressWarnings("unused")
private static final int INDEX_IS_PRIMARY = 3;
@SuppressWarnings("unused")
private static final int INDEX_IS_REVOKED = 4;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.multi_user_ids_fragment, null);
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(EXTRA_KEY_IDS);
if (mPubMasterKeyIds == null) {
Log.e(Constants.TAG, "List of key ids to certify missing!");
getActivity().finish();
return;
}
ArrayList<Boolean> checkedStates = null;
if (savedInstanceState != null) {
checkedStates = (ArrayList<Boolean>) savedInstanceState.getSerializable(ARG_CHECK_STATES);
}
mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates, checkboxVisibility);
mUserIds.setAdapter(mUserIdsAdapter);
mUserIds.setDividerHeight(0);
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
ArrayList<Boolean> states = mUserIdsAdapter.getCheckStates();
// no proper parceling method available :(
outState.putSerializable(ARG_CHECK_STATES, states);
}
public ArrayList<CertifyActionsParcel.CertifyAction> getSelectedCertifyActions() {
if (!checkboxVisibility) {
throw new AssertionError("Item selection not allowed");
}
return mUserIdsAdapter.getSelectedCertifyActions();
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri uri = KeychainContract.UserPackets.buildUserIdsUri();
String selection, ids[];
{
// generate placeholders and string selection args
ids = new String[mPubMasterKeyIds.length];
StringBuilder placeholders = new StringBuilder("?");
for (int i = 0; i < mPubMasterKeyIds.length; i++) {
ids[i] = Long.toString(mPubMasterKeyIds[i]);
if (i != 0) {
placeholders.append(",?");
}
}
// put together selection string
selection = KeychainContract.UserPackets.IS_REVOKED + " = 0" + " AND "
+ KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID
+ " IN (" + placeholders + ")";
}
return new CursorLoader(getActivity(), uri,
USER_IDS_PROJECTION, selection, ids,
KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + " ASC"
+ ", " + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.USER_ID + " ASC"
);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
MatrixCursor matrix = new MatrixCursor(new String[]{
"_id", "user_data", "grouped"
}) {
@Override
public byte[] getBlob(int column) {
return super.getBlob(column);
}
};
data.moveToFirst();
long lastMasterKeyId = 0;
String lastName = "";
ArrayList<String> uids = new ArrayList<>();
boolean header = true;
// Iterate over all rows
while (!data.isAfterLast()) {
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
String userId = data.getString(INDEX_USER_ID);
KeyRing.UserId pieces = KeyRing.splitUserId(userId);
// Two cases:
boolean grouped = masterKeyId == lastMasterKeyId;
boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name);
// Remember for next loop
lastName = pieces.name;
Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped"));
if (!subGrouped) {
// 1. This name should NOT be grouped with the previous, so we flush the buffer
Parcel p = Parcel.obtain();
p.writeStringList(uids);
byte[] d = p.marshall();
p.recycle();
matrix.addRow(new Object[]{
lastMasterKeyId, d, header ? 1 : 0
});
// indicate that we have a header for this masterKeyId
header = false;
// Now clear the buffer, and add the new user id, for the next round
uids.clear();
}
// 2. This name should be grouped with the previous, just add to buffer
uids.add(userId);
lastMasterKeyId = masterKeyId;
// If this one wasn't grouped, the next one's gotta be a header
if (!grouped) {
header = true;
}
// Regardless of the outcome, move to next entry
data.moveToNext();
}
// If there is anything left in the buffer, flush it one last time
if (!uids.isEmpty()) {
Parcel p = Parcel.obtain();
p.writeStringList(uids);
byte[] d = p.marshall();
p.recycle();
matrix.addRow(new Object[]{
lastMasterKeyId, d, header ? 1 : 0
});
}
mUserIdsAdapter.swapCursor(matrix);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mUserIdsAdapter.swapCursor(null);
}
public void setCheckboxVisibility(boolean checkboxVisibility) {
this.checkboxVisibility = checkboxVisibility;
}
}

View File

@@ -19,8 +19,6 @@
package org.sufficientlysecure.keychain.ui;
import java.util.List;
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -49,6 +47,7 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
@@ -59,6 +58,8 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
import java.util.List;
public class SettingsActivity extends AppCompatPreferenceActivity {
public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005;
@@ -405,7 +406,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
}
/**
* This fragment shows the keyserver/contacts sync preferences
* This fragment shows the keyserver/wifi-only-sync/contacts sync preferences
*/
public static class SyncPrefsFragment extends PresetPreferenceFragment {
@@ -422,8 +423,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
super.onResume();
// this needs to be done in onResume since the user can change sync values from Android
// settings and we need to reflect that change when the user navigates back
AccountManager manager = AccountManager.get(getActivity());
final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0];
final Account account = KeychainApplication.createAccountIfNecessary(getActivity());
// for keyserver sync
initializeSyncCheckBox(
(SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER),
@@ -441,8 +441,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
private void initializeSyncCheckBox(final SwitchPreference syncCheckBox,
final Account account,
final String authority) {
boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority)
&& checkContactsPermission(authority);
// account is null if it could not be created for some reason
boolean syncEnabled =
account != null
&& ContentResolver.getSyncAutomatically(account, authority)
&& checkContactsPermission(authority);
syncCheckBox.setChecked(syncEnabled);
setSummary(syncCheckBox, authority, syncEnabled);
@@ -464,6 +467,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
return false;
}
} else {
if (account == null) {
// if account could not be created for some reason,
// we can't have our sync
return false;
}
// disable syncs
ContentResolver.setSyncAutomatically(account, authority, false);
// immediately delete any linked contacts

View File

@@ -31,10 +31,7 @@ import android.widget.Spinner;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.UploadResult;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
@@ -53,7 +50,6 @@ public class UploadKeyActivity extends BaseActivity
// CryptoOperationHelper.Callback vars
private String mKeyserver;
private long mMasterKeyId;
private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
@Override
@@ -63,6 +59,10 @@ public class UploadKeyActivity extends BaseActivity
mUploadButton = findViewById(R.id.upload_key_action_upload);
mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
MultiUserIdsFragment mMultiUserIdsFragment = (MultiUserIdsFragment)
getSupportFragmentManager().findFragmentById(R.id.multi_user_ids_fragment);
mMultiUserIdsFragment.setCheckboxVisibility(false);
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
.getKeyServers()
@@ -89,15 +89,6 @@ public class UploadKeyActivity extends BaseActivity
return;
}
try {
mMasterKeyId = new ProviderHelper(this).getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(mDataUri)).getMasterKeyId();
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "Intent data pointed to bad key!");
finish();
return;
}
}
@Override
@@ -136,7 +127,9 @@ public class UploadKeyActivity extends BaseActivity
@Override
public UploadKeyringParcel createOperationInput() {
return new UploadKeyringParcel(mKeyserver, mMasterKeyId);
long[] masterKeyIds = getIntent().getLongArrayExtra(MultiUserIdsFragment.EXTRA_KEY_IDS);
return new UploadKeyringParcel(mKeyserver, masterKeyIds[0]);
}
@Override

View File

@@ -32,6 +32,8 @@ import android.app.ActivityOptions;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.AsyncTask;
@@ -469,8 +471,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
// use new passphrase!
mSaveKeyringParcel.mNewUnlock = new SaveKeyringParcel.ChangeUnlockParcel(
(Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE),
null
(Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE)
);
mEditOpHelper.cryptoOperation();
@@ -924,6 +925,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
}
mPhoto.setImageBitmap(photo);
mPhoto.setColorFilter(getResources().getColor(R.color.toolbar_photo_tint), PorterDuff.Mode.SRC_ATOP);
mPhotoLayout.setVisibility(View.VISIBLE);
}
};

View File

@@ -238,7 +238,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
// let user choose application
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.setType(Constants.MIME_TYPE_KEYS);
// NOTE: Don't use Intent.EXTRA_TEXT to send the key
// better send it via a Uri!
@@ -455,8 +455,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
private void uploadToKeyserver() {
long keyId;
try {
keyId = new ProviderHelper(getActivity())
.getCachedPublicKeyRing(mDataUri)
.extractOrGetMasterKeyId();
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
Notify.create(getActivity(), "key not found", Style.ERROR).show();
return;
}
Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class);
uploadIntent.setData(mDataUri);
uploadIntent.putExtra(MultiUserIdsFragment.EXTRA_KEY_IDS, new long[]{keyId});
startActivityForResult(uploadIntent, 0);
}

View File

@@ -333,14 +333,6 @@ public class KeyAdapter extends CursorAdapter {
return mUserId.email;
}
}
// TODO: workaround for bug in TokenAutoComplete,
// see https://github.com/open-keychain/open-keychain/issues/1636
@Override
public String toString() {
return " ";
}
}
public static String[] getProjectionWith(String[] projection) {

View File

@@ -39,6 +39,7 @@ import java.util.ArrayList;
public class MultiUserIdsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private final ArrayList<Boolean> mCheckStates;
private boolean checkboxVisibility = true;
public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates) {
super(context, c, flags);
@@ -46,6 +47,11 @@ public class MultiUserIdsAdapter extends CursorAdapter {
mCheckStates = preselectStates == null ? new ArrayList<Boolean>() : preselectStates;
}
public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates, boolean checkboxVisibility) {
this(context,c,flags,preselectStates);
this.checkboxVisibility = checkboxVisibility;
}
@Override
public Cursor swapCursor(Cursor newCursor) {
if (newCursor != null) {
@@ -138,6 +144,7 @@ public class MultiUserIdsAdapter extends CursorAdapter {
}
});
vCheckBox.setClickable(false);
vCheckBox.setVisibility(checkboxVisibility?View.VISIBLE:View.GONE);
View vUidBody = view.findViewById(R.id.user_id_body);
vUidBody.setClickable(true);

View File

@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter;
import android.app.Activity;
import android.content.Context;
import android.graphics.Typeface;
import android.support.v4.app.FragmentActivity;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
@@ -32,6 +33,7 @@ import android.widget.TextView;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import java.util.Calendar;
@@ -65,10 +67,11 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
public SaveKeyringParcel.SubkeyAdd mModel;
}
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// Not recycled, inflate a new view
convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, null);
convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, parent, false);
final ViewHolder holder = new ViewHolder();
holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id);
holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details);
@@ -88,16 +91,8 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
vStatus.setVisibility(View.GONE);
convertView.setTag(holder);
holder.vDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// remove reference model item from adapter (data and notify about change)
SubkeysAddedAdapter.this.remove(holder.mModel);
}
});
}
final ViewHolder holder = (ViewHolder) convertView.getTag();
// save reference to model item
@@ -113,8 +108,41 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
boolean isMasterKey = mNewKeyring && position == 0;
if (isMasterKey) {
holder.vKeyId.setTypeface(null, Typeface.BOLD);
holder.vDelete.setImageResource(R.drawable.ic_change_grey_24dp);
holder.vDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// swapping out the old master key with newly set master key
AddSubkeyDialogFragment addSubkeyDialogFragment =
AddSubkeyDialogFragment.newInstance(true);
addSubkeyDialogFragment
.setOnAlgorithmSelectedListener(
new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() {
@Override
public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) {
// calculate manually as the provided position variable
// is not always accurate
int pos = SubkeysAddedAdapter.this.getPosition(holder.mModel);
SubkeysAddedAdapter.this.remove(holder.mModel);
SubkeysAddedAdapter.this.insert(newSubkey, pos);
}
}
);
addSubkeyDialogFragment.show(
((FragmentActivity)mActivity).getSupportFragmentManager()
, "addSubkeyDialog");
}
});
} else {
holder.vKeyId.setTypeface(null, Typeface.NORMAL);
holder.vDelete.setImageResource(R.drawable.ic_close_grey_24dp);
holder.vDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// remove reference model item from adapter (data and notify about change)
SubkeysAddedAdapter.this.remove(holder.mModel);
}
});
}
holder.vKeyId.setText(R.string.edit_key_new_subkey);

View File

@@ -26,6 +26,7 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -65,6 +66,16 @@ public abstract class BaseActivity extends AppCompatActivity {
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home :
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
public static void onResumeChecks(Context context) {
KeyserverSyncAdapterService.cancelUpdates(context);
// in case user has disabled sync from Android account settings

View File

@@ -17,25 +17,26 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.annotation.TargetApi;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.os.Build;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TableRow;
import android.widget.TextView;
@@ -49,14 +50,18 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.util.Choice;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;
public class AddSubkeyDialogFragment extends DialogFragment {
public interface OnAlgorithmSelectedListener {
public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey);
void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey);
}
public enum SupportedKeyType {
RSA_2048, RSA_3072, RSA_4096, ECC_P256, ECC_P521
}
private static final String ARG_WILL_BE_MASTER_KEY = "will_be_master_key";
@@ -66,18 +71,12 @@ public class AddSubkeyDialogFragment extends DialogFragment {
private CheckBox mNoExpiryCheckBox;
private TableRow mExpiryRow;
private DatePicker mExpiryDatePicker;
private Spinner mAlgorithmSpinner;
private View mKeySizeRow;
private Spinner mKeySizeSpinner;
private View mCurveRow;
private Spinner mCurveSpinner;
private TextView mCustomKeyTextView;
private EditText mCustomKeyEditText;
private TextView mCustomKeyInfoTextView;
private CheckBox mFlagCertify;
private CheckBox mFlagSign;
private CheckBox mFlagEncrypt;
private CheckBox mFlagAuthenticate;
private Spinner mKeyTypeSpinner;
private RadioGroup mUsageRadioGroup;
private RadioButton mUsageNone;
private RadioButton mUsageSign;
private RadioButton mUsageEncrypt;
private RadioButton mUsageSignAndEncrypt;
private boolean mWillBeMasterKey;
@@ -96,6 +95,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
return frag;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity context = getActivity();
@@ -106,25 +107,27 @@ public class AddSubkeyDialogFragment extends DialogFragment {
CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context);
@SuppressLint("InflateParams")
View view = mInflater.inflate(R.layout.add_subkey_dialog, null);
dialog.setView(view);
dialog.setTitle(R.string.title_add_subkey);
mNoExpiryCheckBox = (CheckBox) view.findViewById(R.id.add_subkey_no_expiry);
mExpiryRow = (TableRow) view.findViewById(R.id.add_subkey_expiry_row);
mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker);
mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm);
mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size);
mCurveSpinner = (Spinner) view.findViewById(R.id.add_subkey_curve);
mKeySizeRow = view.findViewById(R.id.add_subkey_row_size);
mCurveRow = view.findViewById(R.id.add_subkey_row_curve);
mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label);
mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input);
mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info);
mFlagCertify = (CheckBox) view.findViewById(R.id.add_subkey_flag_certify);
mFlagSign = (CheckBox) view.findViewById(R.id.add_subkey_flag_sign);
mFlagEncrypt = (CheckBox) view.findViewById(R.id.add_subkey_flag_encrypt);
mFlagAuthenticate = (CheckBox) view.findViewById(R.id.add_subkey_flag_authenticate);
mKeyTypeSpinner = (Spinner) view.findViewById(R.id.add_subkey_type);
mUsageRadioGroup = (RadioGroup) view.findViewById(R.id.add_subkey_usage_group);
mUsageNone = (RadioButton) view.findViewById(R.id.add_subkey_usage_none);
mUsageSign = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign);
mUsageEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_encrypt);
mUsageSignAndEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign_and_encrypt);
if(mWillBeMasterKey) {
dialog.setTitle(R.string.title_change_master_key);
mUsageNone.setVisibility(View.VISIBLE);
mUsageNone.setChecked(true);
} else {
dialog.setTitle(R.string.title_add_subkey);
}
mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
@@ -143,65 +146,24 @@ public class AddSubkeyDialogFragment extends DialogFragment {
mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime());
{
ArrayList<Choice<Algorithm>> choices = new ArrayList<>();
choices.add(new Choice<>(Algorithm.DSA, getResources().getString(
R.string.dsa)));
if (!mWillBeMasterKey) {
choices.add(new Choice<>(Algorithm.ELGAMAL, getResources().getString(
R.string.elgamal)));
}
choices.add(new Choice<>(Algorithm.RSA, getResources().getString(
R.string.rsa)));
choices.add(new Choice<>(Algorithm.ECDSA, getResources().getString(
R.string.ecdsa)));
choices.add(new Choice<>(Algorithm.ECDH, getResources().getString(
R.string.ecdh)));
ArrayAdapter<Choice<Algorithm>> adapter = new ArrayAdapter<>(context,
ArrayList<Choice<SupportedKeyType>> choices = new ArrayList<>();
choices.add(new Choice<>(SupportedKeyType.RSA_2048, getResources().getString(
R.string.rsa_2048), getResources().getString(R.string.rsa_2048_description_html)));
choices.add(new Choice<>(SupportedKeyType.RSA_3072, getResources().getString(
R.string.rsa_3072), getResources().getString(R.string.rsa_3072_description_html)));
choices.add(new Choice<>(SupportedKeyType.RSA_4096, getResources().getString(
R.string.rsa_4096), getResources().getString(R.string.rsa_4096_description_html)));
choices.add(new Choice<>(SupportedKeyType.ECC_P256, getResources().getString(
R.string.ecc_p256), getResources().getString(R.string.ecc_p256_description_html)));
choices.add(new Choice<>(SupportedKeyType.ECC_P521, getResources().getString(
R.string.ecc_p521), getResources().getString(R.string.ecc_p521_description_html)));
TwoLineArrayAdapter adapter = new TwoLineArrayAdapter(context,
android.R.layout.simple_spinner_item, choices);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mAlgorithmSpinner.setAdapter(adapter);
// make RSA the default
mKeyTypeSpinner.setAdapter(adapter);
// make RSA 3072 the default
for (int i = 0; i < choices.size(); ++i) {
if (choices.get(i).getId() == Algorithm.RSA) {
mAlgorithmSpinner.setSelection(i);
break;
}
}
}
// dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change
ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item,
new ArrayList<CharSequence>(Arrays.asList(getResources().getStringArray(R.array.rsa_key_size_spinner_values))));
keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mKeySizeSpinner.setAdapter(keySizeAdapter);
mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length
{
ArrayList<Choice<Curve>> choices = new ArrayList<>();
choices.add(new Choice<>(Curve.NIST_P256, getResources().getString(
R.string.key_curve_nist_p256)));
choices.add(new Choice<>(Curve.NIST_P384, getResources().getString(
R.string.key_curve_nist_p384)));
choices.add(new Choice<>(Curve.NIST_P521, getResources().getString(
R.string.key_curve_nist_p521)));
/* @see SaveKeyringParcel
choices.add(new Choice<Curve>(Curve.BRAINPOOL_P256, getResources().getString(
R.string.key_curve_bp_p256)));
choices.add(new Choice<Curve>(Curve.BRAINPOOL_P384, getResources().getString(
R.string.key_curve_bp_p384)));
choices.add(new Choice<Curve>(Curve.BRAINPOOL_P512, getResources().getString(
R.string.key_curve_bp_p512)));
*/
ArrayAdapter<Choice<Curve>> adapter = new ArrayAdapter<>(context,
android.R.layout.simple_spinner_item, choices);
mCurveSpinner.setAdapter(adapter);
// make NIST P-256 the default
for (int i = 0; i < choices.size(); ++i) {
if (choices.get(i).getId() == Curve.NIST_P256) {
mCurveSpinner.setSelection(i);
if (choices.get(i).getId() == SupportedKeyType.RSA_3072) {
mKeyTypeSpinner.setSelection(i);
break;
}
}
@@ -215,45 +177,35 @@ public class AddSubkeyDialogFragment extends DialogFragment {
final AlertDialog alertDialog = dialog.show();
mCustomKeyEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
mKeyTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
setOkButtonAvailability(alertDialog);
}
});
mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setCustomKeyVisibility();
setOkButtonAvailability(alertDialog);
// noinspection unchecked
SupportedKeyType keyType = ((Choice<SupportedKeyType>) parent.getSelectedItem()).getId();
// RadioGroup.getCheckedRadioButtonId() gives the wrong RadioButton checked
// when programmatically unchecking children radio buttons. Clearing all is the only option.
mUsageRadioGroup.clearCheck();
if(mWillBeMasterKey) {
mUsageNone.setChecked(true);
}
if (keyType == SupportedKeyType.ECC_P521 || keyType == SupportedKeyType.ECC_P256) {
mUsageSignAndEncrypt.setEnabled(false);
if (mWillBeMasterKey) {
mUsageEncrypt.setEnabled(false);
}
} else {
// need to enable if previously disabled for ECC masterkey
mUsageEncrypt.setEnabled(true);
mUsageSignAndEncrypt.setEnabled(true);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
updateUiForAlgorithm(((Choice<Algorithm>) parent.getSelectedItem()).getId());
setCustomKeyVisibility();
setOkButtonAvailability(alertDialog);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
public void onNothingSelected(AdapterView<?> parent) {}
});
return alertDialog;
@@ -269,36 +221,74 @@ public class AddSubkeyDialogFragment extends DialogFragment {
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mFlagCertify.isChecked() && !mFlagSign.isChecked()
&& !mFlagEncrypt.isChecked() && !mFlagAuthenticate.isChecked()) {
Toast.makeText(getActivity(), R.string.edit_key_select_flag, Toast.LENGTH_LONG).show();
if (mUsageRadioGroup.getCheckedRadioButtonId() == -1) {
Toast.makeText(getActivity(), R.string.edit_key_select_usage, Toast.LENGTH_LONG).show();
return;
}
Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId();
// noinspection unchecked
SupportedKeyType keyType = ((Choice<SupportedKeyType>) mKeyTypeSpinner.getSelectedItem()).getId();
Curve curve = null;
Integer keySize = null;
// For EC keys, add a curve
if (algorithm == Algorithm.ECDH || algorithm == Algorithm.ECDSA) {
curve = ((Choice<Curve>) mCurveSpinner.getSelectedItem()).getId();
// Otherwise, get a keysize
} else {
keySize = getProperKeyLength(algorithm, getSelectedKeyLength());
Algorithm algorithm = null;
// set keysize & curve, for RSA & ECC respectively
switch (keyType) {
case RSA_2048: {
keySize = 2048;
break;
}
case RSA_3072: {
keySize = 3072;
break;
}
case RSA_4096: {
keySize = 4096;
break;
}
case ECC_P256: {
curve = Curve.NIST_P256;
break;
}
case ECC_P521: {
curve = Curve.NIST_P521;
break;
}
}
// set algorithm
switch (keyType) {
case RSA_2048:
case RSA_3072:
case RSA_4096: {
algorithm = Algorithm.RSA;
break;
}
case ECC_P256:
case ECC_P521: {
if(mUsageEncrypt.isChecked()) {
algorithm = Algorithm.ECDH;
} else {
algorithm = Algorithm.ECDSA;
}
break;
}
}
// set flags
int flags = 0;
if (mFlagCertify.isChecked()) {
if (mWillBeMasterKey) {
flags |= KeyFlags.CERTIFY_OTHER;
}
if (mFlagSign.isChecked()) {
if (mUsageSign.isChecked()) {
flags |= KeyFlags.SIGN_DATA;
}
if (mFlagEncrypt.isChecked()) {
} else if (mUsageEncrypt.isChecked()) {
flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
} else if (mUsageSignAndEncrypt.isChecked()) {
flags |= KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
}
if (mFlagAuthenticate.isChecked()) {
flags |= KeyFlags.AUTHENTICATION;
}
long expiry;
if (mNoExpiryCheckBox.isChecked()) {
@@ -332,206 +322,29 @@ public class AddSubkeyDialogFragment extends DialogFragment {
}
}
private int getSelectedKeyLength() {
final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem();
final String customLengthString = getResources().getString(R.string.key_size_custom);
final boolean customSelected = customLengthString.equals(selectedItemString);
String keyLengthString = customSelected ? mCustomKeyEditText.getText().toString() : selectedItemString;
int keySize;
try {
keySize = Integer.parseInt(keyLengthString);
} catch (NumberFormatException e) {
keySize = 0;
private class TwoLineArrayAdapter extends ArrayAdapter<Choice<SupportedKeyType>> {
public TwoLineArrayAdapter(Context context, int resource, List<Choice<SupportedKeyType>> objects) {
super(context, resource, objects);
}
return keySize;
}
/**
* <h3>RSA</h3>
* <p>for RSA algorithm, key length must be greater than 2048. Possibility to generate keys bigger
* than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check
* <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and
* <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a
* multiplicity of 8.</p>
* <h3>ElGamal</h3>
* <p>For ElGamal algorithm, supported key lengths are 2048, 3072, 4096 or 8192 bits.</p>
* <h3>DSA</h3>
* <p>For DSA algorithm key length must be between 2048 and 3072. Also, it must me dividable by 64.</p>
*
* @return correct key length, according to BouncyCastle specification. Returns <code>-1</code>, if key length is
* inappropriate.
*/
private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) {
final int[] elGamalSupportedLengths = {2048, 3072, 4096, 8192};
int properKeyLength = -1;
switch (algorithm) {
case RSA: {
if (currentKeyLength >= 2048 && currentKeyLength <= 16384) {
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
}
break;
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
// inflate view if not given one
if (convertView == null) {
convertView = getActivity().getLayoutInflater()
.inflate(R.layout.two_line_spinner_dropdown_item, parent, false);
}
case ELGAMAL: {
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
}
int minimalValue = Integer.MAX_VALUE;
int minimalIndex = -1;
for (int i = 0; i < elGammalKeyDiff.length; i++) {
if (elGammalKeyDiff[i] <= minimalValue) {
minimalValue = elGammalKeyDiff[i];
minimalIndex = i;
}
}
properKeyLength = elGamalSupportedLengths[minimalIndex];
break;
}
case DSA: {
// Bouncy Castle supports 4096 maximum
if (currentKeyLength >= 2048 && currentKeyLength <= 4096) {
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
}
break;
}
}
return properKeyLength;
}
private void setOkButtonAvailability(AlertDialog alertDialog) {
Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId();
boolean enabled = algorithm == Algorithm.ECDSA || algorithm == Algorithm.ECDH
|| getProperKeyLength(algorithm, getSelectedKeyLength()) > 0;
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
}
Choice c = this.getItem(position);
private void setCustomKeyVisibility() {
final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem();
final String customLengthString = getResources().getString(R.string.key_size_custom);
final boolean customSelected = customLengthString.equals(selectedItemString);
final int visibility = customSelected ? View.VISIBLE : View.GONE;
TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
mCustomKeyEditText.setVisibility(visibility);
mCustomKeyTextView.setVisibility(visibility);
mCustomKeyInfoTextView.setVisibility(visibility);
text1.setText(c.getName());
text2.setText(Html.fromHtml(c.getDescription()));
// hide keyboard after setting visibility to gone
if (visibility == View.GONE) {
InputMethodManager imm = (InputMethodManager)
getActivity().getSystemService(FragmentActivity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mCustomKeyEditText.getWindowToken(), 0);
}
}
private void updateUiForAlgorithm(Algorithm algorithm) {
final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter();
keySizeAdapter.clear();
switch (algorithm) {
case RSA: {
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values);
mKeySizeSpinner.setSelection(1);
mKeySizeRow.setVisibility(View.VISIBLE);
mCurveRow.setVisibility(View.GONE);
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa));
// allowed flags:
mFlagSign.setEnabled(true);
mFlagEncrypt.setEnabled(true);
mFlagAuthenticate.setEnabled(true);
if (mWillBeMasterKey) {
mFlagCertify.setEnabled(true);
mFlagCertify.setChecked(true);
mFlagSign.setChecked(false);
mFlagEncrypt.setChecked(false);
} else {
mFlagCertify.setEnabled(false);
mFlagCertify.setChecked(false);
mFlagSign.setChecked(true);
mFlagEncrypt.setChecked(true);
}
mFlagAuthenticate.setChecked(false);
break;
}
case ELGAMAL: {
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values);
mKeySizeSpinner.setSelection(3);
mKeySizeRow.setVisibility(View.VISIBLE);
mCurveRow.setVisibility(View.GONE);
mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length
// allowed flags:
mFlagCertify.setChecked(false);
mFlagCertify.setEnabled(false);
mFlagSign.setChecked(false);
mFlagSign.setEnabled(false);
mFlagEncrypt.setChecked(true);
mFlagEncrypt.setEnabled(true);
mFlagAuthenticate.setChecked(false);
mFlagAuthenticate.setEnabled(false);
break;
}
case DSA: {
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values);
mKeySizeSpinner.setSelection(2);
mKeySizeRow.setVisibility(View.VISIBLE);
mCurveRow.setVisibility(View.GONE);
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa));
// allowed flags:
mFlagCertify.setChecked(false);
mFlagCertify.setEnabled(false);
mFlagSign.setChecked(true);
mFlagSign.setEnabled(true);
mFlagEncrypt.setChecked(false);
mFlagEncrypt.setEnabled(false);
mFlagAuthenticate.setChecked(false);
mFlagAuthenticate.setEnabled(false);
break;
}
case ECDSA: {
mKeySizeRow.setVisibility(View.GONE);
mCurveRow.setVisibility(View.VISIBLE);
mCustomKeyInfoTextView.setText("");
// allowed flags:
mFlagCertify.setEnabled(mWillBeMasterKey);
mFlagCertify.setChecked(mWillBeMasterKey);
mFlagSign.setEnabled(true);
mFlagSign.setChecked(!mWillBeMasterKey);
mFlagEncrypt.setEnabled(false);
mFlagEncrypt.setChecked(false);
mFlagAuthenticate.setEnabled(true);
mFlagAuthenticate.setChecked(false);
break;
}
case ECDH: {
mKeySizeRow.setVisibility(View.GONE);
mCurveRow.setVisibility(View.VISIBLE);
mCustomKeyInfoTextView.setText("");
// allowed flags:
mFlagCertify.setChecked(false);
mFlagCertify.setEnabled(false);
mFlagSign.setChecked(false);
mFlagSign.setEnabled(false);
mFlagEncrypt.setChecked(true);
mFlagEncrypt.setEnabled(true);
mFlagAuthenticate.setChecked(false);
mFlagAuthenticate.setEnabled(false);
break;
}
}
keySizeAdapter.notifyDataSetChanged();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void replaceArrayAdapterContent(ArrayAdapter<CharSequence> arrayAdapter, int stringArrayResourceId) {
final String[] spinnerValuesStringArray = getResources().getStringArray(stringArrayResourceId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
arrayAdapter.addAll(spinnerValuesStringArray);
} else {
for (final String value : spinnerValuesStringArray) {
arrayAdapter.add(value);
}
return convertView;
}
}

View File

@@ -36,6 +36,10 @@ public class EditSubkeyDialogFragment extends DialogFragment {
public static final int MESSAGE_REVOKE = 2;
public static final int MESSAGE_STRIP = 3;
public static final int MESSAGE_MOVE_KEY_TO_CARD = 4;
public static final int SUBKEY_MENU_CHANGE_EXPIRY = 0;
public static final int SUBKEY_MENU_REVOKE_SUBKEY = 1;
public static final int SUBKEY_MENU_STRIP_SUBKEY = 2;
public static final int SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN = 3;
private Messenger mMessenger;
@@ -68,16 +72,16 @@ public class EditSubkeyDialogFragment extends DialogFragment {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
case SUBKEY_MENU_CHANGE_EXPIRY:
sendMessageToHandler(MESSAGE_CHANGE_EXPIRY, null);
break;
case 1:
case SUBKEY_MENU_REVOKE_SUBKEY:
sendMessageToHandler(MESSAGE_REVOKE, null);
break;
case 2:
sendMessageToHandler(MESSAGE_STRIP, null);
case SUBKEY_MENU_STRIP_SUBKEY:
showAlertDialog();
break;
case 3:
case SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN:
sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null);
break;
default:
@@ -95,6 +99,25 @@ public class EditSubkeyDialogFragment extends DialogFragment {
return builder.show();
}
private void showAlertDialog() {
CustomAlertDialogBuilder stripAlertDialog = new CustomAlertDialogBuilder(getActivity());
stripAlertDialog.setTitle(R.string.title_alert_strip).
setMessage(R.string.alert_strip).setCancelable(true);
stripAlertDialog.setPositiveButton(R.string.strip, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendMessageToHandler(MESSAGE_STRIP, null);
}
});
stripAlertDialog.setNegativeButton(R.string.btn_do_not_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dismiss();
}
});
stripAlertDialog.show();
}
/**
* Send message back to handler which is initialized in a activity
*

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2016 Alex Fong Jie Wen <alexfongg@gmail.com>
*
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.util.spinner;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Spinner;
/**
* Custom spinner which uses a hack to
* always set focus on first item in list
*
*/
public class FocusFirstItemSpinner extends Spinner {
/**
* Spinner is originally designed to set focus on the currently selected item.
* When Spinner is selected to show dropdown, 'performClick()' is called internally.
* 'getSelectedItemPosition()' is then called to obtain the item to focus on.
* We use a toggle to have 'getSelectedItemPosition()' return the 0th index
* for this particular case.
*/
private boolean mToggleFlag = true;
public FocusFirstItemSpinner(Context context, AttributeSet attrs,
int defStyle, int mode) {
super(context, attrs, defStyle, mode);
}
public FocusFirstItemSpinner(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public FocusFirstItemSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FocusFirstItemSpinner(Context context, int mode) {
super(context, mode);
}
public FocusFirstItemSpinner(Context context) {
super(context);
}
@Override
public int getSelectedItemPosition() {
if (!mToggleFlag) {
return 0;
}
return super.getSelectedItemPosition();
}
@Override
public boolean performClick() {
mToggleFlag = false;
boolean result = super.performClick();
mToggleFlag = true;
return result;
}
}

View File

@@ -113,11 +113,6 @@ public class CertifyKeySpinner extends KeySpinner {
@Override
boolean isItemEnabled(Cursor cursor) {
// "none" entry is always enabled!
if (cursor.getPosition() == 0) {
return true;
}
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
return false;
}

View File

@@ -72,11 +72,6 @@ public class SignKeySpinner extends KeySpinner {
@Override
boolean isItemEnabled(Cursor cursor) {
// "none" entry is always enabled!
if (cursor.getPosition() == 0) {
return true;
}
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
return false;
}

View File

@@ -18,12 +18,15 @@
package org.sufficientlysecure.keychain.util;
public class Choice <E> {
private String mName;
private E mId;
private String mDescription;
public Choice(E id, String name) {
public Choice(E id, String name, String description) {
mId = id;
mName = name;
mDescription = description;
}
public E getId() {
@@ -34,6 +37,10 @@ public class Choice <E> {
return mName;
}
public String getDescription() {
return mDescription;
}
@Override
public String toString() {
return mName;

View File

@@ -19,6 +19,22 @@
package org.sufficientlysecure.keychain.util;
import android.accounts.Account;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.Pref;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
import java.io.Serializable;
import java.net.Proxy;
import java.util.ArrayList;
@@ -32,19 +48,6 @@ import java.util.Map;
import java.util.Set;
import java.util.Vector;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.Pref;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
/**
* Singleton Implementation of a Preference Helper
*/
@@ -76,9 +79,8 @@ public class Preferences {
/**
* Makes android's preference framework write to our file instead of default.
* This allows us to use the "persistent" attribute to simplify code, which automatically
* This allows us to use the xml "persistent" attribute to simplify code, which automatically
* writes and reads preference values.
* @param manager
*/
public static void setPreferenceManagerFileAndMode(PreferenceManager manager) {
manager.setSharedPreferencesName(PREF_FILE_NAME);
@@ -302,6 +304,23 @@ public class Preferences {
}
/**
* @return true if a periodic sync exists and is set to run automatically, false otherwise
*/
public static boolean getKeyserverSyncEnabled(Context context) {
Account account = KeychainApplication.createAccountIfNecessary(context);
if (account == null) {
// if the account could not be created for some reason, we can't have a sync
return false;
}
String authority = Constants.PROVIDER_AUTHORITY;
return ContentResolver.getSyncAutomatically(account, authority) &&
!ContentResolver.getPeriodicSyncs(account, authority).isEmpty();
}
public CacheTTLPrefs getPassphraseCacheTtl() {
Set<String> pref = mSharedPreferences.getStringSet(Constants.Pref.PASSPHRASE_CACHE_TTLS, null);
if (pref == null) {
@@ -424,6 +443,12 @@ public class Preferences {
};
}
// sync preferences
public boolean getWifiOnlySync() {
return mSharedPreferences.getBoolean(Pref.ENABLE_WIFI_SYNC_ONLY, true);
}
// experimental prefs
public boolean getExperimentalEnableWordConfirm() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -13,147 +13,68 @@
android:paddingRight="24dp"
android:stretchColumns="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="4dp"
android:text="@string/key_creation_el_gamal_info" />
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/label_algorithm" />
android:paddingRight="10dp"
android:text="@string/label_key_type" />
<Spinner
android:id="@+id/add_subkey_algorithm"
<!-- custom spinner for fixing focus on first item in list at all times -->
<org.sufficientlysecure.keychain.ui.util.spinner.FocusFirstItemSpinner
android:id="@+id/add_subkey_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp" />
</TableRow>
<TableRow android:id="@+id/add_subkey_row_size">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/label_key_size" />
<Spinner
android:id="@+id/add_subkey_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:dropDownWidth="wrap_content"
android:padding="4dp" />
</TableRow>
<TableRow
android:id="@+id/add_subkey_row_curve"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/label_ecc_curve"/>
<Spinner
android:id="@+id/add_subkey_curve"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:padding="4dp"/>
</TableRow>
<TextView
android:id="@+id/add_subkey_custom_key_size_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/key_size_custom_info"
android:visibility="gone" />
<EditText
android:id="@+id/add_subkey_custom_key_size_input"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="number"
android:visibility="gone" />
<TextView
android:id="@+id/add_subkey_custom_key_size_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
<TableRow>
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/label_usage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="10dip"
android:layout_gravity="center_vertical|top"
android:paddingRight="10dp"
android:text="@string/label_usage" />
<RadioGroup
android:id="@+id/add_subkey_usage_group"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/add_subkey_flag_certify"
android:enabled="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flag_certify" />
<RadioButton
android:id="@+id/add_subkey_usage_none"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:text="@string/usage_none" />
<RadioButton
android:id="@+id/add_subkey_usage_sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/usage_sign" />
<RadioButton
android:id="@+id/add_subkey_usage_encrypt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/usage_encrypt" />
<RadioButton
android:id="@+id/add_subkey_usage_sign_and_encrypt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/usage_sign_and_encrypt" />
</RadioGroup>
</TableRow>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="10dip" />
<CheckBox
android:id="@+id/add_subkey_flag_sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flag_sign" />
</TableRow>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="10dip" />
<CheckBox
android:id="@+id/add_subkey_flag_encrypt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flag_encrypt" />
</TableRow>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="10dip" />
<CheckBox
android:id="@+id/add_subkey_flag_authenticate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flag_authenticate" />
</TableRow>
<TableRow
android:layout_marginTop="8dp"
@@ -164,7 +85,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="10dip"
android:paddingRight="10dp"
android:text="@string/label_expiry" />
<CheckBox

View File

@@ -13,7 +13,6 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:clickable="true"
android:layout_marginLeft="8dip"
android:layout_marginTop="8dip"/>
<LinearLayout android:id="@+id/user_id_body"
@@ -21,7 +20,6 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:singleLine="true"
android:layout_marginLeft="8dip"
android:layout_marginTop="4dip">
<CheckBox

View File

@@ -22,8 +22,9 @@
android:id="@+id/textView"
android:layout_weight="1" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/view_key_user_ids"
<fragment
android:id="@+id/multi_user_ids_fragment"
android:name="org.sufficientlysecure.keychain.ui.MultiUserIdsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<org.sufficientlysecure.keychain.ui.widget.FixedListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/view_key_user_ids"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingTop="6dp"
android:paddingRight="10dp"
android:paddingBottom="6dp"
android:background="?android:attr/selectableItemBackground">
<TextView android:id="@android:id/text1"
style="?android:attr/spinnerDropDownItemStyle"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="6dp"
android:paddingRight="6dp" />
<TextView android:id="@android:id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="2dp" />
</LinearLayout>

View File

@@ -41,6 +41,12 @@
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp" />
<fragment
android:id="@+id/multi_user_ids_fragment"
android:name="org.sufficientlysecure.keychain.ui.MultiUserIdsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
style="@style/SectionHeader"
android:layout_width="wrap_content"

View File

@@ -35,5 +35,6 @@
<!-- linked ID view -->
<color name="card_view_button">#7bad45</color>
<color name="toolbar_photo_tint">#1E7bad45</color>
</resources>

View File

@@ -13,6 +13,7 @@
<string name="title_encrypt_files">"Encrypt"</string>
<string name="title_decrypt">"Decrypt"</string>
<string name="title_add_subkey">"Add subkey"</string>
<string name="title_change_master_key">Change master key</string>
<string name="title_edit_key">"Edit Key"</string>
<string name="title_linked_create">"Create a Linked Identity"</string>
<string name="title_preferences">"Settings"</string>
@@ -40,6 +41,7 @@
<string name="title_advanced_key_info">"Advanced"</string>
<string name="title_delete_secret_key">"Delete YOUR key '%s'?"</string>
<string name="title_manage_my_keys">"Manage my keys"</string>
<string name="title_alert_strip">"Strip this subkey"</string>
<!-- section -->
<string name="section_user_ids">"Identities"</string>
@@ -165,6 +167,7 @@
<string name="label_keyservers">"Select OpenPGP keyservers"</string>
<string name="label_key_id">"Key ID"</string>
<string name="label_key_created">"Key created %s"</string>
<string name="label_key_type">"Type"</string>
<string name="label_creation">"Creation"</string>
<string name="label_expiry">"Expiry"</string>
<string name="label_usage">"Usage"</string>
@@ -201,6 +204,7 @@
<string name="label_sync_settings_keyserver_title">"Automatic key updates"</string>
<string name="label_sync_settings_keyserver_summary_on">"Every three days, keys are updated from the preferred keyserver"</string>
<string name="label_sync_settings_keyserver_summary_off">"Keys are not automatically updated"</string>
<string name="label_sync_settings_wifi_title">"Sync only on Wi-Fi"</string>
<string name="label_sync_settings_contacts_title">"Link keys to contacts"</string>
<string name="label_sync_settings_contacts_summary_on">"Link keys to contacts based on names and email addresses. This happens completely offline on your device."</string>
<string name="label_sync_settings_contacts_summary_off">"New keys will not be linked to contacts"</string>
@@ -281,22 +285,26 @@
<string name="choice_8hours">"8 hours"</string>
<string name="choice_forever">"forever"</string>
<string name="choice_select_cert">"Select a Key"</string>
<string name="dsa">"DSA"</string>
<string name="elgamal">"ElGamal"</string>
<string name="rsa">"RSA"</string>
<string name="ecdh">"ECDH"</string>
<string name="ecdsa">"ECDSA"</string>
<string name="filemanager_title_open">"Open…"</string>
<string name="rsa_2048">"RSA 2048"</string>
<string name="rsa_2048_description_html">"smaller filesize, considered secure until 2030"</string>
<string name="rsa_3072">"RSA 3072"</string>
<string name="rsa_3072_description_html">"recommended, considered secure until 2040"</string>
<string name="rsa_4096">"RSA 4096"</string>
<string name="rsa_4096_description_html">"larger file size, considered secure until 2040+"</string>
<string name="ecc_p256">"ECC P-256"</string>
<string name="ecc_p256_description_html">"very tiny filesize, considered secure until 2040 &lt;br/> &lt;u>experimental and not supported by all implementations&lt;/u>"</string>
<string name="ecc_p521">"ECC P-521"</string>
<string name="ecc_p521_description_html">"tiny filesize, considered secure until 2040+ &lt;br/> &lt;u>experimental and not supported by all implementations"&lt;/u></string>
<string name="usage_none">"None (subkey binding only)"</string>
<string name="usage_sign">"Sign"</string>
<string name="usage_encrypt">"Encrypt"</string>
<string name="usage_sign_and_encrypt">"Sign &amp; Encrypt"</string>
<string name="error">"Error"</string>
<string name="error_message">"Error: %s"</string>
<string name="theme_dark">"Dark"</string>
<string name="theme_light">"Light"</string>
<!-- key flags -->
<string name="flag_certify">"Certify"</string>
<string name="flag_sign">"Sign"</string>
<string name="flag_encrypt">"Encrypt"</string>
<string name="flag_authenticate">"Authenticate"</string>
<string name="strip">"Strip it"</string>
<!-- sentences -->
<string name="wrong_passphrase">"Wrong password."</string>
@@ -331,6 +339,7 @@
<string name="public_key_deletetion_confirmation">"Delete key '%s'?"</string>
<string name="also_export_secret_keys">"Also export secret keys"</string>
<string name="reinstall_openkeychain">"You encountered a known bug with Android. Please reinstall OpenKeychain if you want to link your contacts with keys."</string>
<string name="alert_strip">"Stripping this subkey will make it unusable on this device!"</string>
<string name="key_exported">"Successfully exported 1 key."</string>
<string name="keys_exported">"Successfully exported %d keys."</string>
@@ -746,7 +755,7 @@
<item>"Move Subkey to Security Token"</item>
</string-array>
<string name="edit_key_new_subkey">"new subkey"</string>
<string name="edit_key_select_flag">"Please select at least one flag!"</string>
<string name="edit_key_select_usage">"Please select key usage!"</string>
<string name="edit_key_error_add_identity">"Add at least one identity!"</string>
<string name="edit_key_error_add_subkey">"Add at least one subkey!"</string>
<string name="edit_key_error_bad_security_token_algo">"Algorithm not supported by Security Token!"</string>

View File

@@ -3,6 +3,12 @@
android:key="syncKeyserver"
android:persistent="false"
android:title="@string/label_sync_settings_keyserver_title"/>
<SwitchPreference
android:key="enableWifiSyncOnly"
android:defaultValue="true"
android:persistent="true"
android:dependency="syncKeyserver"
android:title="@string/label_sync_settings_wifi_title"/>
<SwitchPreference
android:key="syncContacts"
android:persistent="false"

View File

@@ -31,6 +31,8 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -40,8 +42,6 @@ import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.WorkaroundBuildConfig;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
@@ -124,13 +124,14 @@ public class ExportTest {
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ECDH, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("snails");
parcel.mNewUnlock = new ChangeUnlockParcel(null, new Passphrase("1234"));
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase("1234"));
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing2 = result.getRing();
mStaticRing2 = UncachedKeyRing.forTestingOnlyAddDummyLocalSignature(mStaticRing2, "1234");
}
}

View File

@@ -1232,47 +1232,7 @@ public class PgpKeyOperationTest {
}
@Test
public void testUnlockPin() throws Exception {
Passphrase pin = new Passphrase("5235125");
// change passphrase to a pin type
parcel.mNewUnlock = new ChangeUnlockParcel(null, pin);
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
Assert.assertEquals("exactly three packets should have been added (the secret keys + notation packet)",
3, onlyA.size());
Assert.assertEquals("exactly four packets should have been added (the secret keys + notation packet)",
4, onlyB.size());
RawPacket dkSig = onlyB.get(1);
Assert.assertEquals("second modified packet should be notation data",
PacketTags.SIGNATURE, dkSig.tag);
// check that notation data contains pin
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
modified.getEncoded(), false, 0);
Assert.assertEquals("secret key type should be 'pin' after this",
SecretKeyType.PIN,
secretRing.getSecretKey().getSecretKeyTypeSuperExpensive());
// need to sleep for a sec, so the timestamp changes for notation data
Thread.sleep(1000);
{
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase("phrayse"), null);
applyModificationWithChecks(parcel, modified, onlyA, onlyB, new CryptoInputParcel(pin), true, false);
Assert.assertEquals("exactly four packets should have been removed (the secret keys + notation packet)",
4, onlyA.size());
Assert.assertEquals("exactly three packets should have been added (no more notation packet)",
3, onlyB.size());
}
}
@Test
public void testRestricted () throws Exception {
public void testRestricted() throws Exception {
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);

View File

@@ -44,7 +44,7 @@ Development mailinglist at https://lists.riseup.net/www/subscribe/openkeychain
3. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html)
4. Open the Android SDK Manager (shell command: ``android``).
Expand the Tools directory and select "Android SDK Build-tools (Version 23.0.1)".
Expand the Extras directory and install "Android Support Repository"
Expand the Extras directory and install "Android Support Library", as well as "Local Maven repository for Support Libraries"
Select SDK Platform for API levels 21, 22, and 23.
5. Export ANDROID_HOME pointing to your Android SDK
6. Execute ``./gradlew assembleFdroidDebug``
@@ -74,7 +74,7 @@ We are using the newest [Android Studio](http://developer.android.com/sdk/instal
OpenKeychain uses a forked version with some small changes. These changes will been sent to Bouncy Castle.
see
* Fork: https://github.com/openpgp-keychain/bouncycastle
* Fork: https://github.com/open-keychain/bouncycastle
#### Bouncy Castle resources