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

View File

@@ -76,6 +76,7 @@
<!-- other group (for free) --> <!-- other group (for free) -->
<uses-permission android:name="android.permission.INTERNET" /> <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.NFC" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
@@ -89,6 +90,15 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.Keychain.Light"> 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 --> <!-- singleTop for NFC dispatch, see SecurityTokenOperationActivity -->
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"

View File

@@ -118,6 +118,7 @@ public final class Constants {
// keyserver sync settings // keyserver sync settings
public static final String SYNC_CONTACTS = "syncContacts"; public static final String SYNC_CONTACTS = "syncContacts";
public static final String SYNC_KEYSERVER = "syncKeyserver"; public static final String SYNC_KEYSERVER = "syncKeyserver";
public static final String ENABLE_WIFI_SYNC_ONLY = "enableWifiSyncOnly";
// other settings // other settings
public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm"; public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm";
public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities"; 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 // Add OpenKeychain account to Android to link contacts with keys and keyserver sync
createAccountIfNecessary(this); 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 first time, enable keyserver and contact sync
if (Preferences.getPreferences(this).isFirstTime()) { if (Preferences.getPreferences(this).isFirstTime()) {
KeyserverSyncAdapterService.enableKeyserverSync(this); 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_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_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_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_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase),
MSG_MF_PIN (LogLevel.INFO, R.string.msg_mf_pin), MSG_MF_PIN (LogLevel.INFO, R.string.msg_mf_pin),
MSG_MF_ADMIN_PIN (LogLevel.INFO, R.string.msg_mf_admin_pin), MSG_MF_ADMIN_PIN (LogLevel.INFO, R.string.msg_mf_admin_pin),

View File

@@ -18,6 +18,23 @@
package org.sufficientlysecure.keychain.pgp; 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.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.sig.Features; 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.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; 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.Curve;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; 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.NfcKeyToCardOperationsBuilder;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; 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.Primes;
import org.sufficientlysecure.keychain.util.ProgressScaler; 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 * This class is the single place where ALL operations that actually modify a PGP public or secret
* key take place. * key take place.
@@ -1058,8 +1058,8 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_PASSPHRASE, indent); log.add(LogType.MSG_MF_PASSPHRASE, indent);
indent += 1; indent += 1;
sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(),
cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent); saveParcel.mNewUnlock.mNewPassphrase, log, indent);
if (sKR == null) { if (sKR == null) {
// The error has been logged above, just return a bad state // The error has been logged above, just return a bad state
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); 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 /** This method returns true iff the provided keyring has a local direct key signature
* with notation data. * with notation data.
*/ */
@@ -1294,8 +1224,7 @@ public class PgpKeyOperation {
PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray()); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
// noinspection unchecked for (PGPSecretKey sKey : new IterableIterator<>(sKR.getSecretKeys())) {
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent, log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent,
KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID())); KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID()));

View File

@@ -35,6 +35,8 @@ import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.TreeSet; import java.util.TreeSet;
import android.support.annotation.VisibleForTesting;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.SignatureSubpacketTags;
@@ -42,15 +44,22 @@ import org.bouncycastle.bcpg.UserAttributeSubpacketTags;
import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.bouncycastle.openpgp.PGPUtil; 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.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@@ -1310,4 +1319,37 @@ public class UncachedKeyRing {
|| algorithm == PGPPublicKey.ECDH; || 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 { public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db"; 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; static Boolean apgHack = false;
private Context mContext; 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 type INTEGER");
db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB"); db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB");
case 7: case 7:
// consolidate
case 8:
// new table for allowed key ids in API // new table for allowed key ids in API
try { try {
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
} catch (Exception e) { } catch (Exception e) {
// never mind, the column probably already existed // never mind, the column probably already existed
} }
case 9: case 8:
// tbale name for user_ids changed to user_packets // tbale name for user_ids changed to user_packets
db.execSQL("DROP TABLE IF EXISTS certs"); db.execSQL("DROP TABLE IF EXISTS certs");
db.execSQL("DROP TABLE IF EXISTS user_ids"); db.execSQL("DROP TABLE IF EXISTS user_ids");
db.execSQL(CREATE_USER_PACKETS); db.execSQL(CREATE_USER_PACKETS);
db.execSQL(CREATE_CERTS); db.execSQL(CREATE_CERTS);
case 10: case 9:
// do nothing here, just consolidate // do nothing here, just consolidate
case 11: case 10:
// fix problems in database, see #1402 for details // fix problems in database, see #1402 for details
// https://github.com/open-keychain/open-keychain/issues/1402 // https://github.com/open-keychain/open-keychain/issues/1402
db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3"); db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
case 12: case 11:
db.execSQL(CREATE_UPDATE_KEYS); db.execSQL(CREATE_UPDATE_KEYS);
case 13: case 12:
// do nothing here, just consolidate // do nothing here, just consolidate
case 14: case 13:
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");"); db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");");
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
+ UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");"); + UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
db.execSQL("CREATE INDEX verified_certs ON certs (" db.execSQL("CREATE INDEX verified_certs ON certs ("
+ CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");"); + 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 name TEXT");
db.execSQL("ALTER TABLE user_packets ADD COLUMN email TEXT"); db.execSQL("ALTER TABLE user_packets ADD COLUMN email TEXT");
db.execSQL("ALTER TABLE user_packets ADD COLUMN comment 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 // always do consolidate after upgrade

View File

@@ -316,8 +316,10 @@ public class KeychainProvider extends ContentProvider {
+ " WHERE dups." + UserPackets.MASTER_KEY_ID + " WHERE dups." + UserPackets.MASTER_KEY_ID
+ " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND dups." + UserPackets.RANK + " = 0" + " AND dups." + UserPackets.RANK + " = 0"
+ " AND dups." + UserPackets.USER_ID + " AND dups." + UserPackets.NAME
+ " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID + " = " + Tables.USER_PACKETS + "." + UserPackets.NAME + " COLLATE NOCASE"
+ " AND dups." + UserPackets.EMAIL
+ " = " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " COLLATE NOCASE"
+ ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID); + ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID);
projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
projectionMap.put(KeyRings.PUBKEY_DATA, 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,9 +144,10 @@ public class ContactSyncAdapterService extends Service {
public static void requestContactsSync() { public static void requestContactsSync() {
// if user has disabled automatic sync, do nothing // if user has disabled automatic sync, do nothing
if (!ContentResolver.getSyncAutomatically( boolean isSyncEnabled = ContentResolver.getSyncAutomatically(new Account
new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), (Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), ContactsContract.AUTHORITY);
ContactsContract.AUTHORITY)) {
if (!isSyncEnabled) {
return; return;
} }

View File

@@ -11,11 +11,14 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.PeriodicSync;
import android.content.SyncResult; import android.content.SyncResult;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; 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.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.receiver.NetworkReceiver;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; 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_IGNORE_TOR = "ignore_tor";
private static final String ACTION_UPDATE_ALL = "update_all"; 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_DISMISS_NOTIFICATION = "cancel_sync";
private static final String ACTION_START_ORBOT = "start_orbot"; private static final String ACTION_START_ORBOT = "start_orbot";
private static final String ACTION_CANCEL = "cancel"; private static final String ACTION_CANCEL = "cancel";
@@ -176,8 +180,25 @@ public class KeyserverSyncAdapterService extends Service {
@Override @Override
public void onPerformSync(Account account, Bundle extras, String authority, public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) { 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 PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this
.getSystemService(Context.POWER_SERVICE); .getSystemService(Context.POWER_SERVICE);
@SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20 @SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20
@@ -509,6 +530,10 @@ public class KeyserverSyncAdapterService extends Service {
return builder.build(); 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) { public static void enableKeyserverSync(Context context) {
Account account = KeychainApplication.createAccountIfNecessary(context); Account account = KeychainApplication.createAccountIfNecessary(context);
@@ -519,6 +544,19 @@ public class KeyserverSyncAdapterService extends Service {
ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1); ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true); ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true);
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( ContentResolver.addPeriodicSync(
account, account,
Constants.PROVIDER_AUTHORITY, Constants.PROVIDER_AUTHORITY,
@@ -526,6 +564,7 @@ public class KeyserverSyncAdapterService extends Service {
SYNC_INTERVAL SYNC_INTERVAL
); );
} }
}
private boolean isSyncEnabled() { private boolean isSyncEnabled() {
Account account = KeychainApplication.createAccountIfNecessary(this); Account account = KeychainApplication.createAccountIfNecessary(this);

View File

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

View File

@@ -43,6 +43,7 @@ import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@@ -237,6 +238,30 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
mCodeEditText.setImeOptions(EditorInfo.IME_ACTION_DONE); mCodeEditText.setImeOptions(EditorInfo.IME_ACTION_DONE);
setupEditTextSuccessListener(mCodeEditText); 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); TextView codeDisplayText = (TextView) view.findViewById(R.id.backup_code_display);
setupAutomaticLinebreak(codeDisplayText); setupAutomaticLinebreak(codeDisplayText);

View File

@@ -28,7 +28,8 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity;
public class CertifyKeyActivity extends BaseActivity { public class CertifyKeyActivity extends BaseActivity {
public static final String EXTRA_RESULT = "operation_result"; 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"; public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id";
@Override @Override

View File

@@ -62,58 +62,26 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
public class CertifyKeyFragment public class CertifyKeyFragment
extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> {
implements LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_CHECK_STATES = "check_states";
private CheckBox mUploadKeyCheckbox; private CheckBox mUploadKeyCheckbox;
ListView mUserIds;
private CertifyKeySpinner mCertifyKeySpinner; private CertifyKeySpinner mCertifyKeySpinner;
private long[] mPubMasterKeyIds; private MultiUserIdsFragment mMultiUserIdsFragment;
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;
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS); if (savedInstanceState == null) {
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;
// preselect certify key id if given // preselect certify key id if given
long certifyKeyId = getActivity().getIntent() long certifyKeyId = getActivity().getIntent()
.getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); .getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
if (certifyKeyId != Constants.key.none) { if (certifyKeyId != Constants.key.none) {
try { try {
CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId); CachedPublicKeyRing key = (new ProviderHelper(getActivity()))
.getCachedPublicKeyRing(certifyKeyId);
if (key.canCertify()) { if (key.canCertify()) {
mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId); mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId);
} }
@@ -121,15 +89,8 @@ public class CertifyKeyFragment
Log.e(Constants.TAG, "certify certify check failed", e); 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); OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT);
if (result != null) { if (result != null) {
// display result from import // 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 @Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.certify_key_fragment, null); View view = inflater.inflate(R.layout.certify_key_fragment, null);
mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner);
mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); 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 // make certify image gray, like action icons
ImageView vActionCertifyImage = ImageView vActionCertifyImage =
@@ -183,128 +136,11 @@ public class CertifyKeyFragment
return view; 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 @Override
public CertifyActionsParcel createOperationInput() { public CertifyActionsParcel createOperationInput() {
// Bail out if there is not at least one user id selected // 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()) { if (certifyActions.isEmpty()) {
Notify.create(getActivity(), "No identities selected!", Notify.create(getActivity(), "No identities selected!",
Notify.Style.ERROR).show(); Notify.Style.ERROR).show();

View File

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

View File

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

View File

@@ -79,9 +79,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign); mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign);
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
mEncryptKeyView.setThreshold(1); // Start working from first character 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); final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon);
mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() { 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; package org.sufficientlysecure.keychain.ui;
import java.util.List;
import android.Manifest; import android.Manifest;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
@@ -49,6 +47,7 @@ import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity; import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; 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.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
import java.util.List;
public class SettingsActivity extends AppCompatPreferenceActivity { public class SettingsActivity extends AppCompatPreferenceActivity {
public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; 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 { public static class SyncPrefsFragment extends PresetPreferenceFragment {
@@ -422,8 +423,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
super.onResume(); super.onResume();
// this needs to be done in onResume since the user can change sync values from Android // 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 // settings and we need to reflect that change when the user navigates back
AccountManager manager = AccountManager.get(getActivity()); final Account account = KeychainApplication.createAccountIfNecessary(getActivity());
final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0];
// for keyserver sync // for keyserver sync
initializeSyncCheckBox( initializeSyncCheckBox(
(SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER), (SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER),
@@ -441,7 +441,10 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
private void initializeSyncCheckBox(final SwitchPreference syncCheckBox, private void initializeSyncCheckBox(final SwitchPreference syncCheckBox,
final Account account, final Account account,
final String authority) { final String authority) {
boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority) // account is null if it could not be created for some reason
boolean syncEnabled =
account != null
&& ContentResolver.getSyncAutomatically(account, authority)
&& checkContactsPermission(authority); && checkContactsPermission(authority);
syncCheckBox.setChecked(syncEnabled); syncCheckBox.setChecked(syncEnabled);
setSummary(syncCheckBox, authority, syncEnabled); setSummary(syncCheckBox, authority, syncEnabled);
@@ -464,6 +467,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
return false; return false;
} }
} else { } else {
if (account == null) {
// if account could not be created for some reason,
// we can't have our sync
return false;
}
// disable syncs // disable syncs
ContentResolver.setSyncAutomatically(account, authority, false); ContentResolver.setSyncAutomatically(account, authority, false);
// immediately delete any linked contacts // immediately delete any linked contacts

View File

@@ -31,10 +31,7 @@ import android.widget.Spinner;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.UploadResult; 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;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
@@ -53,7 +50,6 @@ public class UploadKeyActivity extends BaseActivity
// CryptoOperationHelper.Callback vars // CryptoOperationHelper.Callback vars
private String mKeyserver; private String mKeyserver;
private long mMasterKeyId;
private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper; private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
@Override @Override
@@ -63,6 +59,10 @@ public class UploadKeyActivity extends BaseActivity
mUploadButton = findViewById(R.id.upload_key_action_upload); mUploadButton = findViewById(R.id.upload_key_action_upload);
mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); 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, ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, Preferences.getPreferences(this) android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
.getKeyServers() .getKeyServers()
@@ -89,15 +89,6 @@ public class UploadKeyActivity extends BaseActivity
return; 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 @Override
@@ -136,7 +127,9 @@ public class UploadKeyActivity extends BaseActivity
@Override @Override
public UploadKeyringParcel createOperationInput() { public UploadKeyringParcel createOperationInput() {
return new UploadKeyringParcel(mKeyserver, mMasterKeyId); long[] masterKeyIds = getIntent().getLongArrayExtra(MultiUserIdsFragment.EXTRA_KEY_IDS);
return new UploadKeyringParcel(mKeyserver, masterKeyIds[0]);
} }
@Override @Override

View File

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

View File

@@ -238,7 +238,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
// let user choose application // let user choose application
Intent sendIntent = new Intent(Intent.ACTION_SEND); 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 // NOTE: Don't use Intent.EXTRA_TEXT to send the key
// better send it via a Uri! // better send it via a Uri!
@@ -455,8 +455,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
} }
private void uploadToKeyserver() { 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); Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class);
uploadIntent.setData(mDataUri); uploadIntent.setData(mDataUri);
uploadIntent.putExtra(MultiUserIdsFragment.EXTRA_KEY_IDS, new long[]{keyId});
startActivityForResult(uploadIntent, 0); startActivityForResult(uploadIntent, 0);
} }

View File

@@ -333,14 +333,6 @@ public class KeyAdapter extends CursorAdapter {
return mUserId.email; 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) { public static String[] getProjectionWith(String[] projection) {

View File

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

View File

@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.support.v4.app.FragmentActivity;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -32,6 +33,7 @@ import android.widget.TextView;
import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import java.util.Calendar; import java.util.Calendar;
@@ -65,10 +67,11 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
public SaveKeyringParcel.SubkeyAdd mModel; public SaveKeyringParcel.SubkeyAdd mModel;
} }
public View getView(final int position, View convertView, ViewGroup parent) { public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) { if (convertView == null) {
// Not recycled, inflate a new view // 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(); final ViewHolder holder = new ViewHolder();
holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id); holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id);
holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details); 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); vStatus.setVisibility(View.GONE);
convertView.setTag(holder); 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(); final ViewHolder holder = (ViewHolder) convertView.getTag();
// save reference to model item // save reference to model item
@@ -113,8 +108,41 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
boolean isMasterKey = mNewKeyring && position == 0; boolean isMasterKey = mNewKeyring && position == 0;
if (isMasterKey) { if (isMasterKey) {
holder.vKeyId.setTypeface(null, Typeface.BOLD); 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 { } else {
holder.vKeyId.setTypeface(null, Typeface.NORMAL); 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); 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.support.v7.widget.Toolbar;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; 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) { public static void onResumeChecks(Context context) {
KeyserverSyncAdapterService.cancelUpdates(context); KeyserverSyncAdapterService.cancelUpdates(context);
// in case user has disabled sync from Android account settings // in case user has disabled sync from Android account settings

View File

@@ -17,25 +17,26 @@
package org.sufficientlysecure.keychain.ui.dialog; package org.sufficientlysecure.keychain.ui.dialog;
import android.annotation.TargetApi; import android.annotation.SuppressLint;
import android.app.Dialog; import android.app.Dialog;
import android.os.Build; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.Editable; import android.text.Html;
import android.text.TextWatcher;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.DatePicker; import android.widget.DatePicker;
import android.widget.EditText; import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TableRow; import android.widget.TableRow;
import android.widget.TextView; import android.widget.TextView;
@@ -49,14 +50,18 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.util.Choice; import org.sufficientlysecure.keychain.util.Choice;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
public class AddSubkeyDialogFragment extends DialogFragment { public class AddSubkeyDialogFragment extends DialogFragment {
public interface OnAlgorithmSelectedListener { 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"; 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 CheckBox mNoExpiryCheckBox;
private TableRow mExpiryRow; private TableRow mExpiryRow;
private DatePicker mExpiryDatePicker; private DatePicker mExpiryDatePicker;
private Spinner mAlgorithmSpinner; private Spinner mKeyTypeSpinner;
private View mKeySizeRow; private RadioGroup mUsageRadioGroup;
private Spinner mKeySizeSpinner; private RadioButton mUsageNone;
private View mCurveRow; private RadioButton mUsageSign;
private Spinner mCurveSpinner; private RadioButton mUsageEncrypt;
private TextView mCustomKeyTextView; private RadioButton mUsageSignAndEncrypt;
private EditText mCustomKeyEditText;
private TextView mCustomKeyInfoTextView;
private CheckBox mFlagCertify;
private CheckBox mFlagSign;
private CheckBox mFlagEncrypt;
private CheckBox mFlagAuthenticate;
private boolean mWillBeMasterKey; private boolean mWillBeMasterKey;
@@ -96,6 +95,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
return frag; return frag;
} }
@NonNull
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity context = getActivity(); final FragmentActivity context = getActivity();
@@ -106,25 +107,27 @@ public class AddSubkeyDialogFragment extends DialogFragment {
CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context); CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context);
@SuppressLint("InflateParams")
View view = mInflater.inflate(R.layout.add_subkey_dialog, null); View view = mInflater.inflate(R.layout.add_subkey_dialog, null);
dialog.setView(view); dialog.setView(view);
dialog.setTitle(R.string.title_add_subkey);
mNoExpiryCheckBox = (CheckBox) view.findViewById(R.id.add_subkey_no_expiry); mNoExpiryCheckBox = (CheckBox) view.findViewById(R.id.add_subkey_no_expiry);
mExpiryRow = (TableRow) view.findViewById(R.id.add_subkey_expiry_row); mExpiryRow = (TableRow) view.findViewById(R.id.add_subkey_expiry_row);
mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker); mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker);
mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm); mKeyTypeSpinner = (Spinner) view.findViewById(R.id.add_subkey_type);
mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size); mUsageRadioGroup = (RadioGroup) view.findViewById(R.id.add_subkey_usage_group);
mCurveSpinner = (Spinner) view.findViewById(R.id.add_subkey_curve); mUsageNone = (RadioButton) view.findViewById(R.id.add_subkey_usage_none);
mKeySizeRow = view.findViewById(R.id.add_subkey_row_size); mUsageSign = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign);
mCurveRow = view.findViewById(R.id.add_subkey_row_curve); mUsageEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_encrypt);
mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label); mUsageSignAndEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign_and_encrypt);
mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input);
mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info); if(mWillBeMasterKey) {
mFlagCertify = (CheckBox) view.findViewById(R.id.add_subkey_flag_certify); dialog.setTitle(R.string.title_change_master_key);
mFlagSign = (CheckBox) view.findViewById(R.id.add_subkey_flag_sign); mUsageNone.setVisibility(View.VISIBLE);
mFlagEncrypt = (CheckBox) view.findViewById(R.id.add_subkey_flag_encrypt); mUsageNone.setChecked(true);
mFlagAuthenticate = (CheckBox) view.findViewById(R.id.add_subkey_flag_authenticate); } else {
dialog.setTitle(R.string.title_add_subkey);
}
mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override
@@ -143,65 +146,24 @@ public class AddSubkeyDialogFragment extends DialogFragment {
mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime()); mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime());
{ {
ArrayList<Choice<Algorithm>> choices = new ArrayList<>(); ArrayList<Choice<SupportedKeyType>> choices = new ArrayList<>();
choices.add(new Choice<>(Algorithm.DSA, getResources().getString( choices.add(new Choice<>(SupportedKeyType.RSA_2048, getResources().getString(
R.string.dsa))); R.string.rsa_2048), getResources().getString(R.string.rsa_2048_description_html)));
if (!mWillBeMasterKey) { choices.add(new Choice<>(SupportedKeyType.RSA_3072, getResources().getString(
choices.add(new Choice<>(Algorithm.ELGAMAL, getResources().getString( R.string.rsa_3072), getResources().getString(R.string.rsa_3072_description_html)));
R.string.elgamal))); 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<>(Algorithm.RSA, getResources().getString( choices.add(new Choice<>(SupportedKeyType.ECC_P256, getResources().getString(
R.string.rsa))); R.string.ecc_p256), getResources().getString(R.string.ecc_p256_description_html)));
choices.add(new Choice<>(Algorithm.ECDSA, getResources().getString( choices.add(new Choice<>(SupportedKeyType.ECC_P521, getResources().getString(
R.string.ecdsa))); R.string.ecc_p521), getResources().getString(R.string.ecc_p521_description_html)));
choices.add(new Choice<>(Algorithm.ECDH, getResources().getString( TwoLineArrayAdapter adapter = new TwoLineArrayAdapter(context,
R.string.ecdh)));
ArrayAdapter<Choice<Algorithm>> adapter = new ArrayAdapter<>(context,
android.R.layout.simple_spinner_item, choices); android.R.layout.simple_spinner_item, choices);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mKeyTypeSpinner.setAdapter(adapter);
mAlgorithmSpinner.setAdapter(adapter); // make RSA 3072 the default
// make RSA the default
for (int i = 0; i < choices.size(); ++i) { for (int i = 0; i < choices.size(); ++i) {
if (choices.get(i).getId() == Algorithm.RSA) { if (choices.get(i).getId() == SupportedKeyType.RSA_3072) {
mAlgorithmSpinner.setSelection(i); mKeyTypeSpinner.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);
break; break;
} }
} }
@@ -215,45 +177,35 @@ public class AddSubkeyDialogFragment extends DialogFragment {
final AlertDialog alertDialog = dialog.show(); final AlertDialog alertDialog = dialog.show();
mCustomKeyEditText.addTextChangedListener(new TextWatcher() { mKeyTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@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 @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setCustomKeyVisibility(); // noinspection unchecked
setOkButtonAvailability(alertDialog); 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 @Override
public void onNothingSelected(AdapterView<?> parent) { 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) {
}
}); });
return alertDialog; return alertDialog;
@@ -269,36 +221,74 @@ public class AddSubkeyDialogFragment extends DialogFragment {
positiveButton.setOnClickListener(new View.OnClickListener() { positiveButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (!mFlagCertify.isChecked() && !mFlagSign.isChecked() if (mUsageRadioGroup.getCheckedRadioButtonId() == -1) {
&& !mFlagEncrypt.isChecked() && !mFlagAuthenticate.isChecked()) { Toast.makeText(getActivity(), R.string.edit_key_select_usage, Toast.LENGTH_LONG).show();
Toast.makeText(getActivity(), R.string.edit_key_select_flag, Toast.LENGTH_LONG).show();
return; return;
} }
Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId(); // noinspection unchecked
SupportedKeyType keyType = ((Choice<SupportedKeyType>) mKeyTypeSpinner.getSelectedItem()).getId();
Curve curve = null; Curve curve = null;
Integer keySize = null; Integer keySize = null;
// For EC keys, add a curve Algorithm algorithm = null;
if (algorithm == Algorithm.ECDH || algorithm == Algorithm.ECDSA) {
curve = ((Choice<Curve>) mCurveSpinner.getSelectedItem()).getId(); // set keysize & curve, for RSA & ECC respectively
// Otherwise, get a keysize switch (keyType) {
} else { case RSA_2048: {
keySize = getProperKeyLength(algorithm, getSelectedKeyLength()); 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; int flags = 0;
if (mFlagCertify.isChecked()) { if (mWillBeMasterKey) {
flags |= KeyFlags.CERTIFY_OTHER; flags |= KeyFlags.CERTIFY_OTHER;
} }
if (mFlagSign.isChecked()) { if (mUsageSign.isChecked()) {
flags |= KeyFlags.SIGN_DATA; flags |= KeyFlags.SIGN_DATA;
} } else if (mUsageEncrypt.isChecked()) {
if (mFlagEncrypt.isChecked()) {
flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; 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; long expiry;
if (mNoExpiryCheckBox.isChecked()) { if (mNoExpiryCheckBox.isChecked()) {
@@ -332,206 +322,29 @@ public class AddSubkeyDialogFragment extends DialogFragment {
} }
} }
private int getSelectedKeyLength() { private class TwoLineArrayAdapter extends ArrayAdapter<Choice<SupportedKeyType>> {
final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); public TwoLineArrayAdapter(Context context, int resource, List<Choice<SupportedKeyType>> objects) {
final String customLengthString = getResources().getString(R.string.key_size_custom); super(context, resource, objects);
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;
}
return keySize;
} }
/**
* <h3>RSA</h3> @Override
* <p>for RSA algorithm, key length must be greater than 2048. Possibility to generate keys bigger public View getDropDownView(int position, View convertView, ViewGroup parent) {
* than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check // inflate view if not given one
* <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and if (convertView == null) {
* <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a convertView = getActivity().getLayoutInflater()
* multiplicity of 8.</p> .inflate(R.layout.two_line_spinner_dropdown_item, parent, false);
* <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;
}
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) { Choice c = this.getItem(position);
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);
}
private void setCustomKeyVisibility() { TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
final String customLengthString = getResources().getString(R.string.key_size_custom);
final boolean customSelected = customLengthString.equals(selectedItemString);
final int visibility = customSelected ? View.VISIBLE : View.GONE;
mCustomKeyEditText.setVisibility(visibility); text1.setText(c.getName());
mCustomKeyTextView.setVisibility(visibility); text2.setText(Html.fromHtml(c.getDescription()));
mCustomKeyInfoTextView.setVisibility(visibility);
// hide keyboard after setting visibility to gone return convertView;
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);
}
} }
} }

View File

@@ -36,6 +36,10 @@ public class EditSubkeyDialogFragment extends DialogFragment {
public static final int MESSAGE_REVOKE = 2; public static final int MESSAGE_REVOKE = 2;
public static final int MESSAGE_STRIP = 3; public static final int MESSAGE_STRIP = 3;
public static final int MESSAGE_MOVE_KEY_TO_CARD = 4; 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; private Messenger mMessenger;
@@ -68,16 +72,16 @@ public class EditSubkeyDialogFragment extends DialogFragment {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
switch (which) { switch (which) {
case 0: case SUBKEY_MENU_CHANGE_EXPIRY:
sendMessageToHandler(MESSAGE_CHANGE_EXPIRY, null); sendMessageToHandler(MESSAGE_CHANGE_EXPIRY, null);
break; break;
case 1: case SUBKEY_MENU_REVOKE_SUBKEY:
sendMessageToHandler(MESSAGE_REVOKE, null); sendMessageToHandler(MESSAGE_REVOKE, null);
break; break;
case 2: case SUBKEY_MENU_STRIP_SUBKEY:
sendMessageToHandler(MESSAGE_STRIP, null); showAlertDialog();
break; break;
case 3: case SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN:
sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null); sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null);
break; break;
default: default:
@@ -95,6 +99,25 @@ public class EditSubkeyDialogFragment extends DialogFragment {
return builder.show(); 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 * 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 @Override
boolean isItemEnabled(Cursor cursor) { boolean isItemEnabled(Cursor cursor) {
// "none" entry is always enabled!
if (cursor.getPosition() == 0) {
return true;
}
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
return false; return false;
} }

View File

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

View File

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

View File

@@ -19,6 +19,22 @@
package org.sufficientlysecure.keychain.util; 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.io.Serializable;
import java.net.Proxy; import java.net.Proxy;
import java.util.ArrayList; import java.util.ArrayList;
@@ -32,19 +48,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Vector; 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 * 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. * 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. * writes and reads preference values.
* @param manager
*/ */
public static void setPreferenceManagerFileAndMode(PreferenceManager manager) { public static void setPreferenceManagerFileAndMode(PreferenceManager manager) {
manager.setSharedPreferencesName(PREF_FILE_NAME); 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() { public CacheTTLPrefs getPassphraseCacheTtl() {
Set<String> pref = mSharedPreferences.getStringSet(Constants.Pref.PASSPHRASE_CACHE_TTLS, null); Set<String> pref = mSharedPreferences.getStringSet(Constants.Pref.PASSPHRASE_CACHE_TTLS, null);
if (pref == 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 // experimental prefs
public boolean getExperimentalEnableWordConfirm() { 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:paddingRight="24dp"
android:stretchColumns="1"> 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> <TableRow>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:text="@string/label_algorithm" /> android:paddingRight="10dp"
android:text="@string/label_key_type" />
<Spinner <!-- custom spinner for fixing focus on first item in list at all times -->
android:id="@+id/add_subkey_algorithm" <org.sufficientlysecure.keychain.ui.util.spinner.FocusFirstItemSpinner
android:id="@+id/add_subkey_type"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="4dp" /> android:dropDownWidth="wrap_content"
</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:padding="4dp" /> android:padding="4dp" />
</TableRow> </TableRow>
<TableRow <TableRow
android:id="@+id/add_subkey_row_curve" android:layout_marginTop="8dp"
android:visibility="gone"> android:layout_marginBottom="8dp">
<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>
<TextView <TextView
android:id="@+id/label_usage" android:id="@+id/label_usage"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical|top"
android:paddingRight="10dip" android:paddingRight="10dp"
android:text="@string/label_usage" /> 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 <RadioButton
android:id="@+id/add_subkey_flag_certify" android:id="@+id/add_subkey_usage_none"
android:enabled="false"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/flag_certify" /> 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>
<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 <TableRow
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
@@ -164,7 +85,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:paddingRight="10dip" android:paddingRight="10dp"
android:text="@string/label_expiry" /> android:text="@string/label_expiry" />
<CheckBox <CheckBox

View File

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

View File

@@ -22,8 +22,9 @@
android:id="@+id/textView" android:id="@+id/textView"
android:layout_weight="1" /> android:layout_weight="1" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView <fragment
android:id="@+id/view_key_user_ids" android:id="@+id/multi_user_ids_fragment"
android:name="org.sufficientlysecure.keychain.ui.MultiUserIdsFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> 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_marginBottom="4dp"
android:layout_marginTop="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 <TextView
style="@style/SectionHeader" style="@style/SectionHeader"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

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

View File

@@ -13,6 +13,7 @@
<string name="title_encrypt_files">"Encrypt"</string> <string name="title_encrypt_files">"Encrypt"</string>
<string name="title_decrypt">"Decrypt"</string> <string name="title_decrypt">"Decrypt"</string>
<string name="title_add_subkey">"Add subkey"</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_edit_key">"Edit Key"</string>
<string name="title_linked_create">"Create a Linked Identity"</string> <string name="title_linked_create">"Create a Linked Identity"</string>
<string name="title_preferences">"Settings"</string> <string name="title_preferences">"Settings"</string>
@@ -40,6 +41,7 @@
<string name="title_advanced_key_info">"Advanced"</string> <string name="title_advanced_key_info">"Advanced"</string>
<string name="title_delete_secret_key">"Delete YOUR key '%s'?"</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_manage_my_keys">"Manage my keys"</string>
<string name="title_alert_strip">"Strip this subkey"</string>
<!-- section --> <!-- section -->
<string name="section_user_ids">"Identities"</string> <string name="section_user_ids">"Identities"</string>
@@ -165,6 +167,7 @@
<string name="label_keyservers">"Select OpenPGP keyservers"</string> <string name="label_keyservers">"Select OpenPGP keyservers"</string>
<string name="label_key_id">"Key ID"</string> <string name="label_key_id">"Key ID"</string>
<string name="label_key_created">"Key created %s"</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_creation">"Creation"</string>
<string name="label_expiry">"Expiry"</string> <string name="label_expiry">"Expiry"</string>
<string name="label_usage">"Usage"</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_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_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_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_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_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> <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_8hours">"8 hours"</string>
<string name="choice_forever">"forever"</string> <string name="choice_forever">"forever"</string>
<string name="choice_select_cert">"Select a Key"</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="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">"Error"</string>
<string name="error_message">"Error: %s"</string> <string name="error_message">"Error: %s"</string>
<string name="theme_dark">"Dark"</string> <string name="theme_dark">"Dark"</string>
<string name="theme_light">"Light"</string> <string name="theme_light">"Light"</string>
<string name="strip">"Strip it"</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>
<!-- sentences --> <!-- sentences -->
<string name="wrong_passphrase">"Wrong password."</string> <string name="wrong_passphrase">"Wrong password."</string>
@@ -331,6 +339,7 @@
<string name="public_key_deletetion_confirmation">"Delete key '%s'?"</string> <string name="public_key_deletetion_confirmation">"Delete key '%s'?"</string>
<string name="also_export_secret_keys">"Also export secret keys"</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="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="key_exported">"Successfully exported 1 key."</string>
<string name="keys_exported">"Successfully exported %d keys."</string> <string name="keys_exported">"Successfully exported %d keys."</string>
@@ -746,7 +755,7 @@
<item>"Move Subkey to Security Token"</item> <item>"Move Subkey to Security Token"</item>
</string-array> </string-array>
<string name="edit_key_new_subkey">"new subkey"</string> <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_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_add_subkey">"Add at least one subkey!"</string>
<string name="edit_key_error_bad_security_token_algo">"Algorithm not supported by Security Token!"</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:key="syncKeyserver"
android:persistent="false" android:persistent="false"
android:title="@string/label_sync_settings_keyserver_title"/> 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 <SwitchPreference
android:key="syncContacts" android:key="syncContacts"
android:persistent="false" android:persistent="false"

View File

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

View File

@@ -1232,47 +1232,7 @@ public class PgpKeyOperationTest {
} }
@Test @Test
public void testUnlockPin() throws Exception { public void testRestricted() 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 {
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); 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) 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``). 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 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. Select SDK Platform for API levels 21, 22, and 23.
5. Export ANDROID_HOME pointing to your Android SDK 5. Export ANDROID_HOME pointing to your Android SDK
6. Execute ``./gradlew assembleFdroidDebug`` 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. OpenKeychain uses a forked version with some small changes. These changes will been sent to Bouncy Castle.
see see
* Fork: https://github.com/openpgp-keychain/bouncycastle * Fork: https://github.com/open-keychain/bouncycastle
#### Bouncy Castle resources #### Bouncy Castle resources