Merge branch 'master' into okhttp3
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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()));
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 + ")");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_change_grey_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_change_grey_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 923 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_change_grey_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_change_grey_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 623 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_change_grey_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_change_grey_24dp.png
Normal file
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 |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 <br/> <u>experimental and not supported by all implementations</u>"</string>
|
||||||
|
<string name="ecc_p521">"ECC P-521"</string>
|
||||||
|
<string name="ecc_p521_description_html">"tiny filesize, considered secure until 2040+ <br/> <u>experimental and not supported by all implementations"</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 & 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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1231,46 +1231,6 @@ public class PgpKeyOperationTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUnlockPin() throws Exception {
|
|
||||||
|
|
||||||
Passphrase pin = new Passphrase("5235125");
|
|
||||||
|
|
||||||
// change passphrase to a pin type
|
|
||||||
parcel.mNewUnlock = new ChangeUnlockParcel(null, pin);
|
|
||||||
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
|
||||||
|
|
||||||
Assert.assertEquals("exactly three packets should have been added (the secret keys + notation packet)",
|
|
||||||
3, onlyA.size());
|
|
||||||
Assert.assertEquals("exactly four packets should have been added (the secret keys + notation packet)",
|
|
||||||
4, onlyB.size());
|
|
||||||
|
|
||||||
RawPacket dkSig = onlyB.get(1);
|
|
||||||
Assert.assertEquals("second modified packet should be notation data",
|
|
||||||
PacketTags.SIGNATURE, dkSig.tag);
|
|
||||||
|
|
||||||
// check that notation data contains pin
|
|
||||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
|
|
||||||
modified.getEncoded(), false, 0);
|
|
||||||
Assert.assertEquals("secret key type should be 'pin' after this",
|
|
||||||
SecretKeyType.PIN,
|
|
||||||
secretRing.getSecretKey().getSecretKeyTypeSuperExpensive());
|
|
||||||
|
|
||||||
// need to sleep for a sec, so the timestamp changes for notation data
|
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
{
|
|
||||||
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase("phrayse"), null);
|
|
||||||
applyModificationWithChecks(parcel, modified, onlyA, onlyB, new CryptoInputParcel(pin), true, false);
|
|
||||||
|
|
||||||
Assert.assertEquals("exactly four packets should have been removed (the secret keys + notation packet)",
|
|
||||||
4, onlyA.size());
|
|
||||||
Assert.assertEquals("exactly three packets should have been added (no more notation packet)",
|
|
||||||
3, onlyB.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRestricted() throws Exception {
|
public void testRestricted() throws Exception {
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user