mirror of
https://github.com/moparisthebest/open-keychain
synced 2025-02-17 07:30:14 -05:00
Merge branch 'master' into yubikey
Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
This commit is contained in:
commit
c0ebc92611
@ -15,6 +15,6 @@ before_install:
|
||||
- ./prepare-tests.sh
|
||||
install: echo "Installation done"
|
||||
script:
|
||||
- gradle assemble -S -q
|
||||
- gradle --info OpenKeychain-Test:testDebug
|
||||
- ./gradlew assemble -S -q
|
||||
- ./gradlew --info OpenKeychain-Test:testDebug
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
* New icons to show status of key (by Brennan Novak)
|
||||
* Important bug fix: Importing of large key collections from a file is now possible
|
||||
* Notification showing cached passphrases
|
||||
* Keys are connected to Android's contacts
|
||||
|
||||
This release wouldn't be possible without the work of Vincent Breitmoser (GSoC 2014), mar-v-in (GSoC 2014), Daniel Albert, Art O Cathain, Daniel Haß, Tim Bray, Thialfihar
|
||||
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.pgp;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
@ -16,11 +34,13 @@ import org.spongycastle.bcpg.SecretSubkeyPacket;
|
||||
import org.spongycastle.bcpg.SignaturePacket;
|
||||
import org.spongycastle.bcpg.UserIDPacket;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||
import org.sufficientlysecure.keychain.support.KeyringBuilder;
|
||||
@ -31,6 +51,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
@ -42,7 +63,7 @@ import java.util.Random;
|
||||
public class PgpKeyOperationTest {
|
||||
|
||||
static UncachedKeyRing staticRing;
|
||||
static String passphrase;
|
||||
final static String passphrase = genPassphrase();
|
||||
|
||||
UncachedKeyRing ring;
|
||||
PgpKeyOperation op;
|
||||
@ -50,37 +71,29 @@ public class PgpKeyOperationTest {
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
|
||||
@BeforeClass public static void setUpOnce() throws Exception {
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
{
|
||||
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!@#$%^&*()-_=";
|
||||
Random r = new Random();
|
||||
StringBuilder passbuilder = new StringBuilder();
|
||||
// 20% chance for an empty passphrase
|
||||
for(int i = 0, j = r.nextInt(10) > 2 ? r.nextInt(20) : 0; i < j; i++) {
|
||||
passbuilder.append(chars.charAt(r.nextInt(chars.length())));
|
||||
}
|
||||
passphrase = passbuilder.toString();
|
||||
System.out.println("Passphrase is '" + passphrase + "'");
|
||||
}
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.SIGN_DATA, null));
|
||||
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.ENCRYPT_COMMS, null));
|
||||
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
|
||||
parcel.mAddUserIds.add("twi");
|
||||
parcel.mAddUserIds.add("pink");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
staticRing = op.createSecretKeyRing(parcel).getRing();
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
Assert.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", staticRing);
|
||||
staticRing = result.getRing();
|
||||
|
||||
// we sleep here for a second, to make sure all new certificates have different timestamps
|
||||
Thread.sleep(1000);
|
||||
@ -107,57 +120,55 @@ public class PgpKeyOperationTest {
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, new Random().nextInt(256)+255, KeyFlags.CERTIFY_OTHER, null));
|
||||
Algorithm.RSA, new Random().nextInt(256)+255, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
Assert.assertNull("creating ring with < 512 bytes keysize should fail", ring);
|
||||
assertFailure("creating ring with < 512 bytes keysize should fail", parcel,
|
||||
LogType.MSG_CR_ERROR_KEYSIZE_512);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
Algorithm.ELGAMAL, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
Assert.assertNull("creating ring with ElGamal master key should fail", ring);
|
||||
assertFailure("creating ring with ElGamal master key should fail", parcel,
|
||||
LogType.MSG_CR_ERROR_FLAGS_ELGAMAL);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
12345, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddUserIds.add("shy");
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddUserIds.add("lotus");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
Assert.assertNull("creating ring with bad algorithm choice should fail", ring);
|
||||
assertFailure("creating master key with null expiry should fail", parcel,
|
||||
LogType.MSG_CR_ERROR_NULL_EXPIRY);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.SIGN_DATA, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
Assert.assertNull("creating ring with non-certifying master key should fail", ring);
|
||||
assertFailure("creating ring with non-certifying master key should fail", parcel,
|
||||
LogType.MSG_CR_ERROR_NO_CERTIFY);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
Assert.assertNull("creating ring without user ids should fail", ring);
|
||||
assertFailure("creating ring without user ids should fail", parcel,
|
||||
LogType.MSG_CR_ERROR_NO_USER_ID);
|
||||
}
|
||||
|
||||
{
|
||||
@ -165,8 +176,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
Assert.assertNull("creating ring without subkeys should fail", ring);
|
||||
assertFailure("creating ring with no master key should fail", parcel,
|
||||
LogType.MSG_CR_ERROR_NO_MASTER);
|
||||
}
|
||||
|
||||
}
|
||||
@ -177,9 +188,9 @@ public class PgpKeyOperationTest {
|
||||
public void testMasterFlags() throws Exception {
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddUserIds.add("luna");
|
||||
ring = op.createSecretKeyRing(parcel).getRing();
|
||||
ring = assertCreateSuccess("creating ring with master key flags must succeed", parcel);
|
||||
|
||||
Assert.assertEquals("the keyring should contain only the master key",
|
||||
1, KeyringTestingHelper.itToList(ring.getPublicKeys()).size());
|
||||
@ -239,10 +250,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.mMasterKeyId = ring.getMasterKeyId() -1;
|
||||
parcel.mFingerprint = ring.getFingerprint();
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with bad master key id should fail", modified);
|
||||
assertModifyFailure("keyring modification with bad master key id should fail",
|
||||
ring, parcel, LogType.MSG_MF_ERROR_KEYID);
|
||||
}
|
||||
|
||||
{
|
||||
@ -251,10 +260,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.mMasterKeyId = null;
|
||||
parcel.mFingerprint = ring.getFingerprint();
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with null master key id should fail", modified);
|
||||
assertModifyFailure("keyring modification with null master key id should fail",
|
||||
ring, parcel, LogType.MSG_MF_ERROR_KEYID);
|
||||
}
|
||||
|
||||
{
|
||||
@ -264,10 +271,8 @@ public class PgpKeyOperationTest {
|
||||
// some byte, off by one
|
||||
parcel.mFingerprint[5] += 1;
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with bad fingerprint should fail", modified);
|
||||
assertModifyFailure("keyring modification with bad fingerprint should fail",
|
||||
ring, parcel, LogType.MSG_MF_ERROR_FINGERPRINT);
|
||||
}
|
||||
|
||||
{
|
||||
@ -275,10 +280,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.mMasterKeyId = ring.getMasterKeyId();
|
||||
parcel.mFingerprint = null;
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with null fingerprint should fail", modified);
|
||||
assertModifyFailure("keyring modification with null fingerprint should fail",
|
||||
ring, parcel, LogType.MSG_MF_ERROR_FINGERPRINT);
|
||||
}
|
||||
|
||||
{
|
||||
@ -286,10 +289,9 @@ public class PgpKeyOperationTest {
|
||||
if (badphrase.equals(passphrase)) {
|
||||
badphrase = "a";
|
||||
}
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, badphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with bad passphrase should fail", modified);
|
||||
assertModifyFailure("keyring modification with bad passphrase should fail",
|
||||
ring, parcel, badphrase, LogType.MSG_MF_UNLOCK_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
@ -300,7 +302,7 @@ public class PgpKeyOperationTest {
|
||||
long expiry = new Date().getTime() / 1000 + 159;
|
||||
int flags = KeyFlags.SIGN_DATA;
|
||||
int bits = 1024 + new Random().nextInt(8);
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, bits, flags, expiry));
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(Algorithm.RSA, bits, null, flags, expiry));
|
||||
|
||||
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
|
||||
@ -336,28 +338,32 @@ public class PgpKeyOperationTest {
|
||||
Assert.assertEquals("added key must have expected flags",
|
||||
flags, newKey.getKeyUsage());
|
||||
Assert.assertEquals("added key must have expected bitsize",
|
||||
bits, newKey.getBitStrength());
|
||||
bits, (int) newKey.getBitStrength());
|
||||
|
||||
{ // bad keysize should fail
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, new Random().nextInt(512), KeyFlags.SIGN_DATA, null));
|
||||
Algorithm.RSA, new Random().nextInt(512), null, KeyFlags.SIGN_DATA, 0L));
|
||||
assertModifyFailure("creating a subkey with keysize < 512 should fail", ring, parcel,
|
||||
LogType.MSG_CR_ERROR_KEYSIZE_512);
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
}
|
||||
|
||||
Assert.assertNull("creating a subkey with keysize < 512 should fail", modified);
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, null));
|
||||
|
||||
assertModifyFailure("creating master key with null expiry should fail", ring, parcel,
|
||||
LogType.MSG_MF_ERROR_NULL_EXPIRY);
|
||||
}
|
||||
|
||||
{ // a past expiry should fail
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.SIGN_DATA,
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA,
|
||||
new Date().getTime()/1000-10));
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("creating subkey with past expiry date should fail", modified);
|
||||
assertModifyFailure("creating subkey with past expiry date should fail", ring, parcel,
|
||||
LogType.MSG_MF_ERROR_PAST_EXPIRY);
|
||||
}
|
||||
|
||||
}
|
||||
@ -394,6 +400,20 @@ public class PgpKeyOperationTest {
|
||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||
}
|
||||
|
||||
{ // change expiry
|
||||
expiry += 60*60*24;
|
||||
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, expiry));
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertNotNull("modified key must have an expiry date",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
Assert.assertEquals("modified key must have expected expiry date",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
Assert.assertEquals("modified key must have same flags as before",
|
||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||
}
|
||||
|
||||
{
|
||||
int flags = KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_COMMS;
|
||||
parcel.reset();
|
||||
@ -418,28 +438,171 @@ public class PgpKeyOperationTest {
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
}
|
||||
|
||||
{ // expiry of 0 should be "no expiry"
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, 0L));
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("old packet must be signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(0).tag);
|
||||
|
||||
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first new packet must be signature", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("signature type must be subkey binding certificate",
|
||||
PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getExpiryTime());
|
||||
}
|
||||
|
||||
{ // a past expiry should fail
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, new Date().getTime()/1000-10));
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("setting subkey expiry to a past date should fail", modified);
|
||||
assertModifyFailure("setting subkey expiry to a past date should fail", ring, parcel,
|
||||
LogType.MSG_MF_ERROR_PAST_EXPIRY);
|
||||
}
|
||||
|
||||
{ // modifying nonexistent keyring should fail
|
||||
{ // modifying nonexistent subkey should fail
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(123, null, null));
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("modifying non-existent subkey should fail", modified);
|
||||
assertModifyFailure("modifying non-existent subkey should fail", ring, parcel,
|
||||
LogType.MSG_MF_ERROR_SUBKEY_MISSING);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMasterModify() throws Exception {
|
||||
|
||||
long expiry = new Date().getTime()/1000 + 1024;
|
||||
long keyId = ring.getMasterKeyId();
|
||||
|
||||
UncachedKeyRing modified = ring;
|
||||
|
||||
// to make this check less trivial, we add a user id, change the primary one and revoke one
|
||||
parcel.mAddUserIds.add("aloe");
|
||||
parcel.mChangePrimaryUserId = "aloe";
|
||||
parcel.mRevokeUserIds.add("pink");
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, expiry));
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
// this implies that only the two non-revoked signatures were changed!
|
||||
Assert.assertEquals("two extra packets in original", 2, onlyA.size());
|
||||
Assert.assertEquals("two extra packets in modified", 2, onlyB.size());
|
||||
|
||||
Assert.assertEquals("first original packet must be a signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(0).tag);
|
||||
Assert.assertEquals("second original packet must be a signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(1).tag);
|
||||
Assert.assertEquals("first new packet must be signature",
|
||||
PacketTags.SIGNATURE, onlyB.get(0).tag);
|
||||
Assert.assertEquals("first new packet must be signature",
|
||||
PacketTags.SIGNATURE, onlyB.get(1).tag);
|
||||
|
||||
Assert.assertNotNull("modified key must have an expiry date",
|
||||
modified.getPublicKey().getExpiryTime());
|
||||
Assert.assertEquals("modified key must have expected expiry date",
|
||||
expiry, modified.getPublicKey().getExpiryTime().getTime() / 1000);
|
||||
Assert.assertEquals("modified key must have same flags as before",
|
||||
ring.getPublicKey().getKeyUsage(), modified.getPublicKey().getKeyUsage());
|
||||
}
|
||||
|
||||
{ // change expiry
|
||||
expiry += 60*60*24;
|
||||
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, expiry));
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertNotNull("modified key must have an expiry date",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
Assert.assertEquals("modified key must have expected expiry date",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
Assert.assertEquals("modified key must have same flags as before",
|
||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||
}
|
||||
|
||||
{
|
||||
int flags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA;
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, flags, null));
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("modified key must have expected flags",
|
||||
flags, modified.getPublicKey(keyId).getKeyUsage());
|
||||
Assert.assertNotNull("key must retain its expiry",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
Assert.assertEquals("key expiry must be unchanged",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
}
|
||||
|
||||
{ // expiry of 0 should be "no expiry"
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, 0L));
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getExpiryTime());
|
||||
}
|
||||
|
||||
{ // if we revoke everything, nothing is left to properly sign...
|
||||
parcel.reset();
|
||||
parcel.mRevokeUserIds.add("twi");
|
||||
parcel.mRevokeUserIds.add("pink");
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, KeyFlags.CERTIFY_OTHER, null));
|
||||
|
||||
assertModifyFailure("master key modification with all user ids revoked should fail", ring, parcel,
|
||||
LogType.MSG_MF_ERROR_MASTER_NONE);
|
||||
}
|
||||
|
||||
{ // any flag not including CERTIFY_OTHER should fail
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, KeyFlags.SIGN_DATA, null));
|
||||
|
||||
assertModifyFailure("setting master key flags without certify should fail", ring, parcel,
|
||||
LogType.MSG_MF_ERROR_NO_CERTIFY);
|
||||
}
|
||||
|
||||
{ // a past expiry should fail
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, new Date().getTime()/1000-10));
|
||||
|
||||
assertModifyFailure("setting subkey expiry to a past date should fail", ring, parcel,
|
||||
LogType.MSG_MF_ERROR_PAST_EXPIRY);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMasterRevoke() throws Exception {
|
||||
|
||||
parcel.reset();
|
||||
parcel.mRevokeSubKeys.add(ring.getMasterKeyId());
|
||||
|
||||
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("no extra packets in original", 0, onlyA.size());
|
||||
Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
|
||||
|
||||
Packet p;
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first new packet must be secret subkey", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("signature type must be subkey binding certificate",
|
||||
PGPSignature.KEY_REVOCATION, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertTrue("subkey must actually be revoked",
|
||||
modified.getPublicKey().isRevoked());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubkeyRevoke() throws Exception {
|
||||
|
||||
@ -555,10 +718,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.reset();
|
||||
parcel.mChangePrimaryUserId = uid;
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0);
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("setting primary user id to a revoked user id should fail", otherModified);
|
||||
assertModifyFailure("setting primary user id to a revoked user id should fail", modified, parcel,
|
||||
LogType.MSG_MF_ERROR_REVOKED_PRIMARY);
|
||||
|
||||
}
|
||||
|
||||
@ -596,6 +757,14 @@ public class PgpKeyOperationTest {
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
}
|
||||
|
||||
{ // revocation of non-existent user id should fail
|
||||
parcel.reset();
|
||||
parcel.mRevokeUserIds.add("nonexistent");
|
||||
|
||||
assertModifyFailure("revocation of nonexistent user id should fail", modified, parcel,
|
||||
LogType.MSG_MF_ERROR_NOEXIST_REVOKE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -603,9 +772,8 @@ public class PgpKeyOperationTest {
|
||||
|
||||
{
|
||||
parcel.mAddUserIds.add("");
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
Assert.assertNull("adding an empty user id should fail", modified);
|
||||
assertModifyFailure("adding an empty user id should fail", ring, parcel,
|
||||
LogType.MSG_MF_UID_ERROR_EMPTY);
|
||||
}
|
||||
|
||||
parcel.reset();
|
||||
@ -673,22 +841,87 @@ public class PgpKeyOperationTest {
|
||||
parcel.mChangePrimaryUserId += "A";
|
||||
}
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("changing primary user id to a non-existent one should fail", modified);
|
||||
assertModifyFailure("changing primary user id to a non-existent one should fail",
|
||||
ring, parcel, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY);
|
||||
}
|
||||
|
||||
// check for revoked primary user id already done in revoke test
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPassphraseChange() throws Exception {
|
||||
|
||||
// change passphrase to empty
|
||||
parcel.mNewPassphrase = "";
|
||||
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("exactly three packets should have been modified (the secret keys)",
|
||||
3, onlyB.size());
|
||||
|
||||
// remember secret key packet with no passphrase for later
|
||||
RawPacket sKeyNoPassphrase = onlyB.get(1);
|
||||
Assert.assertEquals("extracted packet should be a secret subkey",
|
||||
PacketTags.SECRET_SUBKEY, sKeyNoPassphrase.tag);
|
||||
|
||||
// modify keyring, change to non-empty passphrase
|
||||
String otherPassphrase = genPassphrase(true);
|
||||
parcel.mNewPassphrase = otherPassphrase;
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, "");
|
||||
|
||||
RawPacket sKeyWithPassphrase = onlyB.get(1);
|
||||
Assert.assertEquals("extracted packet should be a secret subkey",
|
||||
PacketTags.SECRET_SUBKEY, sKeyNoPassphrase.tag);
|
||||
|
||||
String otherPassphrase2 = genPassphrase(true);
|
||||
parcel.mNewPassphrase = otherPassphrase2;
|
||||
{
|
||||
// if we replace a secret key with one without passphrase
|
||||
modified = KeyringTestingHelper.removePacket(modified, sKeyNoPassphrase.position);
|
||||
modified = KeyringTestingHelper.injectPacket(modified, sKeyNoPassphrase.buf, sKeyNoPassphrase.position);
|
||||
|
||||
// we should still be able to modify it (and change its passphrase) without errors
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0);
|
||||
EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase);
|
||||
Assert.assertTrue("key modification must succeed", result.success());
|
||||
Assert.assertFalse("log must not contain a warning",
|
||||
result.getLog().containsWarnings());
|
||||
Assert.assertTrue("log must contain an empty passphrase retry notice",
|
||||
result.getLog().containsType(LogType.MSG_MF_PASSPHRASE_EMPTY_RETRY));
|
||||
modified = result.getRing();
|
||||
}
|
||||
|
||||
{
|
||||
// if we add one subkey with a different passphrase, that should produce a warning but also work
|
||||
modified = KeyringTestingHelper.removePacket(modified, sKeyWithPassphrase.position);
|
||||
modified = KeyringTestingHelper.injectPacket(modified, sKeyWithPassphrase.buf, sKeyWithPassphrase.position);
|
||||
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0);
|
||||
EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase2);
|
||||
Assert.assertTrue("key modification must succeed", result.success());
|
||||
Assert.assertTrue("log must contain a warning",
|
||||
result.getLog().containsWarnings());
|
||||
Assert.assertTrue("log must contain a failed passphrase change warning",
|
||||
result.getLog().containsType(LogType.MSG_MF_PASSPHRASE_FAIL));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
|
||||
UncachedKeyRing ring,
|
||||
ArrayList<RawPacket> onlyA,
|
||||
ArrayList<RawPacket> onlyB) {
|
||||
return applyModificationWithChecks(parcel, ring, onlyA, onlyB, true, true);
|
||||
return applyModificationWithChecks(parcel, ring, onlyA, onlyB, passphrase, true, true);
|
||||
}
|
||||
|
||||
private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
|
||||
UncachedKeyRing ring,
|
||||
ArrayList<RawPacket> onlyA,
|
||||
ArrayList<RawPacket> onlyB,
|
||||
String passphrase) {
|
||||
return applyModificationWithChecks(parcel, ring, onlyA, onlyB, passphrase, true, true);
|
||||
}
|
||||
|
||||
// applies a parcel modification while running some integrity checks
|
||||
@ -696,6 +929,7 @@ public class PgpKeyOperationTest {
|
||||
UncachedKeyRing ring,
|
||||
ArrayList<RawPacket> onlyA,
|
||||
ArrayList<RawPacket> onlyB,
|
||||
String passphrase,
|
||||
boolean canonicalize,
|
||||
boolean constantCanonicalize) {
|
||||
|
||||
@ -705,8 +939,10 @@ public class PgpKeyOperationTest {
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
UncachedKeyRing rawModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
Assert.assertNotNull("key modification failed", rawModified);
|
||||
EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase);
|
||||
Assert.assertTrue("key modification must succeed", result.success());
|
||||
UncachedKeyRing rawModified = result.getRing();
|
||||
Assert.assertNotNull("key modification must not return null", rawModified);
|
||||
|
||||
if (!canonicalize) {
|
||||
Assert.assertTrue("keyring must differ from original", KeyringTestingHelper.diffKeyrings(
|
||||
@ -753,10 +989,75 @@ public class PgpKeyOperationTest {
|
||||
*/
|
||||
@Test
|
||||
public void testConcat() throws Exception {
|
||||
byte[] actual = TestDataUtil.concatAll(new byte[]{1}, new byte[]{2,-2}, new byte[]{5},new byte[]{3});
|
||||
byte[] actual = TestDataUtil.concatAll(new byte[]{1}, new byte[]{2, -2}, new byte[]{5}, new byte[]{3});
|
||||
byte[] expected = new byte[]{1,2,-2,5,3};
|
||||
Assert.assertEquals(java.util.Arrays.toString(expected), java.util.Arrays.toString(actual));
|
||||
}
|
||||
|
||||
private void assertFailure(String reason, SaveKeyringParcel parcel, LogType expected) {
|
||||
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
|
||||
Assert.assertFalse(reason, result.success());
|
||||
Assert.assertNull(reason, result.getRing());
|
||||
Assert.assertTrue(reason + "(with correct error)",
|
||||
result.getLog().containsType(expected));
|
||||
|
||||
}
|
||||
|
||||
private void assertModifyFailure(String reason, UncachedKeyRing ring,
|
||||
SaveKeyringParcel parcel, String passphrase, LogType expected)
|
||||
throws Exception {
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase);
|
||||
|
||||
Assert.assertFalse(reason, result.success());
|
||||
Assert.assertNull(reason, result.getRing());
|
||||
Assert.assertTrue(reason + "(with correct error)",
|
||||
result.getLog().containsType(expected));
|
||||
|
||||
}
|
||||
|
||||
private void assertModifyFailure(String reason, UncachedKeyRing ring, SaveKeyringParcel parcel,
|
||||
LogType expected)
|
||||
throws Exception {
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase);
|
||||
|
||||
Assert.assertFalse(reason, result.success());
|
||||
Assert.assertNull(reason, result.getRing());
|
||||
Assert.assertTrue(reason + "(with correct error)",
|
||||
result.getLog().containsType(expected));
|
||||
|
||||
}
|
||||
|
||||
private UncachedKeyRing assertCreateSuccess(String reason, SaveKeyringParcel parcel) {
|
||||
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
|
||||
Assert.assertTrue(reason, result.success());
|
||||
Assert.assertNotNull(reason, result.getRing());
|
||||
|
||||
return result.getRing();
|
||||
|
||||
}
|
||||
|
||||
private static String genPassphrase() {
|
||||
return genPassphrase(false);
|
||||
}
|
||||
|
||||
private static String genPassphrase(boolean noEmpty) {
|
||||
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!@#$%^&*()-_=";
|
||||
Random r = new Random();
|
||||
StringBuilder passbuilder = new StringBuilder();
|
||||
// 20% chance for an empty passphrase
|
||||
for(int i = 0, j = noEmpty || r.nextInt(10) > 2 ? r.nextInt(20)+1 : 0; i < j; i++) {
|
||||
passbuilder.append(chars.charAt(r.nextInt(chars.length())));
|
||||
}
|
||||
System.out.println("Generated passphrase: '" + passbuilder.toString() + "'");
|
||||
return passbuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.pgp;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
@ -13,6 +31,7 @@ import org.spongycastle.bcpg.PacketTags;
|
||||
import org.spongycastle.bcpg.UserIDPacket;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
@ -30,10 +49,12 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
@ -60,15 +81,16 @@ public class UncachedKeyringCanonicalizeTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.SIGN_DATA, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.ENCRYPT_COMMS, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
|
||||
parcel.mAddUserIds.add("twi");
|
||||
parcel.mAddUserIds.add("pink");
|
||||
@ -277,7 +299,7 @@ public class UncachedKeyringCanonicalizeTest {
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddUserIds.add("trix");
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.pgp;
|
||||
|
||||
import org.junit.Assert;
|
||||
@ -10,14 +28,17 @@ import org.robolectric.shadows.ShadowLog;
|
||||
import org.spongycastle.bcpg.PacketTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
@ -59,14 +80,15 @@ public class UncachedKeyringMergeTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.SIGN_DATA, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
|
||||
|
||||
parcel.mAddUserIds.add("twi");
|
||||
parcel.mAddUserIds.add("pink");
|
||||
@ -83,7 +105,7 @@ public class UncachedKeyringMergeTest {
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
|
||||
parcel.mAddUserIds.add("shy");
|
||||
// passphrase is tested in PgpKeyOperationTest, just use empty here
|
||||
@ -189,7 +211,7 @@ public class UncachedKeyringMergeTest {
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.SIGN_DATA, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
|
||||
modifiedA = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
modifiedB = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.pgp;
|
||||
|
||||
import org.junit.Assert;
|
||||
@ -13,6 +31,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@ -37,11 +56,11 @@ public class UncachedKeyringTest {
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.SIGN_DATA, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, 1024, KeyFlags.ENCRYPT_COMMS, null));
|
||||
Algorithm.RSA, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
|
||||
parcel.mAddUserIds.add("twi");
|
||||
parcel.mAddUserIds.add("pink");
|
||||
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.provider;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
public class ProviderHelperSaveTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
ShadowLog.stream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongKeyIdCollision() throws Exception {
|
||||
|
||||
UncachedKeyRing first =
|
||||
readRingFromResource("/cooperpair/9E669861368BCA0BE42DAF7DDDA252EBB8EBE1AF.asc");
|
||||
UncachedKeyRing second =
|
||||
readRingFromResource("/cooperpair/A55120427374F3F7AA5F1166DDA252EBB8EBE1AF.asc");
|
||||
|
||||
SaveKeyringResult result;
|
||||
|
||||
// insert both keys, second should fail
|
||||
result = new ProviderHelper(Robolectric.application).savePublicKeyRing(first);
|
||||
Assert.assertTrue("first keyring import should succeed", result.success());
|
||||
result = new ProviderHelper(Robolectric.application).savePublicKeyRing(second);
|
||||
Assert.assertFalse("second keyring import should fail", result.success());
|
||||
|
||||
new KeychainDatabase(Robolectric.application).clearDatabase();
|
||||
|
||||
// and the other way around
|
||||
result = new ProviderHelper(Robolectric.application).savePublicKeyRing(second);
|
||||
Assert.assertTrue("first keyring import should succeed", result.success());
|
||||
result = new ProviderHelper(Robolectric.application).savePublicKeyRing(first);
|
||||
Assert.assertFalse("second keyring import should fail", result.success());
|
||||
|
||||
}
|
||||
|
||||
UncachedKeyRing readRingFromResource(String name) throws Exception {
|
||||
return UncachedKeyRing.fromStream(ProviderHelperSaveTest.class.getResourceAsStream(name)).next();
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) Art O Cathain, Vincent Breitmoser
|
||||
* Copyright (C) Art O Cathain
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -14,6 +15,7 @@
|
||||
* 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.support;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
@ -25,7 +43,7 @@ public class FileImportCacheTest {
|
||||
@Test
|
||||
public void testInputOutput() throws Exception {
|
||||
|
||||
FileImportCache<Bundle> cache = new FileImportCache<Bundle>(Robolectric.application);
|
||||
FileImportCache<Bundle> cache = new FileImportCache<Bundle>(Robolectric.application, "test.pcl");
|
||||
|
||||
ArrayList<Bundle> list = new ArrayList<Bundle>();
|
||||
|
||||
|
@ -0,0 +1,29 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1
|
||||
|
||||
mQINBFJtd/UBEACpw/psXoGNM8RHczviD7FnGdjMQPEJQ+nuWQ2AEGYouulg5hFv
|
||||
0ChuSQVLiqQht2k5K2liyW1MeXoJ8tr9nSn/Zi9nttc0Wo6K7pvrDD40r2HNg305
|
||||
qLCzItr5st3x8cq2cIXvN4LOm2rqpBLZ/sqMmNiW2Y7/aAQqV1xtR35joHqamWHD
|
||||
UPOmzBMs07YSUjXgC1EMx8kWQSV6cuARj93kxWj8R6eoYHHfrWCEGR313wov6QST
|
||||
zIfVU7FqQqOmdLW3LaPHxcrI/TjsnkUN99qdlpjJH/YW925LDPJHAkliqPP5AvhU
|
||||
F9KbY2F8mcIZBCDd8TH+xXynuN3BbIU4kCwVbdx/tcpO1npuJcKB1Go/udyow/Ei
|
||||
Z3nHzJsCVkezvopek77wnwPaP0nAb7f4iIY3gJCoGirOx6N075TgF6MBe00q9oFE
|
||||
y4rvnUnU9/QzOOes95eUMhM+9eK1cuLFEV5t47DfxRdq+fQip3FJ2l6v19sZvQ0G
|
||||
j06pjYqg0of273rG8oXcDrFjb1Zqhj8x1mLl6u7d/ide5wTm9HylBWcYKQjIJJAi
|
||||
WIScxEPIOINDJKgsKTuKtoyNvISJ3xUeS1yzxiIb3YGLIyPgFFx0vFyqJfbkXq70
|
||||
m1n2xnJlkTidfzbZvc6EA7vRGSDYK6FqqhlGhc7UypUEVW8FM/jZNAOS6QARAUGt
|
||||
tCg5RTY2OTg2MTM2OEJDQTBCRTQyREFGN0REREEyNTJFQkI4RUJFMUFGiQI3BBMB
|
||||
CgAhBQJSg/uTAhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEN2iUuu46+Gv
|
||||
+Z0P+wQhkLwm+WGcEsS98Lei9O7hit/k4g/VkLUUQV7BOR3n8uRZIFkdOtpvrFU3
|
||||
aKf246uCy6GM48Oh+1U2cv5InX/WEuKaFo5uF6t79wyt18BUn1weDcU+DQdOSG4f
|
||||
fSnNa55wkN0l0svW4fGIthjmDTz6HZFntYD+9A20wZAqpPIs+vyG9Jp+e9E9Y/W/
|
||||
EFQbNlxHHb9+BMT2+DtNP+HSl3MPFlQPKOLZxyLAU5uzT0Sa0LxhrQy5FgkW6Jog
|
||||
sbAJVM9z0pZw+grzGPciM66ZW1rxeICvbYsdWLytRjqxpY8GS8XudyseUGd+dZim
|
||||
ptarsrE5yfSMg2gW5Z1PTc0tEMXJLUwtpyzQjpFpbb7dPuo2TUp09LgZKX63WCbS
|
||||
Nb1RTaGfkeYudOTo2rh4Jfg+Tb/JRpO6clo0rxAq8nPH2WmG+9TB8Zbb7YRzGWuV
|
||||
/e5SeVNR+zY8tXZKnmUIH1HIprc+BtT6Bupdvd0CT14Mg9MmsFvUXofwHLa4gahr
|
||||
8/iG9y3uHSA6Rhz++yOpyOmNvO1LDxsYNaRCIXQJbqgNwF5YNYlMPsEeY/CG7FOb
|
||||
Afv7rHiYtRRQfz2P4OF900DJO7QL9gdNXJ1+Hajy/5Lvvl7qwqMG4GvVQEsgFc5O
|
||||
jjFCUhE2i20j2kEMxvA5RLBH/fOoGARn87tiKSfb+pqLNZQb
|
||||
=fDJ8
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
@ -0,0 +1,29 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1
|
||||
|
||||
mQINBFKD+38BEADSv5l4xOx9hCRJVcybq6yK5hTpGSFf3xo1bkhoMvyC62ehb4jD
|
||||
MDLwwNRyzCBEWQJLbq/LLizPFN2qXFJpXJcsuqsHNYRtDqDBEjtriRQwSqHnqTXt
|
||||
c0K46FYHldCJQ4/tBXxPI+WwtXjcNRWaV7n2BvR/Jk+B5e4Zz3LPnN0C4w5vORHs
|
||||
hN1jil8A3Hs/F+OmlQYrU8ZtNwTpSo2EXxe2fVgSDCsKRyNsPZj++OyujPzW+yaN
|
||||
lJ9I/q6s9gvX9o9o7nwZbqBETipWsdRK6RfBdTKpnyLNordbWwWTk6GxN8T5Ppit
|
||||
P6a3UlQ71VuflcswCTmEQ1pEfZrlRFKa9psBOW+cZLNxT9h0jGFMh6/B3w48Sag+
|
||||
cFcPBFWParC+cAXBIURDxT9G6bzNLogg7YKoaPsyiXnLDH2VJUCXs27D2wPJL24Q
|
||||
S7npvsg63MPPssWgG5cauLznmNR4y5pQi6oH/C10v0zrUJy6FPJzQhYRhWOvhtz6
|
||||
j88RGMrFNNCdB2VACtn699D+ixu3nRlXHIKCT+xLSfgslVYifmJOCNljBLGHOQ1e
|
||||
FJxQuNVpmmxjvk/8kqK+pHLB9Qn6M1ZYzip7OyUL3OAWabCabgEw2bQmUhiBWD3u
|
||||
buv0WAVOJEAFvBCAeYNQzrQMY+Rc3RnvynG4pI6Tbo8wC6/IJcDOw516JwARASB3
|
||||
tChBNTUxMjA0MjczNzRGM0Y3QUE1RjExNjZEREEyNTJFQkI4RUJFMUFGiQI3BBMB
|
||||
CgAhBQJSg/uTAhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEN2iUuu46+Gv
|
||||
9L0P/3tFu0LOZ/dAPjUNfKJCZqcIuVnD5xShMTsUbVx+QoXMy7rt4iRLD7ofGi/I
|
||||
vTAZehxk3sk/Slx5nbews+3NItyw6mcaP9HlmwKNr6k7BC2kJHcCxH4DNzhmIx1H
|
||||
3T/CggtHX42JBYKlGf22y+M8jAbvsPOUfTznx96mYNrOY6s1dJyn0kRleqJ8+tGj
|
||||
/5+0y90iZnGCa0FtacQkKUPkXwVodeZVxk8z5OEipShYKc+8dl+5WsvOzHqLC/KY
|
||||
xCGRb4JaqEMwouLNg8dTNAXXUvFGqJNDX4+andggogmI1hdD9xExfSU9cAGegg2t
|
||||
vvveC4S+CCHd+zt88iK5ze6F61RxwYhhNbkuFGjdgNGCpHtG/BQhKnYJuKEbq3oi
|
||||
mgNyxJERlfgaWXveiMG0AmACXN+jCkTtqZjQnsg2N2QDL3tjY7usmuiwRL1aVOFG
|
||||
Kw5/Cc+2nDeANS3Xi1403Ni269b1c6kNSoLe4zd0WsbO3Kouds8F8EQfeheXQe97
|
||||
ZxuvBOMsR9wHC3f0sl/vfxCGdUC+khmKk5taKnUeUFJmVmh5ghlVy8FySHGB0QHO
|
||||
zd8GUl59rFpQJNpNFQW2YKDhrcjxIr2AeJrdoDI6NsQ02+Qtep/bbq53hqtAD4jF
|
||||
t3S8vBbTXtRk6g2qn4ojF4SOIc8SAiZcURgVFuSJX8ngFbO4
|
||||
=OEw/
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
3
OpenKeychain-Test/src/test/resources/cooperpair/readme
Normal file
3
OpenKeychain-Test/src/test/resources/cooperpair/readme
Normal file
@ -0,0 +1,3 @@
|
||||
"Cooperpair" testcase under public domain license, by @coruus:
|
||||
|
||||
https://github.com/coruus/cooperpair/tree/master/pgpv4
|
@ -3,8 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.sufficientlysecure.keychain"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="27000"
|
||||
android:versionName="2.7">
|
||||
android:versionCode="28000"
|
||||
android:versionName="2.8">
|
||||
|
||||
<!--
|
||||
General remarks
|
||||
@ -176,7 +176,7 @@
|
||||
android:label="@string/title_decrypt"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
|
||||
<!-- VIEW with mimeType application/pgp-encrypted -->
|
||||
<!-- VIEW with mimeType application/octet-stream, application/pgp and text/pgp -->
|
||||
<intent-filter android:label="@string/intent_send_decrypt">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@ -184,7 +184,9 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<!-- mime type as defined in http://tools.ietf.org/html/rfc3156 -->
|
||||
<data android:mimeType="application/pgp-encrypted" />
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
<data android:mimeType="application/pgp" />
|
||||
<data android:mimeType="text/pgp" />
|
||||
</intent-filter>
|
||||
<!-- Keychain's own Actions -->
|
||||
<!-- DECRYPT with text as extra -->
|
||||
@ -556,6 +558,10 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ui.KeyListActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.ConsolidateDialogActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:label="@string/app_name" />
|
||||
<activity
|
||||
android:name=".ui.HelpActivity"
|
||||
android:label="@string/title_help" />
|
||||
@ -628,7 +634,10 @@
|
||||
android:resource="@xml/account_desc" />
|
||||
</service>
|
||||
|
||||
<service android:name=".service.ContactSyncAdapterService">
|
||||
<service
|
||||
android:name=".service.ContactSyncAdapterService"
|
||||
android:exported="true"
|
||||
android:process=":sync">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
@ -35,6 +35,9 @@ public final class Constants {
|
||||
|
||||
public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain";
|
||||
|
||||
public static final String ACCOUNT_NAME = "OpenKeychain";
|
||||
public static final String ACCOUNT_TYPE = PACKAGE_NAME + ".account";
|
||||
|
||||
// as defined in http://tools.ietf.org/html/rfc3156, section 7
|
||||
public static final String NFC_MIME = "application/pgp-keys";
|
||||
|
||||
@ -68,11 +71,14 @@ public final class Constants {
|
||||
public static final String KEY_SERVERS_DEFAULT_VERSION = "keyServersDefaultVersion";
|
||||
public static final String WRITE_VERSION_HEADER = "writeVersionHeader";
|
||||
public static final String FIRST_TIME = "firstTime";
|
||||
public static final String CACHED_CONSOLIDATE = "cachedConsolidate";
|
||||
public static final String CACHED_CONSOLIDATE_SECRETS = "cachedConsolidateSecrets";
|
||||
public static final String CACHED_CONSOLIDATE_PUBLICS = "cachedConsolidatePublics";
|
||||
}
|
||||
|
||||
public static final class Defaults {
|
||||
public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, subkeys.pgp.net, hkps://pgp.mit.edu";
|
||||
public static final int KEY_SERVERS_VERSION = 2;
|
||||
public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, hkps://pgp.mit.edu";
|
||||
public static final int KEY_SERVERS_VERSION = 3;
|
||||
}
|
||||
|
||||
public static final class DrawerItems {
|
||||
|
@ -20,15 +20,20 @@ package org.sufficientlysecure.keychain;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Application;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.ContactsContract;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.helper.TlsHelper;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PRNGFixes;
|
||||
|
||||
@ -89,14 +94,37 @@ public class KeychainApplication extends Application {
|
||||
TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
|
||||
|
||||
TemporaryStorageProvider.cleanUp(this);
|
||||
|
||||
checkConsolidateRecovery();
|
||||
|
||||
}
|
||||
|
||||
public void checkConsolidateRecovery() {
|
||||
|
||||
// restart consolidate process if it has been interruped before
|
||||
if (Preferences.getPreferences(this).getCachedConsolidate()) {
|
||||
// do something which calls ProviderHelper.consolidateDatabaseStep2 with a progressable
|
||||
Intent consolidateIntent = new Intent(this, ConsolidateDialogActivity.class);
|
||||
consolidateIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(consolidateIntent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void setupAccountAsNeeded(Context context) {
|
||||
AccountManager manager = AccountManager.get(context);
|
||||
Account[] accounts = manager.getAccountsByType(Constants.PACKAGE_NAME);
|
||||
if (accounts == null || accounts.length == 0) {
|
||||
Account dummy = new Account(context.getString(R.string.app_name), Constants.PACKAGE_NAME);
|
||||
manager.addAccountExplicitly(dummy, null, null);
|
||||
// only enabled for Jelly Bean because we need some newer methods in our sync adapter
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
AccountManager manager = AccountManager.get(context);
|
||||
Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE);
|
||||
if (accounts == null || accounts.length == 0) {
|
||||
Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
|
||||
if (manager.addAccountExplicitly(account, null, null)) {
|
||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
|
||||
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Adding account failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.helper;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
@ -234,6 +235,25 @@ public class ContactHelper {
|
||||
return new ArrayList<String>(mails);
|
||||
}
|
||||
|
||||
public static List<String> getContactNames(Context context) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Cursor cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI,
|
||||
new String[]{ContactsContract.Contacts.DISPLAY_NAME},
|
||||
null, null, null);
|
||||
if (cursor == null) return null;
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
while (cursor.moveToNext()) {
|
||||
String name = cursor.getString(0);
|
||||
if (name != null) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
return new ArrayList<String>(names);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public static Uri dataUriFromContactUri(Context context, Uri contactUri) {
|
||||
Cursor contactMasterKey = context.getContentResolver().query(contactUri,
|
||||
new String[]{ContactsContract.Data.DATA2}, null, null, null, null);
|
||||
@ -323,7 +343,7 @@ public class ContactHelper {
|
||||
// Delete fingerprints that are no longer present in OK
|
||||
for (String fingerprint : contactFingerprints) {
|
||||
resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION,
|
||||
new String[]{Constants.PACKAGE_NAME, fingerprint});
|
||||
new String[]{Constants.ACCOUNT_TYPE, fingerprint});
|
||||
}
|
||||
|
||||
}
|
||||
@ -334,7 +354,7 @@ public class ContactHelper {
|
||||
private static Set<String> getRawContactFingerprints(ContentResolver resolver) {
|
||||
HashSet<String> result = new HashSet<String>();
|
||||
Cursor fingerprints = resolver.query(ContactsContract.RawContacts.CONTENT_URI, SOURCE_ID_PROJECTION,
|
||||
ACCOUNT_TYPE_SELECTION, new String[]{Constants.PACKAGE_NAME}, null);
|
||||
ACCOUNT_TYPE_SELECTION, new String[]{Constants.ACCOUNT_TYPE}, null);
|
||||
if (fingerprints != null) {
|
||||
while (fingerprints.moveToNext()) {
|
||||
result.add(fingerprints.getString(0));
|
||||
@ -349,10 +369,11 @@ public class ContactHelper {
|
||||
*
|
||||
* @return raw contact id or -1 if not found
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
private static int findRawContactId(ContentResolver resolver, String fingerprint) {
|
||||
int rawContactId = -1;
|
||||
Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, ID_PROJECTION,
|
||||
ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, new String[]{Constants.PACKAGE_NAME, fingerprint}, null, null);
|
||||
ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, new String[]{Constants.ACCOUNT_TYPE, fingerprint}, null, null);
|
||||
if (raw != null) {
|
||||
if (raw.moveToNext()) {
|
||||
rawContactId = raw.getInt(0);
|
||||
@ -367,8 +388,8 @@ public class ContactHelper {
|
||||
*/
|
||||
private static void insertContact(ArrayList<ContentProviderOperation> ops, Context context, String fingerprint) {
|
||||
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, context.getString(R.string.app_name))
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.PACKAGE_NAME)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE)
|
||||
.withValue(ContactsContract.RawContacts.SOURCE_ID, fingerprint)
|
||||
.build());
|
||||
}
|
||||
|
@ -25,7 +25,12 @@ import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Constants.Pref;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
@ -130,6 +135,36 @@ public class Preferences {
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getCachedConsolidate() {
|
||||
return mSharedPreferences.getBoolean(Pref.CACHED_CONSOLIDATE, false);
|
||||
}
|
||||
|
||||
public void setCachedConsolidate(boolean value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Pref.CACHED_CONSOLIDATE, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public int getCachedConsolidateNumPublics() {
|
||||
return mSharedPreferences.getInt(Pref.CACHED_CONSOLIDATE_PUBLICS, -1);
|
||||
}
|
||||
|
||||
public void setCachedConsolidateNumPublics(int value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putInt(Pref.CACHED_CONSOLIDATE_PUBLICS, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public int getCachedConsolidateNumSecrets() {
|
||||
return mSharedPreferences.getInt(Pref.CACHED_CONSOLIDATE_SECRETS, -1);
|
||||
}
|
||||
|
||||
public void setCachedConsolidateNumSecrets(int value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putInt(Pref.CACHED_CONSOLIDATE_SECRETS, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean isFirstTime() {
|
||||
return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
|
||||
}
|
||||
@ -175,15 +210,24 @@ public class Preferences {
|
||||
// migrate keyserver to hkps
|
||||
if (mSharedPreferences.getInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, 0) !=
|
||||
Constants.Defaults.KEY_SERVERS_VERSION) {
|
||||
String[] servers = getKeyServers();
|
||||
for (int i = 0; i < servers.length; i++) {
|
||||
if (servers[i].equals("pool.sks-keyservers.net")) {
|
||||
servers[i] = "hkps://hkps.pool.sks-keyservers.net";
|
||||
} else if (servers[i].equals("pgp.mit.edu")) {
|
||||
servers[i] = "hkps://pgp.mit.edu";
|
||||
String[] serversArray = getKeyServers();
|
||||
ArrayList<String> servers = new ArrayList<String>(Arrays.asList(serversArray));
|
||||
ListIterator<String> it = servers.listIterator();
|
||||
while (it.hasNext()) {
|
||||
String server = it.next();
|
||||
if (server.equals("pool.sks-keyservers.net")) {
|
||||
// use HKPS!
|
||||
it.set("hkps://hkps.pool.sks-keyservers.net");
|
||||
} else if (server.equals("pgp.mit.edu")) {
|
||||
// use HKPS!
|
||||
it.set("hkps://pgp.mit.edu");
|
||||
} else if (server.equals("subkeys.pgp.net")) {
|
||||
// remove, because often down and no HKPS!
|
||||
it.remove();
|
||||
}
|
||||
|
||||
}
|
||||
setKeyServers(servers);
|
||||
setKeyServers(servers.toArray(new String[servers.size()]));
|
||||
mSharedPreferences.edit()
|
||||
.putInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, Constants.Defaults.KEY_SERVERS_VERSION)
|
||||
.commit();
|
||||
@ -193,6 +237,11 @@ public class Preferences {
|
||||
if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, 0) == 0x21070001) {
|
||||
setDefaultFileCompression(CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
}
|
||||
|
||||
// migrate away from MD5
|
||||
if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, 0) == HashAlgorithmTags.MD5) {
|
||||
setDefaultHashAlgorithm(HashAlgorithmTags.SHA512);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWriteVersionHeader(boolean conceal) {
|
||||
|
@ -24,8 +24,10 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
@ -281,7 +283,7 @@ public class HkpKeyserver extends Keyserver {
|
||||
entry.setBitStrength(Integer.parseInt(matcher.group(3)));
|
||||
|
||||
final int algorithmId = Integer.decode(matcher.group(2));
|
||||
entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId));
|
||||
entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId, null, null));
|
||||
|
||||
// group 1 contains the full fingerprint (v4) or the long key id if available
|
||||
// see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
|
||||
@ -352,25 +354,38 @@ public class HkpKeyserver extends Keyserver {
|
||||
@Override
|
||||
public void add(String armoredKey) throws AddKeyException {
|
||||
try {
|
||||
String query = getUrlPrefix() + mHost + ":" + mPort + "/pks/add";
|
||||
String request = "/pks/add";
|
||||
String params;
|
||||
try {
|
||||
params = "keytext=" + URLEncoder.encode(armoredKey, "utf8");
|
||||
params = "keytext=" + URLEncoder.encode(armoredKey, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AddKeyException();
|
||||
}
|
||||
Log.d(Constants.TAG, "hkp keyserver add: " + query);
|
||||
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request);
|
||||
|
||||
HttpURLConnection connection = openConnection(new URL(query));
|
||||
connection.setRequestMethod("POST");
|
||||
connection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
connection.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length));
|
||||
connection.setDoOutput(true);
|
||||
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
|
||||
wr.writeBytes(params);
|
||||
wr.flush();
|
||||
wr.close();
|
||||
Log.d(Constants.TAG, "hkp keyserver add: " + url.toString());
|
||||
Log.d(Constants.TAG, "params: " + params);
|
||||
|
||||
HttpURLConnection conn = openConnection(url);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length));
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
|
||||
OutputStream os = conn.getOutputStream();
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
|
||||
writer.write(params);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
os.close();
|
||||
|
||||
conn.connect();
|
||||
|
||||
Log.d(Constants.TAG, "response code: " + conn.getResponseCode());
|
||||
Log.d(Constants.TAG, "answer: " + readAll(conn.getInputStream(), conn.getContentEncoding()));
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException", e);
|
||||
throw new AddKeyException();
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
private boolean mExpired;
|
||||
private Date mDate; // TODO: not displayed
|
||||
private String mFingerprintHex;
|
||||
private int mBitStrength;
|
||||
private Integer mBitStrength;
|
||||
private String mCurveOid;
|
||||
private String mAlgorithm;
|
||||
private boolean mSecretKey;
|
||||
private String mPrimaryUserId;
|
||||
@ -162,10 +163,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
this.mFingerprintHex = fingerprintHex;
|
||||
}
|
||||
|
||||
public int getBitStrength() {
|
||||
public Integer getBitStrength() {
|
||||
return mBitStrength;
|
||||
}
|
||||
|
||||
public String getCurveOid() {
|
||||
return mCurveOid;
|
||||
}
|
||||
|
||||
public void setBitStrength(int bitStrength) {
|
||||
this.mBitStrength = bitStrength;
|
||||
}
|
||||
@ -258,13 +263,15 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
mPrimaryUserId = mUserIds.get(0);
|
||||
}
|
||||
|
||||
this.mKeyId = key.getKeyId();
|
||||
this.mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId);
|
||||
mKeyId = key.getKeyId();
|
||||
mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId);
|
||||
|
||||
this.mRevoked = key.isRevoked();
|
||||
this.mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint());
|
||||
this.mBitStrength = key.getBitStrength();
|
||||
mRevoked = key.isRevoked();
|
||||
mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint());
|
||||
mBitStrength = key.getBitStrength();
|
||||
mCurveOid = key.getCurveOid();
|
||||
final int algorithm = key.getAlgorithm();
|
||||
this.mAlgorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm);
|
||||
mAlgorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm, mBitStrength, mCurveOid);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ public class KeybaseKeyserver extends Keyserver {
|
||||
entry.setExtraData(username);
|
||||
|
||||
final int algorithmId = match.getAlgorithmId();
|
||||
entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId));
|
||||
entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId, null, null));
|
||||
final int bitStrength = match.getBitStrength();
|
||||
entry.setBitStrength(bitStrength);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -60,10 +61,6 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
||||
return mRing;
|
||||
}
|
||||
|
||||
public void encode(ArmoredOutputStream stream) throws IOException {
|
||||
getRing().encode(stream);
|
||||
}
|
||||
|
||||
/** Getter that returns the subkey that should be used for signing. */
|
||||
CanonicalizedPublicKey getEncryptionSubKey() throws PgpGeneralException {
|
||||
PGPPublicKey key = getRing().getPublicKey(getEncryptId());
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -523,13 +523,7 @@ public class PgpDecryptVerify {
|
||||
|
||||
// update signature buffer if signature is also present
|
||||
if (signature != null) {
|
||||
try {
|
||||
signature.update(buffer, 0, length);
|
||||
} catch (SignatureException e) {
|
||||
Log.e(Constants.TAG, "SignatureException -> Not a valid signature!", e);
|
||||
signatureResultBuilder.validSignature(false);
|
||||
signature = null;
|
||||
}
|
||||
signature.update(buffer, 0, length);
|
||||
}
|
||||
|
||||
alreadyWritten += length;
|
||||
|
@ -29,7 +29,6 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.regex.Pattern;
|
||||
@ -68,76 +67,6 @@ public class PgpHelper {
|
||||
}
|
||||
}
|
||||
|
||||
// public static final class content {
|
||||
// public static final int unknown = 0;
|
||||
// public static final int encrypted_data = 1;
|
||||
// public static final int keys = 2;
|
||||
// }
|
||||
//
|
||||
// public static int getStreamContent(Context context, InputStream inStream) throws IOException {
|
||||
//
|
||||
// InputStream in = PGPUtil.getDecoderStream(inStream);
|
||||
// PGPObjectFactory pgpF = new PGPObjectFactory(in);
|
||||
// Object object = pgpF.nextObject();
|
||||
// while (object != null) {
|
||||
// if (object instanceof PGPPublicKeyRing || object instanceof PGPSecretKeyRing) {
|
||||
// return Id.content.keys;
|
||||
// } else if (object instanceof PGPEncryptedDataList) {
|
||||
// return Id.content.encrypted_data;
|
||||
// }
|
||||
// object = pgpF.nextObject();
|
||||
// }
|
||||
//
|
||||
// return Id.content.unknown;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Generate a random filename
|
||||
*
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
public static String generateRandomFilename(int length) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
|
||||
byte bytes[] = new byte[length];
|
||||
random.nextBytes(bytes);
|
||||
String result = "";
|
||||
for (int i = 0; i < length; ++i) {
|
||||
int v = (bytes[i] + 256) % 64;
|
||||
if (v < 10) {
|
||||
result += (char) ('0' + v);
|
||||
} else if (v < 36) {
|
||||
result += (char) ('A' + v - 10);
|
||||
} else if (v < 62) {
|
||||
result += (char) ('a' + v - 36);
|
||||
} else if (v == 62) {
|
||||
result += '_';
|
||||
} else if (v == 63) {
|
||||
result += '.';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go once through stream to get length of stream. The length is later used to display progress
|
||||
* when encrypting/decrypting
|
||||
*
|
||||
* @param in
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static long getLengthOfStream(InputStream in) throws IOException {
|
||||
long size = 0;
|
||||
long n = 0;
|
||||
byte dummy[] = new byte[0x10000];
|
||||
while ((n = in.read(dummy)) > 0) {
|
||||
size += n;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes file securely by overwriting it with random data before deleting it.
|
||||
* <p/>
|
||||
|
@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
@ -43,6 +44,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class PgpImportExport {
|
||||
@ -60,10 +62,14 @@ public class PgpImportExport {
|
||||
private ProviderHelper mProviderHelper;
|
||||
|
||||
public PgpImportExport(Context context, Progressable progressable) {
|
||||
this(context, new ProviderHelper(context), progressable);
|
||||
}
|
||||
|
||||
public PgpImportExport(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
||||
super();
|
||||
this.mContext = context;
|
||||
this.mProgressable = progressable;
|
||||
this.mProviderHelper = new ProviderHelper(context);
|
||||
this.mProviderHelper = providerHelper;
|
||||
}
|
||||
|
||||
public PgpImportExport(Context context,
|
||||
@ -93,7 +99,7 @@ public class PgpImportExport {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) {
|
||||
public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) throws AddKeyException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream aos = null;
|
||||
try {
|
||||
@ -103,13 +109,9 @@ public class PgpImportExport {
|
||||
|
||||
String armoredKey = bos.toString("UTF-8");
|
||||
server.add(armoredKey);
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} catch (AddKeyException e) {
|
||||
// TODO: tell the user?
|
||||
return false;
|
||||
Log.e(Constants.TAG, "IOException", e);
|
||||
throw new AddKeyException();
|
||||
} finally {
|
||||
try {
|
||||
if (aos != null) {
|
||||
@ -124,20 +126,23 @@ public class PgpImportExport {
|
||||
|
||||
/** Imports keys from given data. If keyIds is given only those are imported */
|
||||
public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries) {
|
||||
return importKeyRings(entries.iterator(), entries.size());
|
||||
}
|
||||
|
||||
public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num) {
|
||||
updateProgress(R.string.progress_importing, 0, 100);
|
||||
|
||||
// If there aren't even any keys, do nothing here.
|
||||
if (entries == null || entries.size() == 0) {
|
||||
if (entries == null || !entries.hasNext()) {
|
||||
return new ImportKeyResult(
|
||||
ImportKeyResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0);
|
||||
ImportKeyResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
int newKeys = 0, oldKeys = 0, badKeys = 0;
|
||||
int newKeys = 0, oldKeys = 0, badKeys = 0, secret = 0;
|
||||
|
||||
int position = 0;
|
||||
int progSteps = 100 / entries.size();
|
||||
for (ParcelableKeyRing entry : entries) {
|
||||
double progSteps = 100.0 / num;
|
||||
for (ParcelableKeyRing entry : new IterableIterator<ParcelableKeyRing>(entries)) {
|
||||
try {
|
||||
UncachedKeyRing key = UncachedKeyRing.decodeFromData(entry.getBytes());
|
||||
|
||||
@ -157,10 +162,10 @@ public class PgpImportExport {
|
||||
SaveKeyringResult result;
|
||||
if (key.isSecret()) {
|
||||
result = mProviderHelper.saveSecretKeyRing(key,
|
||||
new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100));
|
||||
new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100));
|
||||
} else {
|
||||
result = mProviderHelper.savePublicKeyRing(key,
|
||||
new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100));
|
||||
new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100));
|
||||
}
|
||||
if (!result.success()) {
|
||||
badKeys += 1;
|
||||
@ -168,6 +173,9 @@ public class PgpImportExport {
|
||||
oldKeys += 1;
|
||||
} else {
|
||||
newKeys += 1;
|
||||
if (key.isSecret()) {
|
||||
secret += 1;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
@ -204,7 +212,7 @@ public class PgpImportExport {
|
||||
}
|
||||
}
|
||||
|
||||
return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys);
|
||||
return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys, secret);
|
||||
|
||||
}
|
||||
|
||||
|
@ -24,10 +24,16 @@ import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
|
||||
import org.spongycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.spongycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.spongycastle.bcpg.ECPublicBCPGKey;
|
||||
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.security.DigestException;
|
||||
@ -37,18 +43,14 @@ import java.util.Locale;
|
||||
|
||||
public class PgpKeyHelper {
|
||||
|
||||
public static String getAlgorithmInfo(int algorithm) {
|
||||
return getAlgorithmInfo(null, algorithm, 0);
|
||||
}
|
||||
|
||||
public static String getAlgorithmInfo(Context context, int algorithm) {
|
||||
return getAlgorithmInfo(context, algorithm, 0);
|
||||
public static String getAlgorithmInfo(int algorithm, Integer keySize, String oid) {
|
||||
return getAlgorithmInfo(null, algorithm, keySize, oid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
|
||||
*/
|
||||
public static String getAlgorithmInfo(Context context, int algorithm, int keySize) {
|
||||
public static String getAlgorithmInfo(Context context, int algorithm, Integer keySize, String oid) {
|
||||
String algorithmStr;
|
||||
|
||||
switch (algorithm) {
|
||||
@ -69,10 +71,19 @@ public class PgpKeyHelper {
|
||||
break;
|
||||
}
|
||||
|
||||
case PublicKeyAlgorithmTags.ECDSA:
|
||||
case PublicKeyAlgorithmTags.ECDSA: {
|
||||
if (oid == null) {
|
||||
return "ECDSA";
|
||||
}
|
||||
String oidName = PgpKeyHelper.getCurveInfo(context, oid);
|
||||
return "ECDSA (" + oidName + ")";
|
||||
}
|
||||
case PublicKeyAlgorithmTags.ECDH: {
|
||||
algorithmStr = "ECC";
|
||||
break;
|
||||
if (oid == null) {
|
||||
return "ECDH";
|
||||
}
|
||||
String oidName = PgpKeyHelper.getCurveInfo(context, oid);
|
||||
return "ECDH (" + oidName + ")";
|
||||
}
|
||||
|
||||
default: {
|
||||
@ -90,6 +101,106 @@ public class PgpKeyHelper {
|
||||
return algorithmStr;
|
||||
}
|
||||
|
||||
public static String getAlgorithmInfo(Algorithm algorithm, Integer keySize, Curve curve) {
|
||||
return getAlgorithmInfo(null, algorithm, keySize, curve);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
|
||||
*/
|
||||
public static String getAlgorithmInfo(Context context, Algorithm algorithm, Integer keySize, Curve curve) {
|
||||
String algorithmStr;
|
||||
|
||||
switch (algorithm) {
|
||||
case RSA: {
|
||||
algorithmStr = "RSA";
|
||||
break;
|
||||
}
|
||||
case DSA: {
|
||||
algorithmStr = "DSA";
|
||||
break;
|
||||
}
|
||||
|
||||
case ELGAMAL: {
|
||||
algorithmStr = "ElGamal";
|
||||
break;
|
||||
}
|
||||
|
||||
case ECDSA: {
|
||||
algorithmStr = "ECDSA";
|
||||
if (curve != null) {
|
||||
algorithmStr += " (" + getCurveInfo(context, curve) + ")";
|
||||
}
|
||||
return algorithmStr;
|
||||
}
|
||||
case ECDH: {
|
||||
algorithmStr = "ECDH";
|
||||
if (curve != null) {
|
||||
algorithmStr += " (" + getCurveInfo(context, curve) + ")";
|
||||
}
|
||||
return algorithmStr;
|
||||
}
|
||||
|
||||
default: {
|
||||
if (context != null) {
|
||||
algorithmStr = context.getResources().getString(R.string.unknown_algorithm);
|
||||
} else {
|
||||
algorithmStr = "unknown";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (keySize != null && keySize > 0)
|
||||
return algorithmStr + ", " + keySize + " bit";
|
||||
else
|
||||
return algorithmStr;
|
||||
}
|
||||
|
||||
// Return name of a curve. These are names, no need for translation
|
||||
public static String getCurveInfo(Context context, Curve curve) {
|
||||
switch(curve) {
|
||||
case NIST_P256:
|
||||
return "NIST P-256";
|
||||
case NIST_P384:
|
||||
return "NIST P-384";
|
||||
case NIST_P521:
|
||||
return "NIST P-521";
|
||||
|
||||
/* see SaveKeyringParcel
|
||||
case BRAINPOOL_P256:
|
||||
return "Brainpool P-256";
|
||||
case BRAINPOOL_P384:
|
||||
return "Brainpool P-384";
|
||||
case BRAINPOOL_P512:
|
||||
return "Brainpool P-512";
|
||||
*/
|
||||
}
|
||||
if (context != null) {
|
||||
return context.getResources().getString(R.string.unknown_algorithm);
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public static String getCurveInfo(Context context, String oidStr) {
|
||||
ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(oidStr);
|
||||
|
||||
String name;
|
||||
name = NISTNamedCurves.getName(oid);
|
||||
if (name != null) {
|
||||
return name;
|
||||
}
|
||||
name = TeleTrusTNamedCurves.getName(oid);
|
||||
if (name != null) {
|
||||
return name;
|
||||
}
|
||||
if (context != null) {
|
||||
return context.getResources().getString(R.string.unknown_algorithm);
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts fingerprint to hex
|
||||
* <p/>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -70,7 +70,7 @@ public class PgpSignEncrypt {
|
||||
private long mSignatureMasterKeyId;
|
||||
private int mSignatureHashAlgorithm;
|
||||
private String mSignaturePassphrase;
|
||||
private boolean mEncryptToSigner;
|
||||
private long mAdditionalEncryptId;
|
||||
private boolean mCleartextInput;
|
||||
private String mOriginalFilename;
|
||||
|
||||
@ -103,7 +103,7 @@ public class PgpSignEncrypt {
|
||||
this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId;
|
||||
this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
|
||||
this.mSignaturePassphrase = builder.mSignaturePassphrase;
|
||||
this.mEncryptToSigner = builder.mEncryptToSigner;
|
||||
this.mAdditionalEncryptId = builder.mAdditionalEncryptId;
|
||||
this.mCleartextInput = builder.mCleartextInput;
|
||||
this.mNfcSignedHash = builder.mNfcSignedHash;
|
||||
this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp;
|
||||
@ -127,7 +127,7 @@ public class PgpSignEncrypt {
|
||||
private long mSignatureMasterKeyId = Constants.key.none;
|
||||
private int mSignatureHashAlgorithm = 0;
|
||||
private String mSignaturePassphrase = null;
|
||||
private boolean mEncryptToSigner = false;
|
||||
private long mAdditionalEncryptId = Constants.key.none;
|
||||
private boolean mCleartextInput = false;
|
||||
private String mOriginalFilename = "";
|
||||
private byte[] mNfcSignedHash = null;
|
||||
@ -175,7 +175,7 @@ public class PgpSignEncrypt {
|
||||
}
|
||||
|
||||
public Builder setSignatureMasterKeyId(long signatureMasterKeyId) {
|
||||
this.mSignatureMasterKeyId = signatureMasterKeyId;
|
||||
mSignatureMasterKeyId = signatureMasterKeyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -192,11 +192,11 @@ public class PgpSignEncrypt {
|
||||
/**
|
||||
* Also encrypt with the signing keyring
|
||||
*
|
||||
* @param encryptToSigner
|
||||
* @param additionalEncryptId
|
||||
* @return
|
||||
*/
|
||||
public Builder setEncryptToSigner(boolean encryptToSigner) {
|
||||
mEncryptToSigner = encryptToSigner;
|
||||
public Builder setAdditionalEncryptId(long additionalEncryptId) {
|
||||
mAdditionalEncryptId = additionalEncryptId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -288,10 +288,10 @@ public class PgpSignEncrypt {
|
||||
+ "\nenableCompression:" + enableCompression
|
||||
+ "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput);
|
||||
|
||||
// add signature key id to encryption ids (self-encrypt)
|
||||
if (enableEncryption && enableSignature && mEncryptToSigner) {
|
||||
// add additional key id to encryption ids (mostly to do self-encryption)
|
||||
if (enableEncryption && mAdditionalEncryptId != Constants.key.none) {
|
||||
mEncryptionMasterKeyIds = Arrays.copyOf(mEncryptionMasterKeyIds, mEncryptionMasterKeyIds.length + 1);
|
||||
mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mSignatureMasterKeyId;
|
||||
mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mAdditionalEncryptId;
|
||||
}
|
||||
|
||||
ArmoredOutputStream armorOut = null;
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -44,6 +45,8 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
@ -55,7 +58,8 @@ import java.util.TreeSet;
|
||||
* This class and its relatives UncachedPublicKey and UncachedSecretKey are
|
||||
* used to move around pgp key rings in non crypto related (UI, mostly) code.
|
||||
* It should be used for simple inspection only until it saved in the database,
|
||||
* all actual crypto operations should work with WrappedKeyRings exclusively.
|
||||
* all actual crypto operations should work with CanonicalizedKeyRings
|
||||
* exclusively.
|
||||
*
|
||||
* This class is also special in that it can hold either the PGPPublicKeyRing
|
||||
* or PGPSecretKeyRing derivate of the PGPKeyRing class, since these are
|
||||
@ -118,6 +122,10 @@ public class UncachedKeyRing {
|
||||
return mRing.getPublicKey().getFingerprint();
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return mRing.getPublicKey().getVersion();
|
||||
}
|
||||
|
||||
public static UncachedKeyRing decodeFromData(byte[] data)
|
||||
throws PgpGeneralException, IOException {
|
||||
|
||||
@ -211,8 +219,7 @@ public class UncachedKeyRing {
|
||||
aos.close();
|
||||
}
|
||||
|
||||
/** "Canonicalizes" a public key, removing inconsistencies in the process. This variant can be
|
||||
* applied to public keyrings only.
|
||||
/** "Canonicalizes" a public key, removing inconsistencies in the process.
|
||||
*
|
||||
* More specifically:
|
||||
* - Remove all non-verifying self-certificates
|
||||
@ -229,9 +236,9 @@ public class UncachedKeyRing {
|
||||
* - If the key is a secret key, remove all certificates by foreign keys
|
||||
* - If no valid user id remains, log an error and return null
|
||||
*
|
||||
* This operation writes an OperationLog which can be used as part of a OperationResultParcel.
|
||||
* This operation writes an OperationLog which can be used as part of an OperationResultParcel.
|
||||
*
|
||||
* @return A canonicalized key, or null on fatal error
|
||||
* @return A canonicalized key, or null on fatal error (log will include a message in this case)
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@ -241,6 +248,12 @@ public class UncachedKeyRing {
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(getMasterKeyId()));
|
||||
indent += 1;
|
||||
|
||||
// do not accept v3 keys
|
||||
if (getVersion() <= 3) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_KC_V3_KEY, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
final Date now = new Date();
|
||||
|
||||
int redundantCerts = 0, badCerts = 0;
|
||||
@ -259,13 +272,12 @@ public class UncachedKeyRing {
|
||||
for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getKeySignatures())) {
|
||||
int type = zert.getSignatureType();
|
||||
|
||||
// Disregard certifications on user ids, we will deal with those later
|
||||
// These should most definitely not be here...
|
||||
if (type == PGPSignature.NO_CERTIFICATION
|
||||
|| type == PGPSignature.DEFAULT_CERTIFICATION
|
||||
|| type == PGPSignature.CASUAL_CERTIFICATION
|
||||
|| type == PGPSignature.POSITIVE_CERTIFICATION
|
||||
|| type == PGPSignature.CERTIFICATION_REVOCATION) {
|
||||
// These should not be here...
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE_UID, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
badCerts += 1;
|
||||
@ -328,7 +340,17 @@ public class UncachedKeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<String> processedUserIds = new ArrayList<String>();
|
||||
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||
// check for duplicate user ids
|
||||
if (processedUserIds.contains(userId)) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_UID_DUP,
|
||||
indent, userId);
|
||||
// strip out the first found user id with this name
|
||||
modified = PGPPublicKey.removeCertification(modified, userId);
|
||||
}
|
||||
processedUserIds.add(userId);
|
||||
|
||||
PGPSignature selfCert = null;
|
||||
revocation = null;
|
||||
|
||||
@ -405,13 +427,13 @@ public class UncachedKeyRing {
|
||||
if (selfCert == null) {
|
||||
selfCert = zert;
|
||||
} else if (selfCert.getCreationTime().before(cert.getCreationTime())) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP,
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_CERT_DUP,
|
||||
indent, userId);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, selfCert);
|
||||
redundantCerts += 1;
|
||||
selfCert = zert;
|
||||
} else {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP,
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_CERT_DUP,
|
||||
indent, userId);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
redundantCerts += 1;
|
||||
@ -474,6 +496,10 @@ public class UncachedKeyRing {
|
||||
|
||||
// Replace modified key in the keyring
|
||||
ring = replacePublicKey(ring, modified);
|
||||
if (ring == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent);
|
||||
return null;
|
||||
}
|
||||
indent -= 1;
|
||||
|
||||
}
|
||||
@ -580,8 +606,8 @@ public class UncachedKeyRing {
|
||||
|
||||
}
|
||||
|
||||
// if we already have a cert, and this one is not newer: skip it
|
||||
if (selfCert != null && selfCert.getCreationTime().before(cert.getCreationTime())) {
|
||||
// if we already have a cert, and this one is older: skip it
|
||||
if (selfCert != null && cert.getCreationTime().before(selfCert.getCreationTime())) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_DUP, indent);
|
||||
redundantCerts += 1;
|
||||
continue;
|
||||
@ -641,6 +667,10 @@ public class UncachedKeyRing {
|
||||
}
|
||||
// replace pubkey in keyring
|
||||
ring = replacePublicKey(ring, modified);
|
||||
if (ring == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent);
|
||||
return null;
|
||||
}
|
||||
indent -= 1;
|
||||
}
|
||||
|
||||
@ -681,8 +711,9 @@ public class UncachedKeyRing {
|
||||
|
||||
long masterKeyId = other.getMasterKeyId();
|
||||
|
||||
if (getMasterKeyId() != masterKeyId) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MG_HETEROGENEOUS, indent);
|
||||
if (getMasterKeyId() != masterKeyId
|
||||
|| !Arrays.equals(getFingerprint(), other.getFingerprint())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_HETEROGENEOUS, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -729,6 +760,10 @@ public class UncachedKeyRing {
|
||||
} else {
|
||||
// otherwise, just insert the public key
|
||||
result = replacePublicKey(result, key);
|
||||
if (result == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -757,6 +792,10 @@ public class UncachedKeyRing {
|
||||
if (!key.isMasterKey()) {
|
||||
if (modified != resultKey) {
|
||||
result = replacePublicKey(result, modified);
|
||||
if (result == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -781,6 +820,10 @@ public class UncachedKeyRing {
|
||||
// If anything changed, save the updated (sub)key
|
||||
if (modified != resultKey) {
|
||||
result = replacePublicKey(result, modified);
|
||||
if (result == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -795,7 +838,7 @@ public class UncachedKeyRing {
|
||||
return new UncachedKeyRing(result);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MG_FATAL_ENCODE, indent);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_ENCODE, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -826,16 +869,19 @@ public class UncachedKeyRing {
|
||||
*/
|
||||
private static PGPKeyRing replacePublicKey(PGPKeyRing ring, PGPPublicKey key) {
|
||||
if (ring instanceof PGPPublicKeyRing) {
|
||||
return PGPPublicKeyRing.insertPublicKey((PGPPublicKeyRing) ring, key);
|
||||
PGPPublicKeyRing pubRing = (PGPPublicKeyRing) ring;
|
||||
return PGPPublicKeyRing.insertPublicKey(pubRing, key);
|
||||
} else {
|
||||
PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring;
|
||||
PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID());
|
||||
// TODO generate secret key with S2K dummy, if none exists!
|
||||
if (sKey == null) {
|
||||
Log.e(Constants.TAG, "dummy secret key generation not yet implemented");
|
||||
return null;
|
||||
}
|
||||
sKey = PGPSecretKey.replacePublicKey(sKey, key);
|
||||
return PGPSecretKeyRing.insertSecretKey(secRing, sKey);
|
||||
}
|
||||
PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring;
|
||||
PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID());
|
||||
// TODO generate secret key with S2K dummy, if none exists! for now, just die.
|
||||
if (sKey == null) {
|
||||
throw new RuntimeException("dummy secret key generation not yet implemented");
|
||||
}
|
||||
sKey = PGPSecretKey.replacePublicKey(sKey, key);
|
||||
return PGPSecretKeyRing.insertSecretKey(secRing, sKey);
|
||||
}
|
||||
|
||||
/** This method removes a subkey in a keyring.
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -17,6 +18,10 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.spongycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.spongycastle.bcpg.ECPublicBCPGKey;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
@ -93,10 +98,23 @@ public class UncachedPublicKey {
|
||||
return mPublicKey.getAlgorithm();
|
||||
}
|
||||
|
||||
public int getBitStrength() {
|
||||
public Integer getBitStrength() {
|
||||
if (isEC()) {
|
||||
return null;
|
||||
}
|
||||
return mPublicKey.getBitStrength();
|
||||
}
|
||||
|
||||
public String getCurveOid() {
|
||||
if ( ! isEC()) {
|
||||
return null;
|
||||
}
|
||||
if ( ! (mPublicKey.getPublicKeyPacket().getKey() instanceof ECPublicBCPGKey)) {
|
||||
return null;
|
||||
}
|
||||
return ((ECPublicBCPGKey) mPublicKey.getPublicKeyPacket().getKey()).getCurveOID().getId();
|
||||
}
|
||||
|
||||
/** Returns the primary user id, as indicated by the public key's self certificates.
|
||||
*
|
||||
* This is an expensive operation, since potentially a lot of certificates (and revocations)
|
||||
@ -185,6 +203,10 @@ public class UncachedPublicKey {
|
||||
return getAlgorithm() == PGPPublicKey.DSA;
|
||||
}
|
||||
|
||||
public boolean isEC() {
|
||||
return getAlgorithm() == PGPPublicKey.ECDH || getAlgorithm() == PGPPublicKey.ECDSA;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// TODO make this safe
|
||||
public int getKeyUsage() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -95,9 +96,6 @@ public class WrappedSignature {
|
||||
} catch (PGPException e) {
|
||||
// no matter
|
||||
Log.e(Constants.TAG, "exception reading embedded signatures", e);
|
||||
} catch (IOException e) {
|
||||
// no matter
|
||||
Log.e(Constants.TAG, "exception reading embedded signatures", e);
|
||||
}
|
||||
return sigs;
|
||||
}
|
||||
@ -149,27 +147,17 @@ public class WrappedSignature {
|
||||
}
|
||||
}
|
||||
|
||||
public void update(byte[] data, int offset, int length) throws PgpGeneralException {
|
||||
try {
|
||||
mSig.update(data, offset, length);
|
||||
} catch(SignatureException e) {
|
||||
throw new PgpGeneralException(e);
|
||||
}
|
||||
public void update(byte[] data, int offset, int length) {
|
||||
mSig.update(data, offset, length);
|
||||
}
|
||||
|
||||
public void update(byte data) throws PgpGeneralException {
|
||||
try {
|
||||
mSig.update(data);
|
||||
} catch(SignatureException e) {
|
||||
throw new PgpGeneralException(e);
|
||||
}
|
||||
public void update(byte data) {
|
||||
mSig.update(data);
|
||||
}
|
||||
|
||||
public boolean verify() throws PgpGeneralException {
|
||||
try {
|
||||
return mSig.verify();
|
||||
} catch(SignatureException e) {
|
||||
throw new PgpGeneralException(e);
|
||||
} catch(PGPException e) {
|
||||
throw new PgpGeneralException(e);
|
||||
}
|
||||
@ -178,8 +166,6 @@ public class WrappedSignature {
|
||||
boolean verifySignature(PGPPublicKey key) throws PgpGeneralException {
|
||||
try {
|
||||
return mSig.verifyCertification(key);
|
||||
} catch (SignatureException e) {
|
||||
throw new PgpGeneralException("Sign!", e);
|
||||
} catch (PGPException e) {
|
||||
throw new PgpGeneralException("Error!", e);
|
||||
}
|
||||
@ -188,8 +174,6 @@ public class WrappedSignature {
|
||||
boolean verifySignature(PGPPublicKey masterKey, PGPPublicKey subKey) throws PgpGeneralException {
|
||||
try {
|
||||
return mSig.verifyCertification(masterKey, subKey);
|
||||
} catch (SignatureException e) {
|
||||
throw new PgpGeneralException("Sign!", e);
|
||||
} catch (PGPException e) {
|
||||
throw new PgpGeneralException("Error!", e);
|
||||
}
|
||||
@ -198,8 +182,6 @@ public class WrappedSignature {
|
||||
boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException {
|
||||
try {
|
||||
return mSig.verifyCertification(uid, key);
|
||||
} catch (SignatureException e) {
|
||||
throw new PgpGeneralException("Error!", e);
|
||||
} catch (PGPException e) {
|
||||
throw new PgpGeneralException("Error!", e);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -38,6 +39,7 @@ public class KeychainContract {
|
||||
String FINGERPRINT = "fingerprint";
|
||||
|
||||
String KEY_SIZE = "key_size";
|
||||
String KEY_CURVE_OID = "key_curve_oid";
|
||||
String CAN_SIGN = "can_sign";
|
||||
String CAN_ENCRYPT = "can_encrypt";
|
||||
String CAN_CERTIFY = "can_certify";
|
||||
@ -111,6 +113,7 @@ public class KeychainContract {
|
||||
public static final String HAS_ANY_SECRET = "has_any_secret";
|
||||
public static final String HAS_ENCRYPT = "has_encrypt";
|
||||
public static final String HAS_SIGN = "has_sign";
|
||||
public static final String HAS_CERTIFY = "has_certify";
|
||||
public static final String PUBKEY_DATA = "pubkey_data";
|
||||
public static final String PRIVKEY_DATA = "privkey_data";
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -51,7 +52,7 @@ import java.io.IOException;
|
||||
*/
|
||||
public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
private static final String DATABASE_NAME = "openkeychain.db";
|
||||
private static final int DATABASE_VERSION = 2;
|
||||
private static final int DATABASE_VERSION = 3;
|
||||
static Boolean apgHack = false;
|
||||
|
||||
public interface Tables {
|
||||
@ -85,6 +86,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
|
||||
+ KeysColumns.KEY_ID + " INTEGER, "
|
||||
+ KeysColumns.KEY_SIZE + " INTEGER, "
|
||||
+ KeysColumns.KEY_CURVE_OID + " TEXT, "
|
||||
+ KeysColumns.ALGORITHM + " INTEGER, "
|
||||
+ KeysColumns.FINGERPRINT + " BLOB, "
|
||||
|
||||
@ -201,13 +203,20 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion == 1) {
|
||||
// add has_secret for all who are upgrading from a beta version
|
||||
try {
|
||||
db.execSQL("ALTER TABLE keys ADD COLUMN has_secret BOOLEAN");
|
||||
} catch (Exception e) {
|
||||
// never mind, the column probably already existed
|
||||
}
|
||||
// add has_secret for all who are upgrading from a beta version
|
||||
switch (oldVersion) {
|
||||
case 1:
|
||||
try {
|
||||
db.execSQL("ALTER TABLE keys ADD COLUMN has_secret BOOLEAN");
|
||||
} catch(Exception e){
|
||||
// never mind, the column probably already existed
|
||||
}
|
||||
case 2:
|
||||
try {
|
||||
db.execSQL("ALTER TABLE keys ADD COLUMN " + KeysColumns.KEY_CURVE_OID + " TEXT");
|
||||
} catch(Exception e){
|
||||
// never mind, the column probably already existed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +236,8 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
if (db.equals("apg.db")) {
|
||||
hasApgDb = true;
|
||||
} else if (db.equals("apg_old.db")) {
|
||||
Log.d(Constants.TAG, "Found apg_old.db");
|
||||
Log.d(Constants.TAG, "Found apg_old.db, delete it!");
|
||||
context.getDatabasePath("apg_old.db").delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -310,9 +320,8 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
}
|
||||
}
|
||||
|
||||
// Move to a different file (but don't delete, just to be safe)
|
||||
Log.d(Constants.TAG, "All done - moving apg.db to apg_old.db");
|
||||
context.getDatabasePath("apg.db").renameTo(context.getDatabasePath("apg_old.db"));
|
||||
// delete old database
|
||||
context.getDatabasePath("apg.db").delete();
|
||||
}
|
||||
|
||||
private static void copy(File in, File out) throws IOException {
|
||||
@ -349,4 +358,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
copy(in, out);
|
||||
}
|
||||
|
||||
// DANGEROUS, use in test code ONLY!
|
||||
public void clearDatabase() {
|
||||
getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -245,6 +246,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
|
||||
projectionMap.put(KeyRings.KEY_ID, Tables.KEYS + "." + Keys.KEY_ID);
|
||||
projectionMap.put(KeyRings.KEY_SIZE, Tables.KEYS + "." + Keys.KEY_SIZE);
|
||||
projectionMap.put(KeyRings.KEY_CURVE_OID, Tables.KEYS + "." + Keys.KEY_CURVE_OID);
|
||||
projectionMap.put(KeyRings.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED);
|
||||
projectionMap.put(KeyRings.CAN_CERTIFY, Tables.KEYS + "." + Keys.CAN_CERTIFY);
|
||||
projectionMap.put(KeyRings.CAN_ENCRYPT, Tables.KEYS + "." + Keys.CAN_ENCRYPT);
|
||||
@ -271,6 +273,8 @@ public class KeychainProvider extends ContentProvider {
|
||||
"kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT);
|
||||
projectionMap.put(KeyRings.HAS_SIGN,
|
||||
"kS." + Keys.KEY_ID + " AS " + KeyRings.HAS_SIGN);
|
||||
projectionMap.put(KeyRings.HAS_CERTIFY,
|
||||
"kC." + Keys.KEY_ID + " AS " + KeyRings.HAS_CERTIFY);
|
||||
projectionMap.put(KeyRings.IS_EXPIRED,
|
||||
"(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY
|
||||
+ " < " + new Date().getTime() / 1000 + ") AS " + KeyRings.IS_EXPIRED);
|
||||
@ -324,6 +328,15 @@ public class KeychainProvider extends ContentProvider {
|
||||
+ " AND ( kS." + Keys.EXPIRY + " IS NULL OR kS." + Keys.EXPIRY
|
||||
+ " >= " + new Date().getTime() / 1000 + " )"
|
||||
+ ")" : "")
|
||||
+ (plist.contains(KeyRings.HAS_CERTIFY) ?
|
||||
" LEFT JOIN " + Tables.KEYS + " AS kC ON ("
|
||||
+"kC." + Keys.MASTER_KEY_ID
|
||||
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||
+ " AND kC." + Keys.IS_REVOKED + " = 0"
|
||||
+ " AND kC." + Keys.CAN_CERTIFY + " = 1"
|
||||
+ " AND ( kC." + Keys.EXPIRY + " IS NULL OR kC." + Keys.EXPIRY
|
||||
+ " >= " + new Date().getTime() / 1000 + " )"
|
||||
+ ")" : "")
|
||||
);
|
||||
qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0");
|
||||
// in case there are multiple verifying certificates
|
||||
@ -400,6 +413,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK);
|
||||
projectionMap.put(Keys.KEY_ID, Keys.KEY_ID);
|
||||
projectionMap.put(Keys.KEY_SIZE, Keys.KEY_SIZE);
|
||||
projectionMap.put(Keys.KEY_CURVE_OID, Keys.KEY_CURVE_OID);
|
||||
projectionMap.put(Keys.IS_REVOKED, Keys.IS_REVOKED);
|
||||
projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY);
|
||||
projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT);
|
||||
@ -674,6 +688,11 @@ public class KeychainProvider extends ContentProvider {
|
||||
final int match = mUriMatcher.match(uri);
|
||||
|
||||
switch (match) {
|
||||
// dangerous
|
||||
case KEY_RINGS_UNIFIED: {
|
||||
count = db.delete(Tables.KEY_RINGS_PUBLIC, null, null);
|
||||
break;
|
||||
}
|
||||
case KEY_RING_PUBLIC: {
|
||||
@SuppressWarnings("ConstantConditions") // ensured by uriMatcher above
|
||||
String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1);
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -28,12 +29,16 @@ import android.os.RemoteException;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.NullProgressable;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpImportExport;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
@ -51,9 +56,12 @@ import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.util.FileImportCache;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -63,6 +71,7 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@ -86,6 +95,10 @@ public class ProviderHelper {
|
||||
this(context, new OperationLog(), 0);
|
||||
}
|
||||
|
||||
public ProviderHelper(Context context, OperationLog log) {
|
||||
this(context, log, 0);
|
||||
}
|
||||
|
||||
public ProviderHelper(Context context, OperationLog log, int indent) {
|
||||
mContext = context;
|
||||
mContentResolver = context.getContentResolver();
|
||||
@ -93,14 +106,6 @@ public class ProviderHelper {
|
||||
mIndent = indent;
|
||||
}
|
||||
|
||||
public void resetLog() {
|
||||
if(mLog != null) {
|
||||
// Start a new log (leaving the old one intact)
|
||||
mLog = new OperationLog();
|
||||
mIndent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public OperationLog getLog() {
|
||||
return mLog;
|
||||
}
|
||||
@ -322,6 +327,7 @@ public class ProviderHelper {
|
||||
|
||||
values.put(Keys.KEY_ID, key.getKeyId());
|
||||
values.put(Keys.KEY_SIZE, key.getBitStrength());
|
||||
values.put(Keys.KEY_CURVE_OID, key.getCurveOid());
|
||||
values.put(Keys.ALGORITHM, key.getAlgorithm());
|
||||
values.put(Keys.FINGERPRINT, key.getFingerprint());
|
||||
|
||||
@ -644,7 +650,7 @@ public class ProviderHelper {
|
||||
|
||||
if (publicRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
CanonicalizedPublicKeyRing canPublicRing;
|
||||
@ -658,20 +664,20 @@ public class ProviderHelper {
|
||||
|
||||
// If this is null, there is an error in the log so we can just return
|
||||
if (publicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
|
||||
if (canPublicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
// Early breakout if nothing changed
|
||||
if (Arrays.hashCode(publicRing.getEncoded())
|
||||
== Arrays.hashCode(oldPublicRing.getEncoded())) {
|
||||
log(LogLevel.OK, LogType.MSG_IP_SUCCESS_IDENTICAL);
|
||||
return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, null);
|
||||
}
|
||||
} catch (NotFoundException e) {
|
||||
// Not an issue, just means we are dealing with a new keyring.
|
||||
@ -679,7 +685,7 @@ public class ProviderHelper {
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
|
||||
if (canPublicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -692,12 +698,12 @@ public class ProviderHelper {
|
||||
// Merge data from new public ring into secret one
|
||||
secretRing = secretRing.merge(publicRing, mLog, mIndent);
|
||||
if (secretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
// This has always been a secret key ring, this is a safe cast
|
||||
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
|
||||
if (canSecretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
@ -716,11 +722,11 @@ public class ProviderHelper {
|
||||
}
|
||||
}
|
||||
|
||||
return new SaveKeyringResult(result, mLog);
|
||||
return new SaveKeyringResult(result, mLog, canSecretRing);
|
||||
|
||||
} catch (IOException e) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
} finally {
|
||||
mIndent -= 1;
|
||||
}
|
||||
@ -736,7 +742,7 @@ public class ProviderHelper {
|
||||
|
||||
if ( ! secretRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
CanonicalizedSecretKeyRing canSecretRing;
|
||||
@ -750,14 +756,14 @@ public class ProviderHelper {
|
||||
|
||||
// If this is null, there is an error in the log so we can just return
|
||||
if (secretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
// This is a safe cast, because we made sure this is a secret ring above
|
||||
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
|
||||
if (canSecretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
// Early breakout if nothing changed
|
||||
@ -765,7 +771,7 @@ public class ProviderHelper {
|
||||
== Arrays.hashCode(oldSecretRing.getEncoded())) {
|
||||
log(LogLevel.OK, LogType.MSG_IS_SUCCESS_IDENTICAL,
|
||||
PgpKeyHelper.convertKeyIdToHex(masterKeyId) );
|
||||
return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, null);
|
||||
}
|
||||
} catch (NotFoundException e) {
|
||||
// Not an issue, just means we are dealing with a new keyring
|
||||
@ -774,7 +780,7 @@ public class ProviderHelper {
|
||||
// This is a safe cast, because we made sure this is a secret ring above
|
||||
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
|
||||
if (canSecretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -787,7 +793,7 @@ public class ProviderHelper {
|
||||
// Merge data from new secret ring into public one
|
||||
publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);
|
||||
if (publicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
@ -797,30 +803,289 @@ public class ProviderHelper {
|
||||
|
||||
CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
|
||||
if (canPublicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
int result;
|
||||
|
||||
result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true);
|
||||
if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
}
|
||||
|
||||
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
|
||||
result = saveCanonicalizedSecretKeyRing(canSecretRing);
|
||||
|
||||
return new SaveKeyringResult(result, mLog);
|
||||
return new SaveKeyringResult(result, mLog, canSecretRing);
|
||||
|
||||
} catch (IOException e) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
} finally {
|
||||
mIndent -= 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ConsolidateResult consolidateDatabaseStep1(Progressable progress) {
|
||||
|
||||
// 1a. fetch all secret keyrings into a cache file
|
||||
log(LogLevel.START, LogType.MSG_CON);
|
||||
mIndent += 1;
|
||||
|
||||
progress.setProgress(R.string.progress_con_saving, 0, 100);
|
||||
|
||||
try {
|
||||
|
||||
log(LogLevel.DEBUG, LogType.MSG_CON_SAVE_SECRET);
|
||||
mIndent += 1;
|
||||
|
||||
final Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[]{
|
||||
KeyRings.PRIVKEY_DATA, KeyRings.FINGERPRINT, KeyRings.HAS_ANY_SECRET
|
||||
}, KeyRings.HAS_ANY_SECRET + " = 1", null, null);
|
||||
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_CON_ERROR_DB);
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
Preferences.getPreferences(mContext).setCachedConsolidateNumSecrets(cursor.getCount());
|
||||
|
||||
FileImportCache<ParcelableKeyRing> cache =
|
||||
new FileImportCache<ParcelableKeyRing>(mContext, "consolidate_secret.pcl");
|
||||
cache.writeCache(new Iterator<ParcelableKeyRing>() {
|
||||
ParcelableKeyRing ring;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (ring != null) {
|
||||
return true;
|
||||
}
|
||||
if (cursor.isAfterLast()) {
|
||||
return false;
|
||||
}
|
||||
ring = new ParcelableKeyRing(cursor.getBlob(0),
|
||||
PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(1)));
|
||||
cursor.moveToNext();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelableKeyRing next() {
|
||||
try {
|
||||
return ring;
|
||||
} finally {
|
||||
ring = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "error saving secret", e);
|
||||
log(LogLevel.ERROR, LogType.MSG_CON_ERROR_IO_SECRET);
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog);
|
||||
} finally {
|
||||
mIndent -= 1;
|
||||
}
|
||||
|
||||
progress.setProgress(R.string.progress_con_saving, 3, 100);
|
||||
|
||||
// 1b. fetch all public keyrings into a cache file
|
||||
try {
|
||||
|
||||
log(LogLevel.DEBUG, LogType.MSG_CON_SAVE_PUBLIC);
|
||||
mIndent += 1;
|
||||
|
||||
final Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[]{
|
||||
KeyRings.PUBKEY_DATA, KeyRings.FINGERPRINT
|
||||
}, null, null, null);
|
||||
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_CON_ERROR_DB);
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
Preferences.getPreferences(mContext).setCachedConsolidateNumPublics(cursor.getCount());
|
||||
|
||||
FileImportCache<ParcelableKeyRing> cache =
|
||||
new FileImportCache<ParcelableKeyRing>(mContext, "consolidate_public.pcl");
|
||||
cache.writeCache(new Iterator<ParcelableKeyRing>() {
|
||||
ParcelableKeyRing ring;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (ring != null) {
|
||||
return true;
|
||||
}
|
||||
if (cursor.isAfterLast()) {
|
||||
return false;
|
||||
}
|
||||
ring = new ParcelableKeyRing(cursor.getBlob(0),
|
||||
PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(1)));
|
||||
cursor.moveToNext();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelableKeyRing next() {
|
||||
try {
|
||||
return ring;
|
||||
} finally {
|
||||
ring = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "error saving public", e);
|
||||
log(LogLevel.ERROR, LogType.MSG_CON_ERROR_IO_PUBLIC);
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog);
|
||||
} finally {
|
||||
mIndent -= 1;
|
||||
}
|
||||
|
||||
log(LogLevel.INFO, LogType.MSG_CON_CRITICAL_IN);
|
||||
Preferences.getPreferences(mContext).setCachedConsolidate(true);
|
||||
|
||||
return consolidateDatabaseStep2(progress, false);
|
||||
}
|
||||
|
||||
public ConsolidateResult consolidateDatabaseStep2(Progressable progress) {
|
||||
return consolidateDatabaseStep2(progress, true);
|
||||
}
|
||||
|
||||
private static boolean mConsolidateCritical = false;
|
||||
|
||||
private ConsolidateResult consolidateDatabaseStep2(Progressable progress, boolean recovery) {
|
||||
|
||||
synchronized (ProviderHelper.class) {
|
||||
if (mConsolidateCritical) {
|
||||
log(LogLevel.ERROR, LogType.MSG_CON_ERROR_CONCURRENT);
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
mConsolidateCritical = true;
|
||||
}
|
||||
|
||||
try {
|
||||
Preferences prefs = Preferences.getPreferences(mContext);
|
||||
|
||||
// Set flag that we have a cached consolidation here
|
||||
int numSecrets = prefs.getCachedConsolidateNumSecrets();
|
||||
int numPublics = prefs.getCachedConsolidateNumPublics();
|
||||
|
||||
if (recovery) {
|
||||
if (numSecrets >= 0 && numPublics >= 0) {
|
||||
log(LogLevel.START, LogType.MSG_CON_RECOVER, numSecrets, numPublics);
|
||||
} else {
|
||||
log(LogLevel.START, LogType.MSG_CON_RECOVER_UNKNOWN);
|
||||
}
|
||||
mIndent += 1;
|
||||
}
|
||||
|
||||
if (!prefs.getCachedConsolidate()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_CON_ERROR_BAD_STATE);
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
// 2. wipe database (IT'S DANGEROUS)
|
||||
log(LogLevel.DEBUG, LogType.MSG_CON_DB_CLEAR);
|
||||
mContentResolver.delete(KeyRings.buildUnifiedKeyRingsUri(), null, null);
|
||||
|
||||
FileImportCache<ParcelableKeyRing> cacheSecret =
|
||||
new FileImportCache<ParcelableKeyRing>(mContext, "consolidate_secret.pcl");
|
||||
FileImportCache<ParcelableKeyRing> cachePublic =
|
||||
new FileImportCache<ParcelableKeyRing>(mContext, "consolidate_public.pcl");
|
||||
|
||||
// 3. Re-Import secret keyrings from cache
|
||||
if (numSecrets > 0) try {
|
||||
log(LogLevel.DEBUG, LogType.MSG_CON_REIMPORT_SECRET, numSecrets);
|
||||
mIndent += 1;
|
||||
|
||||
new PgpImportExport(mContext, this,
|
||||
new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport))
|
||||
.importKeyRings(cacheSecret.readCache(false), numSecrets);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "error importing secret", e);
|
||||
log(LogLevel.ERROR, LogType.MSG_CON_ERROR_SECRET);
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog);
|
||||
} finally {
|
||||
mIndent -= 1;
|
||||
}
|
||||
else {
|
||||
log(LogLevel.DEBUG, LogType.MSG_CON_REIMPORT_SECRET_SKIP);
|
||||
}
|
||||
|
||||
// 4. Re-Import public keyrings from cache
|
||||
if (numPublics > 0) try {
|
||||
log(LogLevel.DEBUG, LogType.MSG_CON_REIMPORT_PUBLIC, numPublics);
|
||||
mIndent += 1;
|
||||
|
||||
new PgpImportExport(mContext, this,
|
||||
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
|
||||
.importKeyRings(cachePublic.readCache(false), numPublics);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "error importing public", e);
|
||||
log(LogLevel.ERROR, LogType.MSG_CON_ERROR_PUBLIC);
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog);
|
||||
} finally {
|
||||
mIndent -= 1;
|
||||
}
|
||||
else {
|
||||
log(LogLevel.DEBUG, LogType.MSG_CON_REIMPORT_PUBLIC_SKIP);
|
||||
}
|
||||
|
||||
log(LogLevel.INFO, LogType.MSG_CON_CRITICAL_OUT);
|
||||
Preferences.getPreferences(mContext).setCachedConsolidate(false);
|
||||
|
||||
// 5. Delete caches
|
||||
try {
|
||||
log(LogLevel.DEBUG, LogType.MSG_CON_DELETE_SECRET);
|
||||
mIndent += 1;
|
||||
cacheSecret.delete();
|
||||
} catch (IOException e) {
|
||||
// doesn't /really/ matter
|
||||
Log.e(Constants.TAG, "IOException during delete of secret cache", e);
|
||||
log(LogLevel.WARN, LogType.MSG_CON_WARN_DELETE_SECRET);
|
||||
} finally {
|
||||
mIndent -= 1;
|
||||
}
|
||||
|
||||
try {
|
||||
log(LogLevel.DEBUG, LogType.MSG_CON_DELETE_PUBLIC);
|
||||
mIndent += 1;
|
||||
cachePublic.delete();
|
||||
} catch (IOException e) {
|
||||
// doesn't /really/ matter
|
||||
Log.e(Constants.TAG, "IOException during deletion of public cache", e);
|
||||
log(LogLevel.WARN, LogType.MSG_CON_WARN_DELETE_PUBLIC);
|
||||
} finally {
|
||||
mIndent -= 1;
|
||||
}
|
||||
|
||||
progress.setProgress(100, 100);
|
||||
log(LogLevel.OK, LogType.MSG_CON_SUCCESS);
|
||||
mIndent -= 1;
|
||||
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_OK, mLog);
|
||||
|
||||
} finally {
|
||||
mConsolidateCritical = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
|
||||
*/
|
||||
@ -863,9 +1128,7 @@ public class ProviderHelper {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
String version = PgpHelper.getVersionForHeader(mContext);
|
||||
if (version != null) {
|
||||
keyRing.encodeArmored(bos, version);
|
||||
}
|
||||
keyRing.encodeArmored(bos, version);
|
||||
String armoredKey = bos.toString("UTF-8");
|
||||
|
||||
Log.d(Constants.TAG, "armoredKey:" + armoredKey);
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -23,6 +23,7 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.openintents.openpgp.IOpenPgpService;
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
@ -38,6 +39,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
@ -56,11 +58,16 @@ import java.util.Set;
|
||||
|
||||
public class OpenPgpService extends RemoteService {
|
||||
|
||||
static final String[] KEYRING_PROJECTION =
|
||||
new String[]{
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
};
|
||||
static final String[] EMAIL_SEARCH_PROJECTION = new String[]{
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
KeyRings.IS_EXPIRED,
|
||||
KeyRings.IS_REVOKED,
|
||||
};
|
||||
|
||||
// do not pre-select revoked or expired keys
|
||||
static final String EMAIL_SEARCH_WHERE = KeychainContract.KeyRings.IS_REVOKED + " = 0 AND "
|
||||
+ KeychainContract.KeyRings.IS_EXPIRED + " = 0";
|
||||
|
||||
/**
|
||||
* Search database for key ids based on emails.
|
||||
@ -69,52 +76,61 @@ public class OpenPgpService extends RemoteService {
|
||||
* @return
|
||||
*/
|
||||
private Intent getKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {
|
||||
// find key ids to given emails in database
|
||||
ArrayList<Long> keyIds = new ArrayList<Long>();
|
||||
|
||||
boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0);
|
||||
boolean missingUserIdsCheck = false;
|
||||
boolean duplicateUserIdsCheck = false;
|
||||
|
||||
ArrayList<Long> keyIds = new ArrayList<Long>();
|
||||
ArrayList<String> missingUserIds = new ArrayList<String>();
|
||||
ArrayList<String> duplicateUserIds = new ArrayList<String>();
|
||||
if (!noUserIdsCheck) {
|
||||
for (String email : encryptionUserIds) {
|
||||
// try to find the key for this specific email
|
||||
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
|
||||
Cursor cursor = getContentResolver().query(uri, EMAIL_SEARCH_PROJECTION, EMAIL_SEARCH_WHERE, null, null);
|
||||
try {
|
||||
// result should be one entry containing the key id
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
keyIds.add(id);
|
||||
} else {
|
||||
missingUserIdsCheck = true;
|
||||
missingUserIds.add(email);
|
||||
Log.d(Constants.TAG, "user id missing");
|
||||
}
|
||||
// another entry for this email -> too keys with the same email inside user id
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
duplicateUserIdsCheck = true;
|
||||
duplicateUserIds.add(email);
|
||||
|
||||
for (String email : encryptionUserIds) {
|
||||
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
|
||||
Cursor cursor = getContentResolver().query(uri, KEYRING_PROJECTION, null, null, null);
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
keyIds.add(id);
|
||||
} else {
|
||||
missingUserIdsCheck = true;
|
||||
missingUserIds.add(email);
|
||||
Log.d(Constants.TAG, "user id missing");
|
||||
}
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
duplicateUserIdsCheck = true;
|
||||
duplicateUserIds.add(email);
|
||||
Log.d(Constants.TAG, "more than one user id with the same email");
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
// also pre-select
|
||||
long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
keyIds.add(id);
|
||||
Log.d(Constants.TAG, "more than one user id with the same email");
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert to long[]
|
||||
// convert ArrayList<Long> to long[]
|
||||
long[] keyIdsArray = new long[keyIds.size()];
|
||||
for (int i = 0; i < keyIdsArray.length; i++) {
|
||||
keyIdsArray[i] = keyIds.get(i);
|
||||
}
|
||||
|
||||
// allow the user to verify pub key selection
|
||||
if (missingUserIdsCheck || duplicateUserIdsCheck) {
|
||||
// build PendingIntent
|
||||
if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) {
|
||||
// allow the user to verify pub key selection
|
||||
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_NO_USER_IDS_CHECK, noUserIdsCheck);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, duplicateUserIds);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DUPLICATE_USER_IDS, duplicateUserIds);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
@ -126,16 +142,18 @@ public class OpenPgpService extends RemoteService {
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
// everything was easy, we have exactly one key for every email
|
||||
|
||||
if (keyIdsArray.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (keyIdsArray.length == 0) {
|
||||
Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!");
|
||||
}
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIdsArray);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIdsArray);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Intent getNfcIntent(Intent data, byte[] hashToSign, int hashAlgo) {
|
||||
@ -191,7 +209,7 @@ public class OpenPgpService extends RemoteService {
|
||||
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||
// secret key that is set for this account is deleted?
|
||||
// show account config again!
|
||||
return getCreateAccountIntent(data, data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME));
|
||||
return getCreateAccountIntent(data, getAccountName(data));
|
||||
}
|
||||
}
|
||||
if (passphrase == null) {
|
||||
@ -270,10 +288,9 @@ public class OpenPgpService extends RemoteService {
|
||||
originalFilename = "";
|
||||
}
|
||||
|
||||
long[] keyIds;
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||
keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
||||
} else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) {
|
||||
// first try to get key ids from non-ambiguous key id extra
|
||||
long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
||||
if (keyIds == null) {
|
||||
// get key ids based on given user ids
|
||||
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
|
||||
// give params through to activity...
|
||||
@ -285,20 +302,8 @@ public class OpenPgpService extends RemoteService {
|
||||
// if not success -> result contains a PendingIntent for user interaction
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR,
|
||||
"Missing parameter user_ids or key_ids!")
|
||||
);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||
return result;
|
||||
}
|
||||
|
||||
// add own key for encryption
|
||||
keyIds = Arrays.copyOf(keyIds, keyIds.length + 1);
|
||||
keyIds[keyIds.length - 1] = accSettings.getKeyId();
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
@ -315,7 +320,8 @@ public class OpenPgpService extends RemoteService {
|
||||
.setCompressionId(accSettings.getCompression())
|
||||
.setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
|
||||
.setEncryptionMasterKeyIds(keyIds)
|
||||
.setOriginalFilename(originalFilename);
|
||||
.setOriginalFilename(originalFilename)
|
||||
.setAdditionalEncryptId(accSettings.getKeyId()); // add acc key for encryption
|
||||
|
||||
if (sign) {
|
||||
String passphrase;
|
||||
@ -334,9 +340,6 @@ public class OpenPgpService extends RemoteService {
|
||||
builder.setSignatureHashAlgorithm(accSettings.getHashAlgorithm())
|
||||
.setSignatureMasterKeyId(accSettings.getKeyId())
|
||||
.setSignaturePassphrase(passphrase);
|
||||
} else {
|
||||
// encrypt only
|
||||
builder.setSignatureMasterKeyId(Constants.key.none);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -448,7 +451,7 @@ public class OpenPgpService extends RemoteService {
|
||||
// If signature is unknown we return an _additional_ PendingIntent
|
||||
// to retrieve the missing key
|
||||
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId());
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
|
||||
|
||||
@ -514,7 +517,7 @@ public class OpenPgpService extends RemoteService {
|
||||
// If keys are not in db we return an additional PendingIntent
|
||||
// to retrieve the missing key
|
||||
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, masterKeyId);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
|
||||
|
||||
@ -596,6 +599,16 @@ public class OpenPgpService extends RemoteService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getAccountName(Intent data) {
|
||||
String accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME);
|
||||
// if no account name is given use name "default"
|
||||
if (TextUtils.isEmpty(accName)) {
|
||||
accName = "default";
|
||||
}
|
||||
Log.d(Constants.TAG, "accName: " + accName);
|
||||
return accName;
|
||||
}
|
||||
|
||||
// TODO: multi-threading
|
||||
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
|
||||
|
||||
@ -606,12 +619,7 @@ public class OpenPgpService extends RemoteService {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
String accName;
|
||||
if (data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME) != null) {
|
||||
accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME);
|
||||
} else {
|
||||
accName = "default";
|
||||
}
|
||||
String accName = getAccountName(data);
|
||||
final AccountSettings accSettings = getAccSettings(accName);
|
||||
if (accSettings == null) {
|
||||
return getCreateAccountIntent(data, accName);
|
||||
|
@ -27,6 +27,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
@ -160,7 +161,7 @@ public abstract class RemoteService extends Service {
|
||||
*/
|
||||
protected AccountSettings getAccSettings(String accountName) {
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Log.d(Constants.TAG, "accountName: " + accountName);
|
||||
Log.d(Constants.TAG, "getAccSettings accountName: "+ accountName);
|
||||
|
||||
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
|
||||
|
||||
@ -171,7 +172,7 @@ public abstract class RemoteService extends Service {
|
||||
|
||||
protected Intent getCreateAccountIntent(Intent data, String accountName) {
|
||||
String packageName = getCurrentCallingPackage();
|
||||
Log.d(Constants.TAG, "accountName: " + accountName);
|
||||
Log.d(Constants.TAG, "getCreateAccountIntent accountName: " + accountName);
|
||||
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT);
|
||||
|
@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class AccountSettingsActivity extends ActionBarActivity {
|
||||
@ -106,4 +107,15 @@ public class AccountSettingsActivity extends ActionBarActivity {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// if a result has been returned, display a notify
|
||||
if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) {
|
||||
OperationResultParcel result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
|
||||
@ -177,24 +179,19 @@ public class AccountSettingsFragment extends Fragment implements
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_CREATE_KEY: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// select newly created key
|
||||
try {
|
||||
long masterKeyId = new ProviderHelper(getActivity())
|
||||
.getCachedPublicKeyRing(data.getData())
|
||||
.extractOrGetMasterKeyId();
|
||||
mSelectKeyFragment.selectKey(masterKeyId);
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) {
|
||||
OperationResults.SaveKeyringResult result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT);
|
||||
mSelectKeyFragment.selectKey(result.mRingMasterKeyId);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "missing result!");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// execute activity's onActivityResult to show log notify
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,11 +18,20 @@
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.SpannedString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.BulletSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
@ -39,7 +48,6 @@ import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class RemoteServiceActivity extends ActionBarActivity {
|
||||
@ -68,7 +76,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
// select pub keys action
|
||||
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
|
||||
public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids";
|
||||
public static final String EXTRA_DUBLICATE_USER_IDS = "dublicate_user_ids";
|
||||
public static final String EXTRA_DUPLICATE_USER_IDS = "dublicate_user_ids";
|
||||
public static final String EXTRA_NO_USER_IDS_CHECK = "no_user_ids";
|
||||
// error message
|
||||
public static final String EXTRA_ERROR_MESSAGE = "error_message";
|
||||
|
||||
@ -229,32 +238,41 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
} else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
|
||||
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true);
|
||||
ArrayList<String> missingUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_MISSING_USER_IDS);
|
||||
ArrayList<String> dublicateUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS);
|
||||
.getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS);
|
||||
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
||||
final SpannableString textIntro = new SpannableString(
|
||||
noUserIdsCheck ? getString(R.string.api_select_pub_keys_text_no_user_ids)
|
||||
: getString(R.string.api_select_pub_keys_text)
|
||||
);
|
||||
textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append(textIntro);
|
||||
|
||||
// TODO: do this with spannable instead of HTML to prevent parsing failures with weird user ids
|
||||
String text = "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>";
|
||||
text += "<br/><br/>";
|
||||
if (missingUserIds != null && missingUserIds.size() > 0) {
|
||||
text += getString(R.string.api_select_pub_keys_missing_text);
|
||||
text += "<br/>";
|
||||
text += "<ul>";
|
||||
ssb.append("\n\n");
|
||||
ssb.append(getString(R.string.api_select_pub_keys_missing_text));
|
||||
ssb.append("\n");
|
||||
for (String userId : missingUserIds) {
|
||||
text += "<li>" + userId + "</li>";
|
||||
SpannableString ss = new SpannableString(userId + "\n");
|
||||
ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append(ss);
|
||||
}
|
||||
text += "</ul>";
|
||||
text += "<br/>";
|
||||
}
|
||||
if (dublicateUserIds != null && dublicateUserIds.size() > 0) {
|
||||
text += getString(R.string.api_select_pub_keys_dublicates_text);
|
||||
text += "<br/>";
|
||||
text += "<ul>";
|
||||
ssb.append("\n\n");
|
||||
ssb.append(getString(R.string.api_select_pub_keys_dublicates_text));
|
||||
ssb.append("\n");
|
||||
for (String userId : dublicateUserIds) {
|
||||
text += "<li>" + userId + "</li>";
|
||||
SpannableString ss = new SpannableString(userId + "\n");
|
||||
ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append(ss);
|
||||
}
|
||||
text += "</ul>";
|
||||
}
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
@ -284,8 +302,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
setContentView(R.layout.api_remote_select_pub_keys);
|
||||
|
||||
// set text on view
|
||||
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text);
|
||||
textView.setHtmlFromString(text, true);
|
||||
TextView textView = (TextView) findViewById(R.id.api_select_pub_keys_text);
|
||||
textView.setText(ssb, TextView.BufferType.SPANNABLE);
|
||||
|
||||
/* Load select pub keys fragment */
|
||||
// Check that the activity is using the layout version with
|
||||
|
@ -21,6 +21,8 @@ import android.accounts.Account;
|
||||
import android.app.Service;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
@ -29,9 +31,11 @@ import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.provider.ContactsContract;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.KeychainApplication;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.helper.EmailKeyHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@ -42,7 +46,7 @@ public class ContactSyncAdapterService extends Service {
|
||||
|
||||
private class ContactSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
||||
private final AtomicBoolean importDone = new AtomicBoolean(false);
|
||||
// private final AtomicBoolean importDone = new AtomicBoolean(false);
|
||||
|
||||
public ContactSyncAdapter() {
|
||||
super(ContactSyncAdapterService.this, true);
|
||||
@ -51,47 +55,59 @@ public class ContactSyncAdapterService extends Service {
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
|
||||
final SyncResult syncResult) {
|
||||
importDone.set(false);
|
||||
KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this);
|
||||
EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(),
|
||||
new Handler.Callback() {
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
Bundle data = msg.getData();
|
||||
switch (msg.arg1) {
|
||||
case KeychainIntentServiceHandler.MESSAGE_OKAY:
|
||||
Log.d(Constants.TAG, "Syncing... Done.");
|
||||
synchronized (importDone) {
|
||||
importDone.set(true);
|
||||
importDone.notifyAll();
|
||||
}
|
||||
return true;
|
||||
case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS:
|
||||
if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) &&
|
||||
data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) {
|
||||
Log.d(Constants.TAG, "Syncing... Progress: " +
|
||||
data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" +
|
||||
data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX));
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
Log.d(Constants.TAG, "Syncing... " + msg.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})));
|
||||
synchronized (importDone) {
|
||||
try {
|
||||
if (!importDone.get()) importDone.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(Constants.TAG, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Log.d(Constants.TAG, "Performing a sync!");
|
||||
// TODO: Import is currently disabled for 2.8, until we implement proper origin management
|
||||
// importDone.set(false);
|
||||
// KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this);
|
||||
// EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(),
|
||||
// new Handler.Callback() {
|
||||
// @Override
|
||||
// public boolean handleMessage(Message msg) {
|
||||
// Bundle data = msg.getData();
|
||||
// switch (msg.arg1) {
|
||||
// case KeychainIntentServiceHandler.MESSAGE_OKAY:
|
||||
// Log.d(Constants.TAG, "Syncing... Done.");
|
||||
// synchronized (importDone) {
|
||||
// importDone.set(true);
|
||||
// importDone.notifyAll();
|
||||
// }
|
||||
// return true;
|
||||
// case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS:
|
||||
// if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) &&
|
||||
// data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) {
|
||||
// Log.d(Constants.TAG, "Syncing... Progress: " +
|
||||
// data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" +
|
||||
// data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX));
|
||||
// return false;
|
||||
// }
|
||||
// default:
|
||||
// Log.d(Constants.TAG, "Syncing... " + msg.toString());
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// })));
|
||||
// synchronized (importDone) {
|
||||
// try {
|
||||
// if (!importDone.get()) importDone.wait();
|
||||
// } catch (InterruptedException e) {
|
||||
// Log.w(Constants.TAG, e);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
ContactHelper.writeKeysToContacts(ContactSyncAdapterService.this);
|
||||
}
|
||||
}
|
||||
|
||||
public static void requestSync() {
|
||||
Bundle extras = new Bundle();
|
||||
// no need to wait for internet connection!
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
||||
ContentResolver.requestSync(
|
||||
new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE),
|
||||
ContactsContract.AUTHORITY,
|
||||
extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return new ContactSyncAdapter().getSyncAdapterBinder();
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -49,11 +50,14 @@ import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.util.FileImportCache;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@ -102,6 +106,10 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
|
||||
|
||||
public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE";
|
||||
|
||||
public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE";
|
||||
|
||||
/* keys for data bundle */
|
||||
|
||||
// encrypt, decrypt, import export
|
||||
@ -139,8 +147,13 @@ public class KeychainIntentService extends IntentService
|
||||
// delete file securely
|
||||
public static final String DELETE_FILE = "deleteFile";
|
||||
|
||||
// delete keyring(s)
|
||||
public static final String DELETE_KEY_LIST = "delete_list";
|
||||
public static final String DELETE_IS_SECRET = "delete_is_secret";
|
||||
|
||||
// import key
|
||||
public static final String IMPORT_KEY_LIST = "import_key_list";
|
||||
public static final String IMPORT_KEY_FILE = "import_key_file";
|
||||
|
||||
// export key
|
||||
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
|
||||
@ -162,6 +175,10 @@ public class KeychainIntentService extends IntentService
|
||||
public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
|
||||
public static final String CERTIFY_KEY_UIDS = "sign_key_uids";
|
||||
|
||||
// consolidate
|
||||
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
|
||||
|
||||
|
||||
/*
|
||||
* possible data keys as result send over messenger
|
||||
*/
|
||||
@ -176,8 +193,6 @@ public class KeychainIntentService extends IntentService
|
||||
// export
|
||||
public static final String RESULT_EXPORT = "exported";
|
||||
|
||||
public static final String RESULT_IMPORT = "result";
|
||||
|
||||
Messenger mMessenger;
|
||||
|
||||
private boolean mIsCanceled;
|
||||
@ -246,27 +261,31 @@ public class KeychainIntentService extends IntentService
|
||||
String originalFilename = getOriginalFilename(data);
|
||||
|
||||
/* Operation */
|
||||
PgpSignEncrypt.Builder builder =
|
||||
new PgpSignEncrypt.Builder(
|
||||
new ProviderHelper(this),
|
||||
inputData, outStream);
|
||||
builder.setProgressable(this);
|
||||
|
||||
builder.setEnableAsciiArmorOutput(useAsciiArmor)
|
||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(
|
||||
new ProviderHelper(this),
|
||||
inputData, outStream
|
||||
);
|
||||
builder.setProgressable(this)
|
||||
.setEnableAsciiArmorOutput(useAsciiArmor)
|
||||
.setVersionHeader(PgpHelper.getVersionForHeader(this))
|
||||
.setCompressionId(compressionId)
|
||||
.setSymmetricEncryptionAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
|
||||
.setEncryptionMasterKeyIds(encryptionKeyIds)
|
||||
.setSymmetricPassphrase(symmetricPassphrase)
|
||||
.setSignatureMasterKeyId(signatureKeyId)
|
||||
.setEncryptToSigner(true)
|
||||
.setSignatureHashAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.setSignaturePassphrase(
|
||||
PassphraseCacheService.getCachedPassphrase(this, signatureKeyId))
|
||||
.setOriginalFilename(originalFilename);
|
||||
|
||||
try {
|
||||
builder.setSignatureMasterKeyId(signatureKeyId)
|
||||
.setSignaturePassphrase(
|
||||
PassphraseCacheService.getCachedPassphrase(this, signatureKeyId))
|
||||
.setSignatureHashAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.setAdditionalEncryptId(signatureKeyId);
|
||||
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||
// encrypt-only
|
||||
}
|
||||
|
||||
// this assumes that the bytes are cleartext (valid for current implementation!)
|
||||
if (source == IO_BYTES) {
|
||||
builder.setCleartextInput(true);
|
||||
@ -391,23 +410,41 @@ public class KeychainIntentService extends IntentService
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 60, 100));
|
||||
EditKeyResult result;
|
||||
EditKeyResult modifyResult;
|
||||
|
||||
if (saveParcel.mMasterKeyId != null) {
|
||||
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
|
||||
CanonicalizedSecretKeyRing secRing =
|
||||
providerHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||
new ProviderHelper(this).getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||
|
||||
result = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase);
|
||||
modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase);
|
||||
} else {
|
||||
result = keyOperations.createSecretKeyRing(saveParcel);
|
||||
modifyResult = keyOperations.createSecretKeyRing(saveParcel);
|
||||
}
|
||||
|
||||
UncachedKeyRing ring = result.getRing();
|
||||
// If the edit operation didn't succeed, exit here
|
||||
if (!modifyResult.success()) {
|
||||
// always return SaveKeyringResult, so create one out of the EditKeyResult
|
||||
SaveKeyringResult saveResult = new SaveKeyringResult(
|
||||
SaveKeyringResult.RESULT_ERROR,
|
||||
modifyResult.getLog(),
|
||||
null);
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult);
|
||||
return;
|
||||
}
|
||||
|
||||
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100));
|
||||
UncachedKeyRing ring = modifyResult.getRing();
|
||||
|
||||
// Save the keyring. The ProviderHelper is initialized with the previous log
|
||||
SaveKeyringResult saveResult = new ProviderHelper(this, modifyResult.getLog())
|
||||
.saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100));
|
||||
|
||||
// If the edit operation didn't succeed, exit here
|
||||
if (!saveResult.success()) {
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult);
|
||||
return;
|
||||
}
|
||||
|
||||
// cache new passphrase
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
@ -417,8 +454,11 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
setProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
// make sure new data is synced into contacts
|
||||
ContactSyncAdapterService.requestSync();
|
||||
|
||||
/* Output */
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
@ -454,17 +494,21 @@ public class KeychainIntentService extends IntentService
|
||||
} else {
|
||||
// get entries from cached file
|
||||
FileImportCache<ParcelableKeyRing> cache =
|
||||
new FileImportCache<ParcelableKeyRing>(this);
|
||||
new FileImportCache<ParcelableKeyRing>(this, "key_import.pcl");
|
||||
entries = cache.readCacheIntoList();
|
||||
}
|
||||
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, this);
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, providerHelper, this);
|
||||
ImportKeyResult result = pgpImportExport.importKeyRings(entries);
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putParcelable(RESULT_IMPORT, result);
|
||||
if (result.mSecret > 0) {
|
||||
providerHelper.consolidateDatabaseStep1(this);
|
||||
}
|
||||
// make sure new data is synced into contacts
|
||||
ContactSyncAdapterService.requestSync();
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
@ -549,8 +593,9 @@ public class KeychainIntentService extends IntentService
|
||||
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
||||
|
||||
boolean uploaded = pgpImportExport.uploadKeyRingToServer(server, keyring);
|
||||
if (!uploaded) {
|
||||
try {
|
||||
pgpImportExport.uploadKeyRingToServer(server, keyring);
|
||||
} catch (Keyserver.AddKeyException e) {
|
||||
throw new PgpGeneralException("Unable to export key to selected server");
|
||||
}
|
||||
|
||||
@ -639,7 +684,56 @@ public class KeychainIntentService extends IntentService
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
} else if (ACTION_DELETE.equals(action)) {
|
||||
|
||||
try {
|
||||
|
||||
long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
|
||||
boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
|
||||
|
||||
if (masterKeyIds.length == 0) {
|
||||
throw new PgpGeneralException("List of keys to delete is empty");
|
||||
}
|
||||
|
||||
if (isSecret && masterKeyIds.length > 1) {
|
||||
throw new PgpGeneralException("Secret keys can only be deleted individually!");
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
for (long masterKeyId : masterKeyIds) {
|
||||
int count = getContentResolver().delete(
|
||||
KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null
|
||||
);
|
||||
success |= count > 0;
|
||||
}
|
||||
|
||||
if (isSecret && success) {
|
||||
ConsolidateResult result =
|
||||
new ProviderHelper(this).consolidateDatabaseStep1(this);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
// make sure new data is synced into contacts
|
||||
ContactSyncAdapterService.requestSync();
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
} else if (ACTION_CONSOLIDATE.equals(action)) {
|
||||
ConsolidateResult result;
|
||||
if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
|
||||
result = new ProviderHelper(this).consolidateDatabaseStep2(this);
|
||||
} else {
|
||||
result = new ProviderHelper(this).consolidateDatabaseStep1(this);
|
||||
}
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendErrorToHandler(Exception e) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -155,10 +156,8 @@ public class OperationResultParcel implements Parcelable {
|
||||
if ((resultType & OperationResultParcel.RESULT_ERROR) == 0) {
|
||||
|
||||
if (getLog().containsWarnings()) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
} else {
|
||||
duration = SuperToast.Duration.LONG;
|
||||
color = Style.GREEN;
|
||||
}
|
||||
|
||||
@ -167,7 +166,6 @@ public class OperationResultParcel implements Parcelable {
|
||||
|
||||
} else {
|
||||
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
|
||||
str = "operation failed";
|
||||
@ -180,8 +178,8 @@ public class OperationResultParcel implements Parcelable {
|
||||
button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD,
|
||||
Style.getStyle(color, SuperToast.Animations.POPUP));
|
||||
toast.setText(str);
|
||||
toast.setDuration(duration);
|
||||
toast.setIndeterminate(duration == 0);
|
||||
toast.setDuration(SuperToast.Duration.EXTRA_LONG);
|
||||
toast.setIndeterminate(false);
|
||||
toast.setSwipeToDismiss(true);
|
||||
// If we have a log and it's non-empty, show a View Log button
|
||||
if (button) {
|
||||
@ -289,6 +287,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_IS_SUCCESS (R.string.msg_is_success),
|
||||
|
||||
// keyring canonicalization
|
||||
MSG_KC_V3_KEY (R.string.msg_kc_v3_key),
|
||||
MSG_KC_PUBLIC (R.string.msg_kc_public),
|
||||
MSG_KC_SECRET (R.string.msg_kc_secret),
|
||||
MSG_KC_FATAL_NO_UID (R.string.msg_kc_fatal_no_uid),
|
||||
@ -324,6 +323,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_KC_UID_BAD_TIME (R.string.msg_kc_uid_bad_time),
|
||||
MSG_KC_UID_BAD_TYPE (R.string.msg_kc_uid_bad_type),
|
||||
MSG_KC_UID_BAD (R.string.msg_kc_uid_bad),
|
||||
MSG_KC_UID_CERT_DUP (R.string.msg_kc_uid_cert_dup),
|
||||
MSG_KC_UID_DUP (R.string.msg_kc_uid_dup),
|
||||
MSG_KC_UID_FOREIGN (R.string.msg_kc_uid_foreign),
|
||||
MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert),
|
||||
@ -333,10 +333,11 @@ public class OperationResultParcel implements Parcelable {
|
||||
|
||||
|
||||
// keyring consolidation
|
||||
MSG_MG_ERROR_SECRET_DUMMY(R.string.msg_mg_error_secret_dummy),
|
||||
MSG_MG_ERROR_ENCODE(R.string.msg_mg_error_encode),
|
||||
MSG_MG_ERROR_HETEROGENEOUS(R.string.msg_mg_error_heterogeneous),
|
||||
MSG_MG_PUBLIC (R.string.msg_mg_public),
|
||||
MSG_MG_SECRET (R.string.msg_mg_secret),
|
||||
MSG_MG_FATAL_ENCODE (R.string.msg_mg_fatal_encode),
|
||||
MSG_MG_HETEROGENEOUS (R.string.msg_mg_heterogeneous),
|
||||
MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey),
|
||||
MSG_MG_FOUND_NEW (R.string.msg_mg_found_new),
|
||||
MSG_MG_UNCHANGED (R.string.msg_mg_unchanged),
|
||||
@ -346,10 +347,16 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_CR_ERROR_NO_MASTER (R.string.msg_cr_error_no_master),
|
||||
MSG_CR_ERROR_NO_USER_ID (R.string.msg_cr_error_no_user_id),
|
||||
MSG_CR_ERROR_NO_CERTIFY (R.string.msg_cr_error_no_certify),
|
||||
MSG_CR_ERROR_NULL_EXPIRY(R.string.msg_cr_error_null_expiry),
|
||||
MSG_CR_ERROR_KEYSIZE_512 (R.string.msg_cr_error_keysize_512),
|
||||
MSG_CR_ERROR_NO_KEYSIZE (R.string.msg_cr_error_no_keysize),
|
||||
MSG_CR_ERROR_NO_CURVE (R.string.msg_cr_error_no_curve),
|
||||
MSG_CR_ERROR_UNKNOWN_ALGO (R.string.msg_cr_error_unknown_algo),
|
||||
MSG_CR_ERROR_INTERNAL_PGP (R.string.msg_cr_error_internal_pgp),
|
||||
MSG_CR_ERROR_MASTER_ELGAMAL (R.string.msg_cr_error_master_elgamal),
|
||||
MSG_CR_ERROR_FLAGS_DSA (R.string.msg_cr_error_flags_dsa),
|
||||
MSG_CR_ERROR_FLAGS_ELGAMAL (R.string.msg_cr_error_flags_elgamal),
|
||||
MSG_CR_ERROR_FLAGS_ECDSA (R.string.msg_cr_error_flags_ecdsa),
|
||||
MSG_CR_ERROR_FLAGS_ECDH (R.string.msg_cr_error_flags_ecdh),
|
||||
|
||||
// secret key modify
|
||||
MSG_MF (R.string.msg_mr),
|
||||
@ -357,18 +364,27 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MF_ERROR_FINGERPRINT (R.string.msg_mf_error_fingerprint),
|
||||
MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid),
|
||||
MSG_MF_ERROR_INTEGRITY (R.string.msg_mf_error_integrity),
|
||||
MSG_MF_ERROR_MASTER_NONE(R.string.msg_mf_error_master_none),
|
||||
MSG_MF_ERROR_NO_CERTIFY (R.string.msg_cr_error_no_certify),
|
||||
MSG_MF_ERROR_NOEXIST_PRIMARY (R.string.msg_mf_error_noexist_primary),
|
||||
MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary),
|
||||
MSG_MF_ERROR_NOEXIST_REVOKE (R.string.msg_mf_error_noexist_revoke),
|
||||
MSG_MF_ERROR_NULL_EXPIRY (R.string.msg_mf_error_null_expiry),
|
||||
MSG_MF_ERROR_PASSPHRASE_MASTER(R.string.msg_mf_error_passphrase_master),
|
||||
MSG_MF_ERROR_PAST_EXPIRY(R.string.msg_mf_error_past_expiry),
|
||||
MSG_MF_ERROR_PGP (R.string.msg_mf_error_pgp),
|
||||
MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary),
|
||||
MSG_MF_ERROR_SIG (R.string.msg_mf_error_sig),
|
||||
MSG_MF_ERROR_SUBKEY_MISSING(R.string.msg_mf_error_subkey_missing),
|
||||
MSG_MF_MASTER (R.string.msg_mf_master),
|
||||
MSG_MF_PASSPHRASE (R.string.msg_mf_passphrase),
|
||||
MSG_MF_PASSPHRASE_KEY (R.string.msg_mf_passphrase_key),
|
||||
MSG_MF_PASSPHRASE_EMPTY_RETRY (R.string.msg_mf_passphrase_empty_retry),
|
||||
MSG_MF_PASSPHRASE_FAIL (R.string.msg_mf_passphrase_fail),
|
||||
MSG_MF_PRIMARY_REPLACE_OLD (R.string.msg_mf_primary_replace_old),
|
||||
MSG_MF_PRIMARY_NEW (R.string.msg_mf_primary_new),
|
||||
MSG_MF_SUBKEY_CHANGE (R.string.msg_mf_subkey_change),
|
||||
MSG_MF_SUBKEY_MISSING (R.string.msg_mf_subkey_missing),
|
||||
MSG_MF_SUBKEY_NEW_ID (R.string.msg_mf_subkey_new_id),
|
||||
MSG_MF_SUBKEY_NEW (R.string.msg_mf_subkey_new),
|
||||
MSG_MF_SUBKEY_PAST_EXPIRY (R.string.msg_mf_subkey_past_expiry),
|
||||
MSG_MF_SUBKEY_REVOKE (R.string.msg_mf_subkey_revoke),
|
||||
MSG_MF_SUCCESS (R.string.msg_mf_success),
|
||||
MSG_MF_UID_ADD (R.string.msg_mf_uid_add),
|
||||
@ -377,6 +393,32 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MF_UID_ERROR_EMPTY (R.string.msg_mf_uid_error_empty),
|
||||
MSG_MF_UNLOCK_ERROR (R.string.msg_mf_unlock_error),
|
||||
MSG_MF_UNLOCK (R.string.msg_mf_unlock),
|
||||
|
||||
// consolidate
|
||||
MSG_CON_CRITICAL_IN (R.string.msg_con_critical_in),
|
||||
MSG_CON_CRITICAL_OUT (R.string.msg_con_critical_out),
|
||||
MSG_CON_DB_CLEAR (R.string.msg_con_db_clear),
|
||||
MSG_CON_DELETE_PUBLIC (R.string.msg_con_delete_public),
|
||||
MSG_CON_DELETE_SECRET (R.string.msg_con_delete_secret),
|
||||
MSG_CON_ERROR_BAD_STATE (R.string.msg_con_error_bad_state),
|
||||
MSG_CON_ERROR_CONCURRENT(R.string.msg_con_error_concurrent),
|
||||
MSG_CON_ERROR_DB (R.string.msg_con_error_db),
|
||||
MSG_CON_ERROR_IO_PUBLIC (R.string.msg_con_error_io_public),
|
||||
MSG_CON_ERROR_IO_SECRET (R.string.msg_con_error_io_secret),
|
||||
MSG_CON_ERROR_PUBLIC (R.string.msg_con_error_public),
|
||||
MSG_CON_ERROR_SECRET (R.string.msg_con_error_secret),
|
||||
MSG_CON_RECOVER (R.plurals.msg_con_recover),
|
||||
MSG_CON_RECOVER_UNKNOWN (R.string.msg_con_recover_unknown),
|
||||
MSG_CON_REIMPORT_PUBLIC (R.plurals.msg_con_reimport_public),
|
||||
MSG_CON_REIMPORT_PUBLIC_SKIP (R.string.msg_con_reimport_public_skip),
|
||||
MSG_CON_REIMPORT_SECRET (R.plurals.msg_con_reimport_secret),
|
||||
MSG_CON_REIMPORT_SECRET_SKIP (R.string.msg_con_reimport_secret_skip),
|
||||
MSG_CON (R.string.msg_con),
|
||||
MSG_CON_SAVE_PUBLIC (R.string.msg_con_save_public),
|
||||
MSG_CON_SAVE_SECRET (R.string.msg_con_save_secret),
|
||||
MSG_CON_SUCCESS (R.string.msg_con_success),
|
||||
MSG_CON_WARN_DELETE_PUBLIC (R.string.msg_con_warn_delete_public),
|
||||
MSG_CON_WARN_DELETE_SECRET (R.string.msg_con_warn_delete_secret),
|
||||
;
|
||||
|
||||
private final int mMsgId;
|
||||
@ -406,7 +448,9 @@ public class OperationResultParcel implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mResult);
|
||||
dest.writeTypedList(mLog.toList());
|
||||
if (mLog != null) {
|
||||
dest.writeTypedList(mLog.toList());
|
||||
}
|
||||
}
|
||||
|
||||
public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() {
|
||||
@ -432,6 +476,15 @@ public class OperationResultParcel implements Parcelable {
|
||||
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null));
|
||||
}
|
||||
|
||||
public boolean containsType(LogType type) {
|
||||
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) {
|
||||
if (entry.mType == type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsWarnings() {
|
||||
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) {
|
||||
if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -28,7 +29,10 @@ import com.github.johnpersano.supertoasts.SuperToast;
|
||||
import com.github.johnpersano.supertoasts.util.OnClickWrapper;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
@ -37,7 +41,7 @@ public abstract class OperationResults {
|
||||
|
||||
public static class ImportKeyResult extends OperationResultParcel {
|
||||
|
||||
public final int mNewKeys, mUpdatedKeys, mBadKeys;
|
||||
public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret;
|
||||
|
||||
// At least one new key
|
||||
public static final int RESULT_OK_NEWKEYS = 2;
|
||||
@ -49,18 +53,21 @@ public abstract class OperationResults {
|
||||
public static final int RESULT_WITH_WARNINGS = 16;
|
||||
|
||||
// No keys to import...
|
||||
public static final int RESULT_FAIL_NOTHING = 32 +1;
|
||||
public static final int RESULT_FAIL_NOTHING = 32 + 1;
|
||||
|
||||
public boolean isOkBoth() {
|
||||
return (mResult & (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED))
|
||||
== (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED);
|
||||
}
|
||||
|
||||
public boolean isOkNew() {
|
||||
return (mResult & RESULT_OK_NEWKEYS) == RESULT_OK_NEWKEYS;
|
||||
}
|
||||
|
||||
public boolean isOkUpdated() {
|
||||
return (mResult & RESULT_OK_UPDATED) == RESULT_OK_UPDATED;
|
||||
}
|
||||
|
||||
public boolean isFailNothing() {
|
||||
return (mResult & RESULT_FAIL_NOTHING) == RESULT_FAIL_NOTHING;
|
||||
}
|
||||
@ -70,14 +77,16 @@ public abstract class OperationResults {
|
||||
mNewKeys = source.readInt();
|
||||
mUpdatedKeys = source.readInt();
|
||||
mBadKeys = source.readInt();
|
||||
mSecret = source.readInt();
|
||||
}
|
||||
|
||||
public ImportKeyResult(int result, OperationLog log,
|
||||
int newKeys, int updatedKeys, int badKeys) {
|
||||
int newKeys, int updatedKeys, int badKeys, int secret) {
|
||||
super(result, log);
|
||||
mNewKeys = newKeys;
|
||||
mUpdatedKeys = updatedKeys;
|
||||
mBadKeys = badKeys;
|
||||
mSecret = secret;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -86,6 +95,7 @@ public abstract class OperationResults {
|
||||
dest.writeInt(mNewKeys);
|
||||
dest.writeInt(mUpdatedKeys);
|
||||
dest.writeInt(mBadKeys);
|
||||
dest.writeInt(mSecret);
|
||||
}
|
||||
|
||||
public static Creator<ImportKeyResult> CREATOR = new Creator<ImportKeyResult>() {
|
||||
@ -124,7 +134,7 @@ public abstract class OperationResults {
|
||||
if (this.isOkBoth()) {
|
||||
str = activity.getResources().getQuantityString(
|
||||
R.plurals.import_keys_added_and_updated_1, mNewKeys, mNewKeys);
|
||||
str += " "+ activity.getResources().getQuantityString(
|
||||
str += " " + activity.getResources().getQuantityString(
|
||||
R.plurals.import_keys_added_and_updated_2, mUpdatedKeys, mUpdatedKeys, withWarnings);
|
||||
} else if (isOkUpdated()) {
|
||||
str = activity.getResources().getQuantityString(
|
||||
@ -185,13 +195,13 @@ public abstract class OperationResults {
|
||||
public static class EditKeyResult extends OperationResultParcel {
|
||||
|
||||
private transient UncachedKeyRing mRing;
|
||||
public final Long mRingMasterKeyId;
|
||||
public final long mRingMasterKeyId;
|
||||
|
||||
public EditKeyResult(int result, OperationLog log,
|
||||
UncachedKeyRing ring) {
|
||||
UncachedKeyRing ring) {
|
||||
super(result, log);
|
||||
mRing = ring;
|
||||
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : null;
|
||||
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none;
|
||||
}
|
||||
|
||||
public UncachedKeyRing getRing() {
|
||||
@ -224,8 +234,12 @@ public abstract class OperationResults {
|
||||
|
||||
public static class SaveKeyringResult extends OperationResultParcel {
|
||||
|
||||
public SaveKeyringResult(int result, OperationLog log) {
|
||||
public final long mRingMasterKeyId;
|
||||
|
||||
public SaveKeyringResult(int result, OperationLog log,
|
||||
CanonicalizedKeyRing ring) {
|
||||
super(result, log);
|
||||
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none;
|
||||
}
|
||||
|
||||
// Some old key was updated
|
||||
@ -240,6 +254,34 @@ public abstract class OperationResults {
|
||||
return (mResult & UPDATED) == UPDATED;
|
||||
}
|
||||
|
||||
public SaveKeyringResult(Parcel source) {
|
||||
super(source);
|
||||
mRingMasterKeyId = source.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mRingMasterKeyId);
|
||||
}
|
||||
|
||||
public static Creator<SaveKeyringResult> CREATOR = new Creator<SaveKeyringResult>() {
|
||||
public SaveKeyringResult createFromParcel(final Parcel source) {
|
||||
return new SaveKeyringResult(source);
|
||||
}
|
||||
|
||||
public SaveKeyringResult[] newArray(final int size) {
|
||||
return new SaveKeyringResult[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class ConsolidateResult extends OperationResultParcel {
|
||||
|
||||
public ConsolidateResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ public class PassphraseCacheService extends Service {
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
|
||||
private static final int MSG_PASSPHRASE_CACHE_GET_OKAY = 1;
|
||||
private static final int MSG_PASSPHRASE_CACHE_GET_KEY_NO_FOUND = 2;
|
||||
private static final int MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND = 2;
|
||||
|
||||
private BroadcastReceiver mIntentReceiver;
|
||||
|
||||
@ -170,7 +170,7 @@ public class PassphraseCacheService extends Service {
|
||||
switch (returnMessage.what) {
|
||||
case MSG_PASSPHRASE_CACHE_GET_OKAY:
|
||||
return returnMessage.getData().getString(EXTRA_PASSPHRASE);
|
||||
case MSG_PASSPHRASE_CACHE_GET_KEY_NO_FOUND:
|
||||
case MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND:
|
||||
throw new KeyNotFoundException();
|
||||
default:
|
||||
throw new KeyNotFoundException("should not happen!");
|
||||
@ -322,7 +322,7 @@ public class PassphraseCacheService extends Service {
|
||||
msg.setData(bundle);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Passphrase for unknown key was requested!");
|
||||
msg.what = MSG_PASSPHRASE_CACHE_GET_KEY_NO_FOUND;
|
||||
msg.what = MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -79,14 +80,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
// performance gain for using Parcelable here would probably be negligible,
|
||||
// use Serializable instead.
|
||||
public static class SubkeyAdd implements Serializable {
|
||||
public int mAlgorithm;
|
||||
public int mKeysize;
|
||||
public Algorithm mAlgorithm;
|
||||
public Integer mKeySize;
|
||||
public Curve mCurve;
|
||||
public int mFlags;
|
||||
public Long mExpiry;
|
||||
|
||||
public SubkeyAdd(int algorithm, int keysize, int flags, Long expiry) {
|
||||
public SubkeyAdd(Algorithm algorithm, Integer keySize, Curve curve, int flags, Long expiry) {
|
||||
mAlgorithm = algorithm;
|
||||
mKeysize = keysize;
|
||||
mKeySize = keySize;
|
||||
mCurve = curve;
|
||||
mFlags = flags;
|
||||
mExpiry = expiry;
|
||||
}
|
||||
@ -94,7 +97,8 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
@Override
|
||||
public String toString() {
|
||||
String out = "mAlgorithm: " + mAlgorithm + ", ";
|
||||
out += "mKeysize: " + mKeysize + ", ";
|
||||
out += "mKeySize: " + mKeySize + ", ";
|
||||
out += "mCurve: " + mCurve + ", ";
|
||||
out += "mFlags: " + mFlags;
|
||||
out += "mExpiry: " + mExpiry;
|
||||
|
||||
@ -213,4 +217,20 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// All supported algorithms
|
||||
public enum Algorithm {
|
||||
RSA, DSA, ELGAMAL, ECDSA, ECDH
|
||||
}
|
||||
|
||||
// All curves defined in the standard
|
||||
// http://www.bouncycastle.org/wiki/pages/viewpage.action?pageId=362269
|
||||
public enum Curve {
|
||||
NIST_P256, NIST_P384, NIST_P521,
|
||||
|
||||
// these are supported by gpg, but they are not in rfc6637 and not supported by BouncyCastle yet
|
||||
// (adding support would be trivial though -> JcaPGPKeyConverter.java:190)
|
||||
// BRAINPOOL_P256, BRAINPOOL_P384, BRAINPOOL_P512
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -40,6 +41,7 @@ import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
@ -53,9 +55,12 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
@ -64,18 +69,18 @@ import java.util.ArrayList;
|
||||
/**
|
||||
* Signs the specified public key with the specified secret master key
|
||||
*/
|
||||
public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {
|
||||
public class CertifyKeyActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private View mCertifyButton;
|
||||
private ImageView mActionCertifyImage;
|
||||
private CheckBox mUploadKeyCheckbox;
|
||||
private Spinner mSelectKeyserverSpinner;
|
||||
private ScrollView mScrollView;
|
||||
|
||||
private SelectSecretKeyLayoutFragment mSelectKeyFragment;
|
||||
private CertifyKeySpinner mCertifyKeySpinner;
|
||||
|
||||
private Uri mDataUri;
|
||||
private long mPubKeyId = 0;
|
||||
private long mMasterKeyId = 0;
|
||||
private long mPubKeyId = Constants.key.none;
|
||||
private long mMasterKeyId = Constants.key.none;
|
||||
|
||||
private ListView mUserIds;
|
||||
private UserIdsAdapter mUserIdsAdapter;
|
||||
@ -89,20 +94,24 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
setContentView(R.layout.certify_key_activity);
|
||||
|
||||
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.sign_key_select_key_fragment);
|
||||
mCertifyKeySpinner = (CertifyKeySpinner) findViewById(R.id.certify_key_spinner);
|
||||
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
|
||||
mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox);
|
||||
mCertifyButton = findViewById(R.id.certify_key_certify_button);
|
||||
mActionCertifyImage = (ImageView) findViewById(R.id.certify_key_action_certify_image);
|
||||
mUserIds = (ListView) findViewById(R.id.view_key_user_ids);
|
||||
mScrollView = (ScrollView) findViewById(R.id.certify_scroll_view);
|
||||
|
||||
// make certify image gray, like action icons
|
||||
mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
|
||||
mSelectKeyFragment.setCallback(this);
|
||||
mSelectKeyFragment.setFilterCertify(true);
|
||||
mCertifyKeySpinner.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
|
||||
@Override
|
||||
public void onKeyChanged(long masterKeyId) {
|
||||
mMasterKeyId = masterKeyId;
|
||||
}
|
||||
});
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
||||
@ -135,9 +144,9 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
public void onClick(View v) {
|
||||
if (mPubKeyId != 0) {
|
||||
if (mMasterKeyId == 0) {
|
||||
mSelectKeyFragment.setError(getString(R.string.select_key_to_certify));
|
||||
Notify.showNotify(CertifyKeyActivity.this, getString(R.string.select_key_to_certify),
|
||||
Notify.Style.ERROR);
|
||||
scrollUp();
|
||||
} else {
|
||||
initiateCertifying();
|
||||
}
|
||||
@ -162,6 +171,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||
}
|
||||
|
||||
private void scrollUp() {
|
||||
mScrollView.post(new Runnable() {
|
||||
public void run() {
|
||||
mScrollView.fullScroll(ScrollView.FOCUS_UP);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static final String USER_IDS_SELECTION = UserIds.IS_REVOKED + " = 0";
|
||||
|
||||
static final String[] KEYRING_PROJECTION =
|
||||
@ -199,6 +216,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
if (data.moveToFirst()) {
|
||||
// TODO: put findViewById in onCreate!
|
||||
mPubKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
mCertifyKeySpinner.setHiddenMasterKeyId(mPubKeyId);
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(mPubKeyId);
|
||||
((TextView) findViewById(R.id.key_id)).setText(keyIdStr);
|
||||
|
||||
@ -213,6 +231,9 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
break;
|
||||
case LOADER_ID_USER_IDS:
|
||||
mUserIdsAdapter.swapCursor(data);
|
||||
// when some user ids are pre-checked, the focus is requested and the scroll view goes
|
||||
// down. This fixes it.
|
||||
scrollUp();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -292,8 +313,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
Notify.showNotify(CertifyKeyActivity.this, R.string.key_certify_success,
|
||||
Notify.Style.INFO);
|
||||
// Notify.showNotify(CertifyKeyActivity.this, R.string.key_certify_success,
|
||||
// Notify.Style.INFO);
|
||||
|
||||
OperationResultParcel result = new OperationResultParcel(OperationResultParcel.RESULT_OK, null);
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OperationResultParcel.EXTRA_RESULT, result);
|
||||
CertifyKeyActivity.this.setResult(RESULT_OK, intent);
|
||||
CertifyKeyActivity.this.finish();
|
||||
|
||||
// check if we need to send the key to the server or not
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
@ -345,13 +372,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OperationResultParcel.EXTRA_RESULT, message.getData());
|
||||
Notify.showNotify(CertifyKeyActivity.this, R.string.key_send_success,
|
||||
Notify.Style.INFO);
|
||||
//Notify.showNotify(CertifyKeyActivity.this, R.string.key_send_success,
|
||||
//Notify.Style.INFO);
|
||||
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
OperationResultParcel result = new OperationResultParcel(OperationResultParcel.RESULT_OK, null);
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OperationResultParcel.EXTRA_RESULT, result);
|
||||
CertifyKeyActivity.this.setResult(RESULT_OK, intent);
|
||||
CertifyKeyActivity.this.finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -367,14 +395,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* callback from select key fragment
|
||||
*/
|
||||
@Override
|
||||
public void onKeySelected(long secretKeyId) {
|
||||
mMasterKeyId = secretKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
|
||||
/**
|
||||
* We can not directly create a dialog on the application context.
|
||||
* This activity encapsulates a DialogFragment to emulate a dialog.
|
||||
*/
|
||||
public class ConsolidateDialogActivity extends FragmentActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// this activity itself has no content view (see manifest)
|
||||
|
||||
consolidateRecovery();
|
||||
|
||||
}
|
||||
|
||||
private void consolidateRecovery() {
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
/* don't care about the results (for now?)
|
||||
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final ConsolidateResult result =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT_CONSOLIDATE);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
result.createNotify(ConsolidateDialogActivity.this).show();
|
||||
*/
|
||||
|
||||
ConsolidateDialogActivity.this.finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Send all information needed to service to import key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putBoolean(KeychainIntentService.CONSOLIDATE_RECOVERY, true);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -33,6 +33,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
@ -42,6 +43,8 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
public class CreateKeyFinalFragment extends Fragment {
|
||||
@ -125,10 +128,9 @@ public class CreateKeyFinalFragment extends Fragment {
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_importing),
|
||||
getString(R.string.progress_building_key),
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
@ -140,26 +142,21 @@ public class CreateKeyFinalFragment extends Fragment {
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final OperationResults.EditKeyResult result =
|
||||
final OperationResults.SaveKeyringResult result =
|
||||
returnData.getParcelable(OperationResultParcel.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
Log.e(Constants.TAG, "result == null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.getResult() == OperationResultParcel.RESULT_OK) {
|
||||
if (mUploadCheckbox.isChecked()) {
|
||||
// result will be displayed after upload
|
||||
uploadKey(result);
|
||||
} else {
|
||||
// TODO: return result
|
||||
result.createNotify(getActivity());
|
||||
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
}
|
||||
if (mUploadCheckbox.isChecked()) {
|
||||
// result will be displayed after upload
|
||||
uploadKey(result);
|
||||
} else {
|
||||
// display result on error without finishing activity
|
||||
result.createNotify(getActivity());
|
||||
Intent data = new Intent();
|
||||
data.putExtra(OperationResultParcel.EXTRA_RESULT, result);
|
||||
getActivity().setResult(Activity.RESULT_OK, data);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -169,9 +166,12 @@ public class CreateKeyFinalFragment extends Fragment {
|
||||
Bundle data = new Bundle();
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.RSA, 4096, null, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.RSA, 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
|
||||
String userId = KeyRing.createUserId(mName, mEmail, null);
|
||||
parcel.mAddUserIds.add(userId);
|
||||
parcel.mChangePrimaryUserId = userId;
|
||||
@ -191,7 +191,7 @@ public class CreateKeyFinalFragment extends Fragment {
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
private void uploadKey(final OperationResults.EditKeyResult editKeyResult) {
|
||||
private void uploadKey(final OperationResults.SaveKeyringResult saveKeyResult) {
|
||||
// Send all information needed to service to upload key in other thread
|
||||
final Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
@ -199,7 +199,7 @@ public class CreateKeyFinalFragment extends Fragment {
|
||||
|
||||
// set data uri as path to keyring
|
||||
Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
|
||||
editKeyResult.mRingMasterKeyId);
|
||||
saveKeyResult.mRingMasterKeyId);
|
||||
intent.setData(blobUri);
|
||||
|
||||
// fill values for this action
|
||||
@ -211,7 +211,6 @@ public class CreateKeyFinalFragment extends Fragment {
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after uploading is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_uploading), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
@ -219,20 +218,16 @@ public class CreateKeyFinalFragment extends Fragment {
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// TODO: not supported by upload?
|
||||
// if (result.getResult() == OperationResultParcel.RESULT_OK) {
|
||||
// TODO: return result
|
||||
editKeyResult.createNotify(getActivity());
|
||||
// TODO: upload operation needs a result!
|
||||
// TODO: then combine these results
|
||||
//if (result.getResult() == OperationResultParcel.RESULT_OK) {
|
||||
//Notify.showNotify(getActivity(), R.string.key_send_success,
|
||||
//Notify.Style.INFO);
|
||||
|
||||
Notify.showNotify(getActivity(), R.string.key_send_success,
|
||||
Notify.Style.INFO);
|
||||
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
Intent data = new Intent();
|
||||
data.putExtra(OperationResultParcel.EXTRA_RESULT, saveKeyResult);
|
||||
getActivity().setResult(Activity.RESULT_OK, data);
|
||||
getActivity().finish();
|
||||
// } else {
|
||||
// // display result on error without finishing activity
|
||||
// editKeyResult.createNotify(getActivity());
|
||||
// }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -48,8 +48,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
|
||||
@ -340,7 +339,8 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
} else {
|
||||
mSaveKeyringParcel.mRevokeUserIds.add(userId);
|
||||
// not possible to revoke and change to primary user id
|
||||
if (mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) {
|
||||
if (mSaveKeyringParcel.mChangePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) {
|
||||
mSaveKeyringParcel.mChangePrimaryUserId = null;
|
||||
}
|
||||
}
|
||||
@ -407,10 +407,10 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE:
|
||||
Long expiry = (Long) message.getData().
|
||||
getSerializable(EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY_DATE);
|
||||
mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = expiry;
|
||||
case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY:
|
||||
mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry =
|
||||
(Long) message.getData().getSerializable(
|
||||
EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY);
|
||||
break;
|
||||
}
|
||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||
@ -504,7 +504,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
private void save(String passphrase) {
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString());
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_saving),
|
||||
@ -520,8 +519,8 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final OperationResults.EditKeyResult result =
|
||||
returnData.getParcelable(EditKeyResult.EXTRA_RESULT);
|
||||
final OperationResultParcel result =
|
||||
returnData.getParcelable(OperationResultParcel.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
@ -534,7 +533,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
// if good -> finish, return result to showkey and display there!
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EditKeyResult.EXTRA_RESULT, result);
|
||||
intent.putExtra(OperationResultParcel.EXTRA_RESULT, result);
|
||||
getActivity().setResult(EditKeyActivity.RESULT_OK, intent);
|
||||
getActivity().finish();
|
||||
|
||||
|
@ -73,16 +73,13 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
|
||||
public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryption_key_ids";
|
||||
|
||||
// view
|
||||
ViewPager mViewPagerMode;
|
||||
//PagerTabStrip mPagerTabStripMode;
|
||||
PagerTabStripAdapter mTabsAdapterMode;
|
||||
ViewPager mViewPagerContent;
|
||||
PagerTabStrip mPagerTabStripContent;
|
||||
PagerTabStripAdapter mTabsAdapterContent;
|
||||
private int mCurrentMode = PAGER_MODE_ASYMMETRIC;
|
||||
private ViewPager mViewPagerContent;
|
||||
private PagerTabStrip mPagerTabStripContent;
|
||||
private PagerTabStripAdapter mTabsAdapterContent;
|
||||
|
||||
// tabs
|
||||
int mSwitchToMode = PAGER_MODE_ASYMMETRIC;
|
||||
int mSwitchToContent = PAGER_CONTENT_MESSAGE;
|
||||
private int mSwitchToContent = PAGER_CONTENT_MESSAGE;
|
||||
|
||||
private static final int PAGER_MODE_ASYMMETRIC = 0;
|
||||
private static final int PAGER_MODE_SYMMETRIC = 1;
|
||||
@ -102,7 +99,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
|
||||
private String mMessage = "";
|
||||
|
||||
public boolean isModeSymmetric() {
|
||||
return PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem();
|
||||
return PAGER_MODE_SYMMETRIC == mCurrentMode;
|
||||
}
|
||||
|
||||
public boolean isContentMessage() {
|
||||
@ -312,59 +309,60 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
|
||||
String title = isContentMessage() ? getString(R.string.title_share_message)
|
||||
: getString(R.string.title_share_file);
|
||||
|
||||
// fallback on Android 2.3, otherwise we would get weird results
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
return Intent.createChooser(prototype, title);
|
||||
}
|
||||
|
||||
// prevent recursion aka Inception :P
|
||||
String[] blacklist = new String[]{Constants.PACKAGE_NAME + ".ui.EncryptActivity"};
|
||||
|
||||
List<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>();
|
||||
|
||||
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(prototype, 0);
|
||||
List<ResolveInfo> resInfoListFiltered = new ArrayList<ResolveInfo>();
|
||||
if (!resInfoList.isEmpty()) {
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
// do not add blacklisted ones
|
||||
if (resolveInfo.activityInfo == null || Arrays.asList(blacklist).contains(resolveInfo.activityInfo.name))
|
||||
continue;
|
||||
|
||||
resInfoListFiltered.add(resolveInfo);
|
||||
}
|
||||
|
||||
if (!resInfoListFiltered.isEmpty()) {
|
||||
// sorting for nice readability
|
||||
Collections.sort(resInfoListFiltered, new Comparator<ResolveInfo>() {
|
||||
@Override
|
||||
public int compare(ResolveInfo first, ResolveInfo second) {
|
||||
String firstName = first.loadLabel(getPackageManager()).toString();
|
||||
String secondName = second.loadLabel(getPackageManager()).toString();
|
||||
return firstName.compareToIgnoreCase(secondName);
|
||||
}
|
||||
});
|
||||
|
||||
// create the custom intent list
|
||||
for (ResolveInfo resolveInfo : resInfoListFiltered) {
|
||||
Intent targetedShareIntent = (Intent) prototype.clone();
|
||||
targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName);
|
||||
targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
|
||||
|
||||
LabeledIntent lIntent = new LabeledIntent(targetedShareIntent,
|
||||
resolveInfo.activityInfo.packageName,
|
||||
resolveInfo.loadLabel(getPackageManager()),
|
||||
resolveInfo.activityInfo.icon);
|
||||
targetedShareIntents.add(lIntent);
|
||||
}
|
||||
|
||||
// Create chooser with only one Intent in it
|
||||
Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title);
|
||||
// append all other Intents
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{}));
|
||||
return chooserIntent;
|
||||
}
|
||||
|
||||
}
|
||||
// Disabled, produced an empty list on Huawei U8860 with Android Version 4.0.3
|
||||
// // fallback on Android 2.3, otherwise we would get weird results
|
||||
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
// return Intent.createChooser(prototype, title);
|
||||
// }
|
||||
//
|
||||
// // prevent recursion aka Inception :P
|
||||
// String[] blacklist = new String[]{Constants.PACKAGE_NAME + ".ui.EncryptActivity"};
|
||||
//
|
||||
// List<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>();
|
||||
//
|
||||
// List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(prototype, 0);
|
||||
// List<ResolveInfo> resInfoListFiltered = new ArrayList<ResolveInfo>();
|
||||
// if (!resInfoList.isEmpty()) {
|
||||
// for (ResolveInfo resolveInfo : resInfoList) {
|
||||
// // do not add blacklisted ones
|
||||
// if (resolveInfo.activityInfo == null || Arrays.asList(blacklist).contains(resolveInfo.activityInfo.name))
|
||||
// continue;
|
||||
//
|
||||
// resInfoListFiltered.add(resolveInfo);
|
||||
// }
|
||||
//
|
||||
// if (!resInfoListFiltered.isEmpty()) {
|
||||
// // sorting for nice readability
|
||||
// Collections.sort(resInfoListFiltered, new Comparator<ResolveInfo>() {
|
||||
// @Override
|
||||
// public int compare(ResolveInfo first, ResolveInfo second) {
|
||||
// String firstName = first.loadLabel(getPackageManager()).toString();
|
||||
// String secondName = second.loadLabel(getPackageManager()).toString();
|
||||
// return firstName.compareToIgnoreCase(secondName);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// // create the custom intent list
|
||||
// for (ResolveInfo resolveInfo : resInfoListFiltered) {
|
||||
// Intent targetedShareIntent = (Intent) prototype.clone();
|
||||
// targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName);
|
||||
// targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
|
||||
//
|
||||
// LabeledIntent lIntent = new LabeledIntent(targetedShareIntent,
|
||||
// resolveInfo.activityInfo.packageName,
|
||||
// resolveInfo.loadLabel(getPackageManager()),
|
||||
// resolveInfo.activityInfo.icon);
|
||||
// targetedShareIntents.add(lIntent);
|
||||
// }
|
||||
//
|
||||
// // Create chooser with only one Intent in it
|
||||
// Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title);
|
||||
// // append all other Intents
|
||||
// chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{}));
|
||||
// return chooserIntent;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
// fallback to Android's default chooser
|
||||
return Intent.createChooser(prototype, title);
|
||||
@ -385,7 +383,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
|
||||
sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
|
||||
}
|
||||
sendIntent.setType("application/pgp-encrypted");
|
||||
sendIntent.setType("application/octet-stream");
|
||||
}
|
||||
if (!isModeSymmetric() && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<String>();
|
||||
@ -474,13 +472,9 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
mViewPagerMode = (ViewPager) findViewById(R.id.encrypt_pager_mode);
|
||||
//mPagerTabStripMode = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_mode);
|
||||
mViewPagerContent = (ViewPager) findViewById(R.id.encrypt_pager_content);
|
||||
mPagerTabStripContent = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_content);
|
||||
|
||||
mTabsAdapterMode = new PagerTabStripAdapter(this);
|
||||
mViewPagerMode.setAdapter(mTabsAdapterMode);
|
||||
mTabsAdapterContent = new PagerTabStripAdapter(this);
|
||||
mViewPagerContent.setAdapter(mTabsAdapterContent);
|
||||
}
|
||||
@ -502,10 +496,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
|
||||
|
||||
// Handle intent actions
|
||||
handleActions(getIntent());
|
||||
|
||||
mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class, null, getString(R.string.label_asymmetric));
|
||||
mTabsAdapterMode.addTab(EncryptSymmetricFragment.class, null, getString(R.string.label_symmetric));
|
||||
mViewPagerMode.setCurrentItem(mSwitchToMode);
|
||||
updateModeFragment();
|
||||
|
||||
mTabsAdapterContent.addTab(EncryptMessageFragment.class, null, getString(R.string.label_message));
|
||||
mTabsAdapterContent.addTab(EncryptFileFragment.class, null, getString(R.string.label_files));
|
||||
@ -521,6 +512,16 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
private void updateModeFragment() {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.encrypt_pager_mode,
|
||||
mCurrentMode == PAGER_MODE_SYMMETRIC
|
||||
? new EncryptSymmetricFragment()
|
||||
: new EncryptAsymmetricFragment())
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.isCheckable()) {
|
||||
@ -528,9 +529,8 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.check_use_symmetric:
|
||||
mSwitchToMode = item.isChecked() ? PAGER_MODE_SYMMETRIC : PAGER_MODE_ASYMMETRIC;
|
||||
|
||||
mViewPagerMode.setCurrentItem(mSwitchToMode);
|
||||
mCurrentMode = item.isChecked() ? PAGER_MODE_SYMMETRIC : PAGER_MODE_ASYMMETRIC;
|
||||
updateModeFragment();
|
||||
notifyUpdate();
|
||||
break;
|
||||
case R.id.check_use_armor:
|
||||
@ -604,7 +604,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
|
||||
mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
|
||||
|
||||
// preselect keys given by intent
|
||||
mSwitchToMode = PAGER_MODE_ASYMMETRIC;
|
||||
mCurrentMode = PAGER_MODE_ASYMMETRIC;
|
||||
|
||||
/**
|
||||
* Main Actions
|
||||
|
@ -18,35 +18,22 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
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.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.tokenautocomplete.TokenCompleteTextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -54,22 +41,20 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class EncryptAsymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
public static final String ARG_SIGNATURE_KEY_ID = "signature_key_id";
|
||||
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
|
||||
|
||||
ProviderHelper mProviderHelper;
|
||||
|
||||
// view
|
||||
private Spinner mSign;
|
||||
private KeySpinner mSign;
|
||||
private EncryptKeyCompletionView mEncryptKeyView;
|
||||
private SelectSignKeyCursorAdapter mSignAdapter = new SelectSignKeyCursorAdapter();
|
||||
|
||||
// model
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
|
||||
if (mSign != null) {
|
||||
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,17 +86,11 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
|
||||
|
||||
mSign = (Spinner) view.findViewById(R.id.sign);
|
||||
mSign.setAdapter(mSignAdapter);
|
||||
mSign.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
mSign = (KeySpinner) view.findViewById(R.id.sign);
|
||||
mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
setSignatureKeyId(parent.getAdapter().getItemId(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
setSignatureKeyId(Constants.key.none);
|
||||
public void onKeyChanged(long masterKeyId) {
|
||||
setSignatureKeyId(masterKeyId);
|
||||
}
|
||||
});
|
||||
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
||||
@ -128,42 +107,6 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
// preselect keys given
|
||||
preselectKeys();
|
||||
|
||||
getLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<Cursor>() {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
String[] projection = new String[]{
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
KeyRings.KEY_ID,
|
||||
KeyRings.USER_ID,
|
||||
KeyRings.IS_EXPIRED,
|
||||
KeyRings.HAS_SIGN,
|
||||
KeyRings.HAS_ANY_SECRET
|
||||
};
|
||||
|
||||
String where = KeyRings.HAS_ANY_SECRET + " = 1 AND " + KeyRings.HAS_SIGN + " NOT NULL AND "
|
||||
+ KeyRings.IS_REVOKED + " = 0 AND " + KeyRings.IS_EXPIRED + " = 0";
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, projection, where, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
mSignAdapter.swapCursor(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
mSignAdapter.swapCursor(null);
|
||||
}
|
||||
});
|
||||
mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
|
||||
@Override
|
||||
public void onTokenAdded(Object token) {
|
||||
@ -194,6 +137,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
KeyRings.buildUnifiedKeyRingUri(signatureKey));
|
||||
if(keyring.hasAnySecret()) {
|
||||
setSignatureKeyId(keyring.getMasterKeyId());
|
||||
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
@ -211,6 +155,8 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
}
|
||||
}
|
||||
// This is to work-around a rendering bug in TokenCompleteTextView
|
||||
mEncryptKeyView.requestFocus();
|
||||
updateEncryptionKeys();
|
||||
}
|
||||
}
|
||||
@ -233,95 +179,4 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
setEncryptionKeyIds(keyIdsArr);
|
||||
setEncryptionUserIds(userIds.toArray(new String[userIds.size()]));
|
||||
}
|
||||
|
||||
private class SelectSignKeyCursorAdapter extends BaseAdapter implements SpinnerAdapter {
|
||||
private CursorAdapter inner;
|
||||
private int mIndexUserId;
|
||||
private int mIndexKeyId;
|
||||
private int mIndexMasterKeyId;
|
||||
|
||||
public SelectSignKeyCursorAdapter() {
|
||||
inner = new CursorAdapter(null, null, 0) {
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return getActivity().getLayoutInflater().inflate(R.layout.encrypt_asymmetric_signkey, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId));
|
||||
((TextView) view.findViewById(android.R.id.title)).setText(userId[2] == null ? userId[0] : (userId[0] + " (" + userId[2] + ")"));
|
||||
((TextView) view.findViewById(android.R.id.text1)).setText(userId[1]);
|
||||
((TextView) view.findViewById(android.R.id.text2)).setText(PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(mIndexMasterKeyId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
if (newCursor == null) return inner.swapCursor(null);
|
||||
|
||||
mIndexKeyId = newCursor.getColumnIndex(KeyRings.KEY_ID);
|
||||
mIndexUserId = newCursor.getColumnIndex(KeyRings.USER_ID);
|
||||
mIndexMasterKeyId = newCursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
|
||||
if (newCursor.moveToFirst()) {
|
||||
do {
|
||||
if (newCursor.getLong(mIndexMasterKeyId) == mEncryptInterface.getSignatureKey()) {
|
||||
mSign.setSelection(newCursor.getPosition() + 1);
|
||||
}
|
||||
} while (newCursor.moveToNext());
|
||||
}
|
||||
return inner.swapCursor(newCursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return inner.getCount() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
if (position == 0) return null;
|
||||
return inner.getItem(position - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (position == 0) return Constants.key.none;
|
||||
return inner.getItemId(position - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = getDropDownView(position, convertView, parent);
|
||||
v.findViewById(android.R.id.text1).setVisibility(View.GONE);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
||||
View v;
|
||||
if (position == 0) {
|
||||
if (convertView == null) {
|
||||
v = inner.newView(null, null, parent);
|
||||
} else {
|
||||
v = convertView;
|
||||
}
|
||||
((TextView) v.findViewById(android.R.id.title)).setText("None");
|
||||
v.findViewById(android.R.id.text1).setVisibility(View.GONE);
|
||||
v.findViewById(android.R.id.text2).setVisibility(View.GONE);
|
||||
} else {
|
||||
v = inner.getView(position - 1, convertView, parent);
|
||||
v.findViewById(android.R.id.text1).setVisibility(View.VISIBLE);
|
||||
v.findViewById(android.R.id.text2).setVisibility(View.VISIBLE);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ public class FirstTimeActivity extends ActionBarActivity {
|
||||
mSkipSetup.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finishSetup();
|
||||
finishSetup(null);
|
||||
}
|
||||
});
|
||||
|
||||
@ -80,18 +80,22 @@ public class FirstTimeActivity extends ActionBarActivity {
|
||||
|
||||
if (requestCode == REQUEST_CODE_CREATE_OR_IMPORT_KEY) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
finishSetup();
|
||||
finishSetup(data);
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "No valid request code!");
|
||||
}
|
||||
}
|
||||
|
||||
private void finishSetup() {
|
||||
private void finishSetup(Intent srcData) {
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
prefs.setFirstTime(false);
|
||||
Intent intent = new Intent(FirstTimeActivity.this, KeyListActivity.class);
|
||||
startActivity(intent);
|
||||
Intent intent = new Intent(this, KeyListActivity.class);
|
||||
// give intent through to display notify
|
||||
if (srcData != null) {
|
||||
intent.putExtras(srcData);
|
||||
}
|
||||
startActivityForResult(intent, 0);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||
@ -64,7 +65,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
+ "IMPORT_KEY_FROM_KEYSERVER";
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT =
|
||||
Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN_RESULT";
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
|
||||
public static final String ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_KEY_FROM_FILE_AND_RETURN";
|
||||
@ -87,7 +88,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String EXTRA_FINGERPRINT = "fingerprint";
|
||||
|
||||
// only used by ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN when used from OpenPgpService
|
||||
// only used by ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE when used from OpenPgpService
|
||||
public static final String EXTRA_PENDING_INTENT_DATA = "data";
|
||||
private Intent mPendingIntentData;
|
||||
|
||||
@ -170,7 +171,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
startListFragment(savedInstanceState, importData, null, null);
|
||||
}
|
||||
} else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action)
|
||||
|| ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(action)
|
||||
|| ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(action)
|
||||
|| ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(action)) {
|
||||
|
||||
// only used for OpenPgpService
|
||||
@ -287,7 +288,9 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
// cancel loader and clear list
|
||||
mListFragment.destroyLoader();
|
||||
if (mListFragment != null) {
|
||||
mListFragment.destroyLoader();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -456,28 +459,25 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
return;
|
||||
}
|
||||
final ImportKeyResult result =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT_IMPORT);
|
||||
returnData.getParcelable(OperationResultParcel.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
Log.e(Constants.TAG, "result == null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())) {
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())
|
||||
|| ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
|
||||
ImportKeysActivity.this.setResult(RESULT_OK, intent);
|
||||
ImportKeysActivity.this.finish();
|
||||
return;
|
||||
}
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) {
|
||||
ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData);
|
||||
ImportKeysActivity.this.finish();
|
||||
return;
|
||||
}
|
||||
if (ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) {
|
||||
ImportKeysActivity.this.setResult(RESULT_OK);
|
||||
ImportKeysActivity.this.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
result.createNotify(ImportKeysActivity.this).show();
|
||||
}
|
||||
@ -503,7 +503,8 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
// to prevent Java Binder problems on heavy imports
|
||||
// read FileImportCache for more info.
|
||||
try {
|
||||
FileImportCache<ParcelableKeyRing> cache = new FileImportCache<ParcelableKeyRing>(this);
|
||||
FileImportCache<ParcelableKeyRing> cache =
|
||||
new FileImportCache<ParcelableKeyRing>(this, "key_import.pcl");
|
||||
cache.writeCache(selectedEntries);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
@ -29,15 +29,19 @@ import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ImportKeysServerFragment extends Fragment {
|
||||
public static final String ARG_QUERY = "query";
|
||||
public static final String ARG_KEYSERVER = "keyserver";
|
||||
@ -46,7 +50,7 @@ public class ImportKeysServerFragment extends Fragment {
|
||||
private ImportKeysActivity mImportActivity;
|
||||
|
||||
private View mSearchButton;
|
||||
private EditText mQueryEditText;
|
||||
private AutoCompleteTextView mQueryEditText;
|
||||
private View mConfigButton;
|
||||
private View mConfigLayout;
|
||||
private Spinner mServerSpinner;
|
||||
@ -75,7 +79,7 @@ public class ImportKeysServerFragment extends Fragment {
|
||||
View view = inflater.inflate(R.layout.import_keys_server_fragment, container, false);
|
||||
|
||||
mSearchButton = view.findViewById(R.id.import_server_search);
|
||||
mQueryEditText = (EditText) view.findViewById(R.id.import_server_query);
|
||||
mQueryEditText = (AutoCompleteTextView) view.findViewById(R.id.import_server_query);
|
||||
mConfigButton = view.findViewById(R.id.import_server_config_button);
|
||||
mConfigLayout = view.findViewById(R.id.import_server_config);
|
||||
mServerSpinner = (Spinner) view.findViewById(R.id.import_server_spinner);
|
||||
@ -93,6 +97,16 @@ public class ImportKeysServerFragment extends Fragment {
|
||||
mSearchButton.setEnabled(false);
|
||||
}
|
||||
|
||||
List<String> namesAndEmails = ContactHelper.getContactNames(getActivity());
|
||||
namesAndEmails.addAll(ContactHelper.getContactMails(getActivity()));
|
||||
mQueryEditText.setThreshold(3);
|
||||
mQueryEditText.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
namesAndEmails
|
||||
)
|
||||
);
|
||||
|
||||
mSearchButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -17,8 +18,11 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
@ -28,6 +32,10 @@ import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
@ -63,6 +71,7 @@ public class KeyListActivity extends DrawerActivity {
|
||||
getMenuInflater().inflate(R.menu.key_list, menu);
|
||||
|
||||
if (Constants.DEBUG) {
|
||||
menu.findItem(R.id.menu_key_list_debug_cons).setVisible(true);
|
||||
menu.findItem(R.id.menu_key_list_debug_read).setVisible(true);
|
||||
menu.findItem(R.id.menu_key_list_debug_write).setVisible(true);
|
||||
menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true);
|
||||
@ -92,6 +101,10 @@ public class KeyListActivity extends DrawerActivity {
|
||||
mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_cons:
|
||||
consolidate();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_read:
|
||||
try {
|
||||
KeychainDatabase.debugBackup(this, true);
|
||||
@ -133,7 +146,67 @@ public class KeyListActivity extends DrawerActivity {
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||
startActivity(intent);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
private void consolidate() {
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final ConsolidateResult result =
|
||||
returnData.getParcelable(OperationResultParcel.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.createNotify(KeyListActivity.this).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Send all information needed to service to import key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// if a result has been returned, display a notify
|
||||
if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) {
|
||||
OperationResultParcel result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -79,14 +80,15 @@ public class KeyListFragment extends LoaderFragment
|
||||
private KeyListAdapter mAdapter;
|
||||
private StickyListHeadersListView mStickyList;
|
||||
|
||||
// saves the mode object for multiselect, needed for reset at some point
|
||||
private ActionMode mActionMode = null;
|
||||
|
||||
private String mQuery;
|
||||
private SearchView mSearchView;
|
||||
// empty list layout
|
||||
private Button mButtonEmptyCreate;
|
||||
private Button mButtonEmptyImport;
|
||||
|
||||
public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012;
|
||||
|
||||
/**
|
||||
* Load custom layout with StickyListView from library
|
||||
*/
|
||||
@ -105,7 +107,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getActivity(), CreateKeyActivity.class);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
});
|
||||
mButtonEmptyImport = (Button) view.findViewById(R.id.key_list_empty_button_import);
|
||||
@ -115,7 +117,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getActivity(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
});
|
||||
|
||||
@ -148,6 +150,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
android.view.MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.key_list_multi, menu);
|
||||
mActionMode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -193,6 +196,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
mActionMode = null;
|
||||
mAdapter.clearSelection();
|
||||
}
|
||||
|
||||
@ -288,6 +292,13 @@ public class KeyListFragment extends LoaderFragment
|
||||
// this view is made visible if no data is available
|
||||
mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
|
||||
|
||||
// end action mode, if any
|
||||
if (mActionMode != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
mActionMode.finish();
|
||||
}
|
||||
}
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setContentShown(true);
|
||||
@ -330,12 +341,12 @@ public class KeyListFragment extends LoaderFragment
|
||||
* Show dialog to delete key
|
||||
*
|
||||
* @param masterKeyIds
|
||||
* @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not
|
||||
* @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) {
|
||||
// Can only work on singular secret keys
|
||||
if(hasSecret && masterKeyIds.length > 1) {
|
||||
if (hasSecret && masterKeyIds.length > 1) {
|
||||
Notify.showNotify(getActivity(), R.string.secret_cannot_multiple,
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
@ -365,6 +376,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
// Get the searchview
|
||||
MenuItem searchItem = menu.findItem(R.id.menu_key_list_search);
|
||||
|
||||
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
|
||||
// Execute this when searching
|
||||
@ -383,7 +395,6 @@ public class KeyListFragment extends LoaderFragment
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
mQuery = null;
|
||||
mSearchView.setQuery("", true);
|
||||
getLoaderManager().restartLoader(0, null, KeyListFragment.this);
|
||||
return true;
|
||||
}
|
||||
@ -399,11 +410,18 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
Log.d(Constants.TAG, "onQueryTextChange s:" + s);
|
||||
// Called when the action bar search text has changed. Update
|
||||
// the search filter, and restart the loader to do a new query
|
||||
// with this filter.
|
||||
mQuery = !TextUtils.isEmpty(s) ? s : null;
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
// If the nav drawer is opened, onQueryTextChange("") is executed.
|
||||
// This hack prevents restarting the loader.
|
||||
// TODO: better way to fix this?
|
||||
String tmp = (mQuery == null) ? "" : mQuery;
|
||||
if (!s.equals(tmp)) {
|
||||
mQuery = s;
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -479,7 +497,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
boolean isExpired = !cursor.isNull(INDEX_EXPIRY)
|
||||
&& new Date(cursor.getLong(INDEX_EXPIRY)*1000).before(new Date());
|
||||
&& new Date(cursor.getLong(INDEX_EXPIRY) * 1000).before(new Date());
|
||||
boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
||||
|
||||
// Note: order is important!
|
||||
@ -521,6 +539,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
return mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
}
|
||||
|
||||
public long getMasterKeyId(int id) {
|
||||
if (!mCursor.moveToPosition(id)) {
|
||||
throw new IllegalStateException("couldn't move cursor to position " + id);
|
||||
@ -625,7 +644,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
public boolean isAnySecretSelected() {
|
||||
for (int pos : mSelection.keySet()) {
|
||||
if(mAdapter.isSecretAvailable(pos))
|
||||
if (mAdapter.isSecretAvailable(pos))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -317,10 +317,10 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
|
||||
private static void initializeHashAlgorithm
|
||||
(final IntegerListPreference mHashAlgorithm, int[] valueIds, String[] entries, String[] values) {
|
||||
valueIds = new int[]{HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160,
|
||||
valueIds = new int[]{HashAlgorithmTags.RIPEMD160,
|
||||
HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256,
|
||||
HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512,};
|
||||
entries = new String[]{"MD5", "RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384",
|
||||
entries = new String[]{"RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384",
|
||||
"SHA-512",};
|
||||
values = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
@ -166,7 +167,7 @@ public class ViewCertActivity extends ActionBarActivity
|
||||
mStatus.setTextColor(getResources().getColor(R.color.black));
|
||||
}
|
||||
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(this, sig.getKeyAlgorithm(), 0);
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(this, sig.getKeyAlgorithm(), null, null);
|
||||
mAlgorithm.setText(algorithmStr);
|
||||
|
||||
mRowReason.setVisibility(View.GONE);
|
||||
|
@ -54,6 +54,8 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||
@ -303,8 +305,10 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -313,6 +317,7 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// if a result has been returned, display a notify
|
||||
if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) {
|
||||
OperationResultParcel result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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
|
||||
|
@ -155,8 +155,8 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
||||
// don't show full fingerprint on key import
|
||||
holder.fingerprint.setVisibility(View.GONE);
|
||||
|
||||
if (entry.getBitStrength() != 0 && entry.getAlgorithm() != null) {
|
||||
holder.algorithm.setText("" + entry.getBitStrength() + "/" + entry.getAlgorithm());
|
||||
if (entry.getAlgorithm() != null) {
|
||||
holder.algorithm.setText(entry.getAlgorithm());
|
||||
holder.algorithm.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.algorithm.setVisibility(View.INVISIBLE);
|
||||
|
@ -35,7 +35,9 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class SubkeysAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
@ -50,6 +52,7 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
Keys.RANK,
|
||||
Keys.ALGORITHM,
|
||||
Keys.KEY_SIZE,
|
||||
Keys.KEY_CURVE_OID,
|
||||
Keys.HAS_SECRET,
|
||||
Keys.CAN_CERTIFY,
|
||||
Keys.CAN_ENCRYPT,
|
||||
@ -64,14 +67,15 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
private static final int INDEX_RANK = 2;
|
||||
private static final int INDEX_ALGORITHM = 3;
|
||||
private static final int INDEX_KEY_SIZE = 4;
|
||||
private static final int INDEX_HAS_SECRET = 5;
|
||||
private static final int INDEX_CAN_CERTIFY = 6;
|
||||
private static final int INDEX_CAN_ENCRYPT = 7;
|
||||
private static final int INDEX_CAN_SIGN = 8;
|
||||
private static final int INDEX_IS_REVOKED = 9;
|
||||
private static final int INDEX_CREATION = 10;
|
||||
private static final int INDEX_EXPIRY = 11;
|
||||
private static final int INDEX_FINGERPRINT = 12;
|
||||
private static final int INDEX_KEY_CURVE_OID = 5;
|
||||
private static final int INDEX_HAS_SECRET = 6;
|
||||
private static final int INDEX_CAN_CERTIFY = 7;
|
||||
private static final int INDEX_CAN_ENCRYPT = 8;
|
||||
private static final int INDEX_CAN_SIGN = 9;
|
||||
private static final int INDEX_IS_REVOKED = 10;
|
||||
private static final int INDEX_CREATION = 11;
|
||||
private static final int INDEX_EXPIRY = 12;
|
||||
private static final int INDEX_FINGERPRINT = 13;
|
||||
|
||||
public SubkeysAdapter(Context context, Cursor c, int flags,
|
||||
SaveKeyringParcel saveKeyringParcel) {
|
||||
@ -139,7 +143,8 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
|
||||
context,
|
||||
cursor.getInt(INDEX_ALGORITHM),
|
||||
cursor.getInt(INDEX_KEY_SIZE)
|
||||
cursor.getInt(INDEX_KEY_SIZE),
|
||||
cursor.getString(INDEX_KEY_CURVE_OID)
|
||||
);
|
||||
|
||||
vKeyId.setText(keyIdStr);
|
||||
@ -183,7 +188,7 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
|
||||
SaveKeyringParcel.SubkeyChange subkeyChange = mSaveKeyringParcel.getSubkeyChange(keyId);
|
||||
if (subkeyChange != null) {
|
||||
if (subkeyChange.mExpiry == null) {
|
||||
if (subkeyChange.mExpiry == null || subkeyChange.mExpiry == 0L) {
|
||||
expiryDate = null;
|
||||
} else {
|
||||
expiryDate = new Date(subkeyChange.mExpiry * 1000);
|
||||
@ -198,9 +203,13 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
boolean isExpired;
|
||||
if (expiryDate != null) {
|
||||
isExpired = expiryDate.before(new Date());
|
||||
Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
expiryCal.setTime(expiryDate);
|
||||
// convert from UTC to time zone of device
|
||||
expiryCal.setTimeZone(TimeZone.getDefault());
|
||||
|
||||
vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": "
|
||||
+ DateFormat.getDateFormat(context).format(expiryDate));
|
||||
+ DateFormat.getDateFormat(context).format(expiryCal.getTime()));
|
||||
} else {
|
||||
isExpired = false;
|
||||
|
||||
|
@ -33,21 +33,19 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAdd> {
|
||||
private LayoutInflater mInflater;
|
||||
private Activity mActivity;
|
||||
|
||||
// hold a private reference to the underlying data List
|
||||
private List<SaveKeyringParcel.SubkeyAdd> mData;
|
||||
|
||||
public SubkeysAddedAdapter(Activity activity, List<SaveKeyringParcel.SubkeyAdd> data) {
|
||||
super(activity, -1, data);
|
||||
mActivity = activity;
|
||||
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mData = data;
|
||||
}
|
||||
|
||||
static class ViewHolder {
|
||||
@ -101,16 +99,21 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
|
||||
mActivity,
|
||||
holder.mModel.mAlgorithm,
|
||||
holder.mModel.mKeysize
|
||||
holder.mModel.mKeySize,
|
||||
holder.mModel.mCurve
|
||||
);
|
||||
holder.vKeyId.setText(R.string.edit_key_new_subkey);
|
||||
holder.vKeyDetails.setText(algorithmStr);
|
||||
|
||||
if (holder.mModel.mExpiry != null) {
|
||||
if (holder.mModel.mExpiry != 0L) {
|
||||
Date expiryDate = new Date(holder.mModel.mExpiry * 1000);
|
||||
Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
expiryCal.setTime(expiryDate);
|
||||
// convert from UTC to time zone of device
|
||||
expiryCal.setTimeZone(TimeZone.getDefault());
|
||||
|
||||
holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": "
|
||||
+ DateFormat.getDateFormat(getContext()).format(expiryDate));
|
||||
+ DateFormat.getDateFormat(getContext()).format(expiryCal.getTime()));
|
||||
} else {
|
||||
holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": "
|
||||
+ getContext().getString(R.string.none));
|
||||
|
@ -20,19 +20,18 @@ package org.sufficientlysecure.keychain.ui.dialog;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.DatePicker;
|
||||
@ -40,17 +39,18 @@ import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
|
||||
import org.sufficientlysecure.keychain.util.Choice;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
@ -67,7 +67,10 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
private TableRow mExpiryRow;
|
||||
private DatePicker mExpiryDatePicker;
|
||||
private Spinner mAlgorithmSpinner;
|
||||
private View mKeySizeRow;
|
||||
private Spinner mKeySizeSpinner;
|
||||
private View mCurveRow;
|
||||
private Spinner mCurveSpinner;
|
||||
private TextView mCustomKeyTextView;
|
||||
private EditText mCustomKeyEditText;
|
||||
private TextView mCustomKeyInfoTextView;
|
||||
@ -76,6 +79,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
private CheckBox mFlagEncrypt;
|
||||
private CheckBox mFlagAuthenticate;
|
||||
|
||||
private boolean mWillBeMasterKey;
|
||||
|
||||
public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) {
|
||||
mAlgorithmSelectedListener = listener;
|
||||
}
|
||||
@ -96,7 +101,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
final FragmentActivity context = getActivity();
|
||||
final LayoutInflater mInflater;
|
||||
|
||||
final boolean willBeMasterKey = getArguments().getBoolean(ARG_WILL_BE_MASTER_KEY);
|
||||
mWillBeMasterKey = getArguments().getBoolean(ARG_WILL_BE_MASTER_KEY);
|
||||
mInflater = context.getLayoutInflater();
|
||||
|
||||
CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context);
|
||||
@ -110,6 +115,9 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker);
|
||||
mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm);
|
||||
mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size);
|
||||
mCurveSpinner = (Spinner) view.findViewById(R.id.add_subkey_curve);
|
||||
mKeySizeRow = view.findViewById(R.id.add_subkey_row_size);
|
||||
mCurveRow = view.findViewById(R.id.add_subkey_row_curve);
|
||||
mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label);
|
||||
mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input);
|
||||
mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info);
|
||||
@ -130,27 +138,36 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
});
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
mExpiryDatePicker.setMinDate(new Date().getTime() + DateUtils.DAY_IN_MILLIS);
|
||||
// date picker works based on default time zone
|
||||
Calendar minDateCal = Calendar.getInstance(TimeZone.getDefault());
|
||||
minDateCal.add(Calendar.DAY_OF_YEAR, 1); // at least one day after creation (today)
|
||||
mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime());
|
||||
}
|
||||
|
||||
ArrayList<Choice> choices = new ArrayList<Choice>();
|
||||
choices.add(new Choice(PublicKeyAlgorithmTags.DSA, getResources().getString(
|
||||
R.string.dsa)));
|
||||
if (!willBeMasterKey) {
|
||||
choices.add(new Choice(PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, getResources().getString(
|
||||
R.string.elgamal)));
|
||||
}
|
||||
choices.add(new Choice(PublicKeyAlgorithmTags.RSA_GENERAL, getResources().getString(
|
||||
R.string.rsa)));
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(context,
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mAlgorithmSpinner.setAdapter(adapter);
|
||||
// make RSA the default
|
||||
for (int i = 0; i < choices.size(); ++i) {
|
||||
if (choices.get(i).getId() == PublicKeyAlgorithmTags.RSA_GENERAL) {
|
||||
mAlgorithmSpinner.setSelection(i);
|
||||
break;
|
||||
{
|
||||
ArrayList<Choice<Algorithm>> choices = new ArrayList<Choice<Algorithm>>();
|
||||
choices.add(new Choice<Algorithm>(Algorithm.DSA, getResources().getString(
|
||||
R.string.dsa)));
|
||||
if (!mWillBeMasterKey) {
|
||||
choices.add(new Choice<Algorithm>(Algorithm.ELGAMAL, getResources().getString(
|
||||
R.string.elgamal)));
|
||||
}
|
||||
choices.add(new Choice<Algorithm>(Algorithm.RSA, getResources().getString(
|
||||
R.string.rsa)));
|
||||
choices.add(new Choice<Algorithm>(Algorithm.ECDSA, getResources().getString(
|
||||
R.string.ecdsa)));
|
||||
choices.add(new Choice<Algorithm>(Algorithm.ECDH, getResources().getString(
|
||||
R.string.ecdh)));
|
||||
ArrayAdapter<Choice<Algorithm>> adapter = new ArrayAdapter<Choice<Algorithm>>(context,
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mAlgorithmSpinner.setAdapter(adapter);
|
||||
// make RSA the default
|
||||
for (int i = 0; i < choices.size(); ++i) {
|
||||
if (choices.get(i).getId() == Algorithm.RSA) {
|
||||
mAlgorithmSpinner.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,58 +178,42 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
mKeySizeSpinner.setAdapter(keySizeAdapter);
|
||||
mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length
|
||||
|
||||
{
|
||||
ArrayList<Choice<Curve>> choices = new ArrayList<Choice<Curve>>();
|
||||
|
||||
dialog.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface di, int id) {
|
||||
di.dismiss();
|
||||
Choice newKeyAlgorithmChoice = (Choice) mAlgorithmSpinner.getSelectedItem();
|
||||
int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(), getSelectedKeyLength());
|
||||
choices.add(new Choice<Curve>(Curve.NIST_P256, getResources().getString(
|
||||
R.string.key_curve_nist_p256)));
|
||||
choices.add(new Choice<Curve>(Curve.NIST_P384, getResources().getString(
|
||||
R.string.key_curve_nist_p384)));
|
||||
choices.add(new Choice<Curve>(Curve.NIST_P521, getResources().getString(
|
||||
R.string.key_curve_nist_p521)));
|
||||
|
||||
int flags = 0;
|
||||
if (mFlagCertify.isChecked()) {
|
||||
flags |= KeyFlags.CERTIFY_OTHER;
|
||||
}
|
||||
if (mFlagSign.isChecked()) {
|
||||
flags |= KeyFlags.SIGN_DATA;
|
||||
}
|
||||
if (mFlagEncrypt.isChecked()) {
|
||||
flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
||||
}
|
||||
if (mFlagAuthenticate.isChecked()) {
|
||||
flags |= KeyFlags.AUTHENTICATION;
|
||||
}
|
||||
/* @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)));
|
||||
*/
|
||||
|
||||
Long expiry;
|
||||
if (mNoExpiryCheckBox.isChecked()) {
|
||||
expiry = null;
|
||||
} else {
|
||||
Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
//noinspection ResourceType
|
||||
selectedCal.set(mExpiryDatePicker.getYear(),
|
||||
mExpiryDatePicker.getMonth(), mExpiryDatePicker.getDayOfMonth());
|
||||
expiry = selectedCal.getTime().getTime() / 1000;
|
||||
}
|
||||
|
||||
SaveKeyringParcel.SubkeyAdd newSubkey = new SaveKeyringParcel.SubkeyAdd(
|
||||
newKeyAlgorithmChoice.getId(),
|
||||
newKeySize,
|
||||
flags,
|
||||
expiry
|
||||
);
|
||||
mAlgorithmSelectedListener.onAlgorithmSelected(newSubkey);
|
||||
}
|
||||
ArrayAdapter<Choice<Curve>> adapter = new ArrayAdapter<Choice<Curve>>(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;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
dialog.setCancelable(true);
|
||||
dialog.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface di, int id) {
|
||||
di.dismiss();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// onClickListener are set in onStart() to override default dismiss behaviour
|
||||
dialog.setPositiveButton(android.R.string.ok, null);
|
||||
dialog.setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
final AlertDialog alertDialog = dialog.show();
|
||||
|
||||
@ -246,7 +247,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
setKeyLengthSpinnerValuesForAlgorithm(((Choice) parent.getSelectedItem()).getId());
|
||||
updateUiForAlgorithm(((Choice<Algorithm>) parent.getSelectedItem()).getId());
|
||||
|
||||
setCustomKeyVisibility();
|
||||
setOkButtonAvailability(alertDialog);
|
||||
@ -260,6 +261,79 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart(); //super.onStart() is where dialog.show() is actually called on the underlying dialog, so we have to do it after this point
|
||||
AlertDialog d = (AlertDialog) getDialog();
|
||||
if (d != null) {
|
||||
Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
|
||||
Button negativeButton = d.getButton(Dialog.BUTTON_NEGATIVE);
|
||||
positiveButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (!mFlagCertify.isChecked() && !mFlagSign.isChecked()
|
||||
&& !mFlagEncrypt.isChecked() && !mFlagAuthenticate.isChecked()) {
|
||||
Toast.makeText(getActivity(), R.string.edit_key_select_flag, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId();
|
||||
Curve curve = null;
|
||||
Integer keySize = null;
|
||||
// For EC keys, add a curve
|
||||
if (algorithm == Algorithm.ECDH || algorithm == Algorithm.ECDSA) {
|
||||
curve = ((Choice<Curve>) mCurveSpinner.getSelectedItem()).getId();
|
||||
// Otherwise, get a keysize
|
||||
} else {
|
||||
keySize = getProperKeyLength(algorithm, getSelectedKeyLength());
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
if (mFlagCertify.isChecked()) {
|
||||
flags |= KeyFlags.CERTIFY_OTHER;
|
||||
}
|
||||
if (mFlagSign.isChecked()) {
|
||||
flags |= KeyFlags.SIGN_DATA;
|
||||
}
|
||||
if (mFlagEncrypt.isChecked()) {
|
||||
flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
||||
}
|
||||
if (mFlagAuthenticate.isChecked()) {
|
||||
flags |= KeyFlags.AUTHENTICATION;
|
||||
}
|
||||
|
||||
long expiry;
|
||||
if (mNoExpiryCheckBox.isChecked()) {
|
||||
expiry = 0L;
|
||||
} else {
|
||||
Calendar selectedCal = Calendar.getInstance(TimeZone.getDefault());
|
||||
//noinspection ResourceType
|
||||
selectedCal.set(mExpiryDatePicker.getYear(),
|
||||
mExpiryDatePicker.getMonth(), mExpiryDatePicker.getDayOfMonth());
|
||||
// date picker uses default time zone, we need to convert to UTC
|
||||
selectedCal.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
expiry = selectedCal.getTime().getTime() / 1000;
|
||||
}
|
||||
|
||||
SaveKeyringParcel.SubkeyAdd newSubkey = new SaveKeyringParcel.SubkeyAdd(
|
||||
algorithm, keySize, curve, flags, expiry
|
||||
);
|
||||
mAlgorithmSelectedListener.onAlgorithmSelected(newSubkey);
|
||||
|
||||
// finally, dismiss the dialogue
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
negativeButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private int getSelectedKeyLength() {
|
||||
final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem();
|
||||
final String customLengthString = getResources().getString(R.string.key_size_custom);
|
||||
@ -290,16 +364,16 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
* @return correct key length, according to SpongyCastle specification. Returns <code>-1</code>, if key length is
|
||||
* inappropriate.
|
||||
*/
|
||||
private int getProperKeyLength(int algorithmId, int currentKeyLength) {
|
||||
private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) {
|
||||
final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192};
|
||||
int properKeyLength = -1;
|
||||
switch (algorithmId) {
|
||||
case PublicKeyAlgorithmTags.RSA_GENERAL:
|
||||
switch (algorithm) {
|
||||
case RSA:
|
||||
if (currentKeyLength > 1024 && currentKeyLength <= 16384) {
|
||||
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
|
||||
}
|
||||
break;
|
||||
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
|
||||
case ELGAMAL:
|
||||
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
|
||||
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
|
||||
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
|
||||
@ -314,7 +388,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
}
|
||||
properKeyLength = elGamalSupportedLengths[minimalIndex];
|
||||
break;
|
||||
case PublicKeyAlgorithmTags.DSA:
|
||||
case DSA:
|
||||
if (currentKeyLength >= 512 && currentKeyLength <= 1024) {
|
||||
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
|
||||
}
|
||||
@ -324,10 +398,10 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
}
|
||||
|
||||
private void setOkButtonAvailability(AlertDialog alertDialog) {
|
||||
final Choice selectedAlgorithm = (Choice) mAlgorithmSpinner.getSelectedItem();
|
||||
final int selectedKeySize = getSelectedKeyLength(); //Integer.parseInt((String) mKeySizeSpinner.getSelectedItem());
|
||||
final int properKeyLength = getProperKeyLength(selectedAlgorithm.getId(), selectedKeySize);
|
||||
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(properKeyLength > 0);
|
||||
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() {
|
||||
@ -343,38 +417,104 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
// hide keyboard after setting visibility to gone
|
||||
if (visibility == View.GONE) {
|
||||
InputMethodManager imm = (InputMethodManager)
|
||||
getActivity().getSystemService(getActivity().INPUT_METHOD_SERVICE);
|
||||
getActivity().getSystemService(FragmentActivity.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(mCustomKeyEditText.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void setKeyLengthSpinnerValuesForAlgorithm(int algorithmId) {
|
||||
private void updateUiForAlgorithm(Algorithm algorithm) {
|
||||
final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter();
|
||||
final Object selectedItem = mKeySizeSpinner.getSelectedItem();
|
||||
keySizeAdapter.clear();
|
||||
switch (algorithmId) {
|
||||
case PublicKeyAlgorithmTags.RSA_GENERAL:
|
||||
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 PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
|
||||
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 PublicKeyAlgorithmTags.DSA:
|
||||
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();
|
||||
|
||||
// when switching algorithm, try to select same key length as before
|
||||
for (int i = 0; i < keySizeAdapter.getCount(); i++) {
|
||||
if (selectedItem.equals(keySizeAdapter.getItem(i))) {
|
||||
mKeySizeSpinner.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
|
@ -18,7 +18,9 @@
|
||||
package org.sufficientlysecure.keychain.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
@ -31,9 +33,10 @@ import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -48,8 +51,6 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
private TextView mMainMessage;
|
||||
private View mInflateView;
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
/**
|
||||
* Creates new instance of this delete file dialog fragment
|
||||
*/
|
||||
@ -68,7 +69,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
final Messenger messenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
|
||||
|
||||
@ -83,6 +84,8 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
|
||||
builder.setTitle(R.string.warning);
|
||||
|
||||
final boolean hasSecret;
|
||||
|
||||
// If only a single key has been selected
|
||||
if (masterKeyIds.length == 1) {
|
||||
long masterKeyId = masterKeyIds[0];
|
||||
@ -98,7 +101,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
}
|
||||
);
|
||||
String userId = (String) data.get(KeyRings.USER_ID);
|
||||
boolean hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1;
|
||||
hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1;
|
||||
|
||||
// Set message depending on which key it is.
|
||||
mMainMessage.setText(getString(
|
||||
@ -107,12 +110,12 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
userId
|
||||
));
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
sendMessageToHandler(MESSAGE_ERROR, null);
|
||||
dismiss();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
mMainMessage.setText(R.string.key_deletion_confirmation_multi);
|
||||
hasSecret = false;
|
||||
}
|
||||
|
||||
builder.setIcon(R.drawable.ic_dialog_alert_holo_light);
|
||||
@ -120,18 +123,45 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
boolean success = false;
|
||||
for (long masterKeyId : masterKeyIds) {
|
||||
int count = activity.getContentResolver().delete(
|
||||
KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null
|
||||
);
|
||||
success = count > 0;
|
||||
}
|
||||
if (success) {
|
||||
sendMessageToHandler(MESSAGE_OKAY, null);
|
||||
} else {
|
||||
sendMessageToHandler(MESSAGE_ERROR, null);
|
||||
}
|
||||
// Send all information needed to service to import key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_DELETE);
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_deleting),
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
try {
|
||||
Message msg = Message.obtain();
|
||||
msg.copyFrom(message);
|
||||
messenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "messenger error", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putLongArray(KeychainIntentService.DELETE_KEY_LIST, masterKeyIds);
|
||||
data.putBoolean(KeychainIntentService.DELETE_IS_SECRET, hasSecret);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
@ -146,23 +176,4 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
return builder.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,10 @@ import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
@ -40,17 +41,14 @@ import java.util.TimeZone;
|
||||
|
||||
public class EditSubkeyExpiryDialogFragment extends DialogFragment {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_CREATION_DATE = "creation_date";
|
||||
private static final String ARG_EXPIRY_DATE = "expiry_date";
|
||||
private static final String ARG_CREATION = "creation";
|
||||
private static final String ARG_EXPIRY = "expiry";
|
||||
|
||||
public static final int MESSAGE_NEW_EXPIRY_DATE = 1;
|
||||
public static final int MESSAGE_NEW_EXPIRY = 1;
|
||||
public static final int MESSAGE_CANCEL = 2;
|
||||
public static final String MESSAGE_DATA_EXPIRY_DATE = "expiry_date";
|
||||
public static final String MESSAGE_DATA_EXPIRY = "expiry";
|
||||
|
||||
private Messenger mMessenger;
|
||||
private Calendar mExpiryCal;
|
||||
|
||||
private DatePicker mDatePicker;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
@ -60,8 +58,8 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment {
|
||||
EditSubkeyExpiryDialogFragment frag = new EditSubkeyExpiryDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
args.putSerializable(ARG_CREATION_DATE, creationDate);
|
||||
args.putSerializable(ARG_EXPIRY_DATE, expiryDate);
|
||||
args.putSerializable(ARG_CREATION, creationDate);
|
||||
args.putSerializable(ARG_EXPIRY, expiryDate);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@ -75,15 +73,17 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment {
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
Date creationDate = new Date(getArguments().getLong(ARG_CREATION_DATE) * 1000);
|
||||
Date expiryDate = new Date(getArguments().getLong(ARG_EXPIRY_DATE) * 1000);
|
||||
long creation = getArguments().getLong(ARG_CREATION);
|
||||
long expiry = getArguments().getLong(ARG_EXPIRY);
|
||||
|
||||
Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
creationCal.setTime(creationDate);
|
||||
mExpiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
mExpiryCal.setTime(expiryDate);
|
||||
creationCal.setTime(new Date(creation * 1000));
|
||||
final Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
expiryCal.setTime(new Date(expiry * 1000));
|
||||
|
||||
Log.d(Constants.TAG, "onCreateDialog");
|
||||
// date picker works with default time zone, we need to convert from UTC to default timezone
|
||||
creationCal.setTimeZone(TimeZone.getDefault());
|
||||
expiryCal.setTimeZone(TimeZone.getDefault());
|
||||
|
||||
// Explicitly not using DatePickerDialog here!
|
||||
// DatePickerDialog is difficult to customize and has many problems (see old git versions)
|
||||
@ -95,19 +95,64 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment {
|
||||
View view = inflater.inflate(R.layout.edit_subkey_expiry_dialog, null);
|
||||
alert.setView(view);
|
||||
|
||||
mDatePicker = (DatePicker) view.findViewById(R.id.edit_subkey_expiry_date_picker);
|
||||
final CheckBox noExpiry = (CheckBox) view.findViewById(R.id.edit_subkey_expiry_no_expiry);
|
||||
final DatePicker datePicker = (DatePicker) view.findViewById(R.id.edit_subkey_expiry_date_picker);
|
||||
|
||||
noExpiry.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
datePicker.setVisibility(View.GONE);
|
||||
} else {
|
||||
datePicker.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// init date picker with default selected date
|
||||
if (expiry == 0L) {
|
||||
noExpiry.setChecked(true);
|
||||
datePicker.setVisibility(View.GONE);
|
||||
|
||||
Calendar todayCal = Calendar.getInstance(TimeZone.getDefault());
|
||||
if (creationCal.after(todayCal)) {
|
||||
// Note: This is just for the rare cases where creation is _after_ today
|
||||
|
||||
// set it to creation date +1 day (don't set it to creationCal, it would break crash
|
||||
// datePicker.setMinDate() execution with IllegalArgumentException
|
||||
Calendar creationCalPlusOne = (Calendar) creationCal.clone();
|
||||
creationCalPlusOne.add(Calendar.DAY_OF_YEAR, 1);
|
||||
datePicker.init(
|
||||
creationCalPlusOne.get(Calendar.YEAR),
|
||||
creationCalPlusOne.get(Calendar.MONTH),
|
||||
creationCalPlusOne.get(Calendar.DAY_OF_MONTH),
|
||||
null
|
||||
);
|
||||
|
||||
} else {
|
||||
// normally, just init with today
|
||||
datePicker.init(
|
||||
todayCal.get(Calendar.YEAR),
|
||||
todayCal.get(Calendar.MONTH),
|
||||
todayCal.get(Calendar.DAY_OF_MONTH),
|
||||
null
|
||||
);
|
||||
}
|
||||
} else {
|
||||
noExpiry.setChecked(false);
|
||||
datePicker.setVisibility(View.VISIBLE);
|
||||
|
||||
// set date picker to current expiry
|
||||
datePicker.init(
|
||||
expiryCal.get(Calendar.YEAR),
|
||||
expiryCal.get(Calendar.MONTH),
|
||||
expiryCal.get(Calendar.DAY_OF_MONTH),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
// will crash with IllegalArgumentException if we set a min date
|
||||
// that is not before expiry
|
||||
if (creationCal.before(mExpiryCal)) {
|
||||
mDatePicker.setMinDate(creationCal.getTime().getTime()
|
||||
+ DateUtils.DAY_IN_MILLIS);
|
||||
} else {
|
||||
// when creation date isn't available
|
||||
mDatePicker.setMinDate(mExpiryCal.getTime().getTime()
|
||||
+ DateUtils.DAY_IN_MILLIS);
|
||||
}
|
||||
datePicker.setMinDate(creationCal.getTime().getTime());
|
||||
}
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@ -115,34 +160,28 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
//noinspection ResourceType
|
||||
selectedCal.set(mDatePicker.getYear(), mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
|
||||
|
||||
if (mExpiryCal != null) {
|
||||
long numDays = (selectedCal.getTimeInMillis() / 86400000)
|
||||
- (mExpiryCal.getTimeInMillis() / 86400000);
|
||||
if (numDays > 0) {
|
||||
Bundle data = new Bundle();
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime().getTime() / 1000);
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
|
||||
}
|
||||
long expiry;
|
||||
if (noExpiry.isChecked()) {
|
||||
expiry = 0L;
|
||||
} else {
|
||||
Bundle data = new Bundle();
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime().getTime() / 1000);
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
|
||||
}
|
||||
}
|
||||
});
|
||||
Calendar selectedCal = Calendar.getInstance(TimeZone.getDefault());
|
||||
//noinspection ResourceType
|
||||
selectedCal.set(datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth());
|
||||
// date picker uses default time zone, we need to convert to UTC
|
||||
selectedCal.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
alert.setNeutralButton(R.string.btn_no_date, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
long numDays = (selectedCal.getTimeInMillis() / 86400000)
|
||||
- (expiryCal.getTimeInMillis() / 86400000);
|
||||
if (numDays <= 0) {
|
||||
Log.e(Constants.TAG, "Should not happen! Expiry num of days <= 0!");
|
||||
throw new RuntimeException();
|
||||
}
|
||||
expiry = selectedCal.getTime().getTime() / 1000;
|
||||
}
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, null);
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY, expiry);
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY, data);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -25,6 +25,7 @@ import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.DialogInterface.OnKeyListener;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@ -113,7 +114,12 @@ public class ProgressDialogFragment extends DialogFragment {
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
ProgressDialog dialog = new ProgressDialog(activity);
|
||||
// if the progress dialog is displayed from the application class, design is missing
|
||||
// hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
|
||||
ContextThemeWrapper context = new ContextThemeWrapper(activity,
|
||||
R.style.Theme_AppCompat_Light);
|
||||
|
||||
ProgressDialog dialog = new ProgressDialog(context);
|
||||
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
dialog.setCancelable(false);
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.AttributeSet;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
|
||||
public class CertifyKeySpinner extends KeySpinner {
|
||||
private long mHiddenMasterKeyId = Constants.key.none;
|
||||
|
||||
public CertifyKeySpinner(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CertifyKeySpinner(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CertifyKeySpinner(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public void setHiddenMasterKeyId(long hiddenMasterKeyId) {
|
||||
this.mHiddenMasterKeyId = hiddenMasterKeyId;
|
||||
reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader() {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
String[] projection = new String[]{
|
||||
KeychainContract.KeyRings._ID,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.KEY_ID,
|
||||
KeychainContract.KeyRings.USER_ID,
|
||||
KeychainContract.KeyRings.IS_EXPIRED,
|
||||
KeychainContract.KeyRings.HAS_CERTIFY,
|
||||
KeychainContract.KeyRings.HAS_ANY_SECRET
|
||||
};
|
||||
|
||||
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND "
|
||||
+ KeychainContract.KeyRings.HAS_CERTIFY + " NOT NULL AND "
|
||||
+ KeychainContract.KeyRings.IS_REVOKED + " = 0 AND "
|
||||
+ KeychainContract.KeyRings.IS_EXPIRED + " = 0 AND " + KeychainDatabase.Tables.KEYS + "."
|
||||
+ KeychainContract.KeyRings.MASTER_KEY_ID + " != " + mHiddenMasterKeyId;
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||
}
|
||||
}
|
@ -111,7 +111,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
if (getContext() instanceof FragmentActivity) {
|
||||
((FragmentActivity) getContext()).getSupportLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Cursor>() {
|
||||
((FragmentActivity) getContext()).getSupportLoaderManager().initLoader(hashCode(), null, new LoaderManager.LoaderCallbacks<Cursor>() {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// These are the rows that we will retrieve.
|
||||
@ -143,6 +143,8 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
|
||||
swapCursor(null);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.e(Constants.TAG, "EncryptKeyCompletionView must be attached to a FragmentActivity, this is " + getContext().getClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
@ -38,9 +38,7 @@ import org.sufficientlysecure.keychain.R;
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
custom:foldedLabel="@string/TEXT_TO_DISPLAY_WHEN_FOLDED"
|
||||
custom:unFoldedLabel="@string/TEXT_TO_DISPLAY_WHEN_UNFOLDED"
|
||||
custom:foldedIcon="ICON_NAME_FROM_FontAwesomeText_TO_USE_WHEN_FOLDED"
|
||||
custom:unFoldedIcon="ICON_NAME_FROM_FontAwesomeText_TO_USE_WHEN_UNFOLDED">
|
||||
custom:unFoldedLabel="@string/TEXT_TO_DISPLAY_WHEN_UNFOLDED">
|
||||
|
||||
<include layout="@layout/ELEMENTS_TO_BE_FOLDED"/>
|
||||
|
||||
@ -49,7 +47,7 @@ import org.sufficientlysecure.keychain.R;
|
||||
*/
|
||||
public class FoldableLinearLayout extends LinearLayout {
|
||||
|
||||
private ImageButton mFoldableIcon;
|
||||
private ImageView mFoldableIcon;
|
||||
private boolean mFolded;
|
||||
private boolean mHasMigrated = false;
|
||||
private Integer mShortAnimationDuration = null;
|
||||
@ -139,7 +137,7 @@ public class FoldableLinearLayout extends LinearLayout {
|
||||
}
|
||||
|
||||
private void initialiseInnerViews() {
|
||||
mFoldableIcon = (ImageButton) mFoldableLayout.findViewById(R.id.foldableIcon);
|
||||
mFoldableIcon = (ImageView) mFoldableLayout.findViewById(R.id.foldableIcon);
|
||||
mFoldableIcon.setImageResource(R.drawable.ic_action_expand);
|
||||
mFoldableTextView = (TextView) mFoldableLayout.findViewById(R.id.foldableText);
|
||||
mFoldableTextView.setText(mFoldedLabel);
|
||||
|
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public abstract class KeySpinner extends Spinner {
|
||||
public interface OnKeyChangedListener {
|
||||
public void onKeyChanged(long masterKeyId);
|
||||
}
|
||||
|
||||
private long mSelectedKeyId;
|
||||
private SelectKeyAdapter mAdapter = new SelectKeyAdapter();
|
||||
private OnKeyChangedListener mListener;
|
||||
|
||||
public KeySpinner(Context context) {
|
||||
super(context);
|
||||
initView();
|
||||
}
|
||||
|
||||
public KeySpinner(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView();
|
||||
}
|
||||
|
||||
public KeySpinner(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
setAdapter(mAdapter);
|
||||
super.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (mListener != null) {
|
||||
mListener.onKeyChanged(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
if (mListener != null) {
|
||||
mListener.onKeyChanged(Constants.key.none);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public abstract Loader<Cursor> onCreateLoader();
|
||||
|
||||
@Override
|
||||
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void setOnKeyChangedListener(OnKeyChangedListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
reload();
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
if (getContext() instanceof FragmentActivity) {
|
||||
((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(hashCode(), null, new LoaderManager.LoaderCallbacks<Cursor>() {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return KeySpinner.this.onCreateLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
mAdapter.swapCursor(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.e(Constants.TAG, "KeySpinner must be attached to FragmentActivity, this is " + getContext().getClass());
|
||||
}
|
||||
}
|
||||
|
||||
public long getSelectedKeyId() {
|
||||
return mSelectedKeyId;
|
||||
}
|
||||
|
||||
public void setSelectedKeyId(long selectedKeyId) {
|
||||
this.mSelectedKeyId = selectedKeyId;
|
||||
}
|
||||
|
||||
private class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {
|
||||
private CursorAdapter inner;
|
||||
private int mIndexUserId;
|
||||
private int mIndexKeyId;
|
||||
private int mIndexMasterKeyId;
|
||||
|
||||
public SelectKeyAdapter() {
|
||||
inner = new CursorAdapter(null, null, 0) {
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return View.inflate(getContext(), R.layout.keyspinner_key, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId));
|
||||
((TextView) view.findViewById(android.R.id.title)).setText(userId[2] == null ? userId[0] : (userId[0] + " (" + userId[2] + ")"));
|
||||
((TextView) view.findViewById(android.R.id.text1)).setText(userId[1]);
|
||||
((TextView) view.findViewById(android.R.id.text2)).setText(PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
try {
|
||||
return ((Cursor) getItem(position)).getLong(mIndexMasterKeyId);
|
||||
} catch (Exception e) {
|
||||
// This can happen on concurrent modification :(
|
||||
return Constants.key.none;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
if (newCursor == null) return inner.swapCursor(null);
|
||||
|
||||
mIndexKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.KEY_ID);
|
||||
mIndexUserId = newCursor.getColumnIndex(KeychainContract.KeyRings.USER_ID);
|
||||
mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID);
|
||||
if (newCursor.moveToFirst()) {
|
||||
do {
|
||||
if (newCursor.getLong(mIndexMasterKeyId) == mSelectedKeyId) {
|
||||
setSelection(newCursor.getPosition() + 1);
|
||||
}
|
||||
} while (newCursor.moveToNext());
|
||||
}
|
||||
return inner.swapCursor(newCursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return inner.getCount() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
if (position == 0) return null;
|
||||
return inner.getItem(position - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (position == 0) return Constants.key.none;
|
||||
return inner.getItemId(position - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
try {
|
||||
View v = getDropDownView(position, convertView, parent);
|
||||
v.findViewById(android.R.id.text1).setVisibility(View.GONE);
|
||||
return v;
|
||||
} catch (NullPointerException e) {
|
||||
// This is for the preview...
|
||||
return View.inflate(getContext(), android.R.layout.simple_list_item_1, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
||||
View v;
|
||||
if (position == 0) {
|
||||
if (convertView == null) {
|
||||
v = inner.newView(null, null, parent);
|
||||
} else {
|
||||
v = convertView;
|
||||
}
|
||||
((TextView) v.findViewById(android.R.id.title)).setText("None");
|
||||
v.findViewById(android.R.id.text1).setVisibility(View.GONE);
|
||||
v.findViewById(android.R.id.text2).setVisibility(View.GONE);
|
||||
} else {
|
||||
v = inner.getView(position - 1, convertView, parent);
|
||||
v.findViewById(android.R.id.text1).setVisibility(View.VISIBLE);
|
||||
v.findViewById(android.R.id.text2).setVisibility(View.VISIBLE);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
public class NoSwipeWrapContentViewPager extends android.support.v4.view.ViewPager {
|
||||
public NoSwipeWrapContentViewPager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public NoSwipeWrapContentViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
|
||||
int height;
|
||||
View child = getChildAt(getCurrentItem());
|
||||
if (child != null) {
|
||||
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
|
||||
height = child.getMeasuredHeight();
|
||||
} else {
|
||||
height = 0;
|
||||
}
|
||||
|
||||
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent arg0) {
|
||||
// Never allow swiping to switch between pages
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// Never allow swiping to switch between pages
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.AttributeSet;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
|
||||
public class SignKeySpinner extends KeySpinner {
|
||||
public SignKeySpinner(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SignKeySpinner(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SignKeySpinner(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader() {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
String[] projection = new String[]{
|
||||
KeychainContract.KeyRings._ID,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.KEY_ID,
|
||||
KeychainContract.KeyRings.USER_ID,
|
||||
KeychainContract.KeyRings.IS_EXPIRED,
|
||||
KeychainContract.KeyRings.HAS_SIGN,
|
||||
KeychainContract.KeyRings.HAS_ANY_SECRET
|
||||
};
|
||||
|
||||
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND " + KeychainContract.KeyRings.HAS_SIGN + " NOT NULL AND "
|
||||
+ KeychainContract.KeyRings.IS_REVOKED + " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0";
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||
}
|
||||
}
|
@ -50,7 +50,6 @@ public class AlgorithmNames {
|
||||
mEncryptionNames.put(PGPEncryptedData.TRIPLE_DES, "Triple DES");
|
||||
mEncryptionNames.put(PGPEncryptedData.IDEA, "IDEA");
|
||||
|
||||
mHashNames.put(HashAlgorithmTags.MD5, "MD5");
|
||||
mHashNames.put(HashAlgorithmTags.RIPEMD160, "RIPEMD-160");
|
||||
mHashNames.put(HashAlgorithmTags.SHA1, "SHA-1");
|
||||
mHashNames.put(HashAlgorithmTags.SHA224, "SHA-224");
|
||||
|
@ -17,21 +17,16 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
public class Choice {
|
||||
public class Choice <E> {
|
||||
private String mName;
|
||||
private int mId;
|
||||
private E mId;
|
||||
|
||||
public Choice() {
|
||||
mId = -1;
|
||||
mName = "";
|
||||
}
|
||||
|
||||
public Choice(int id, String name) {
|
||||
public Choice(E id, String name) {
|
||||
mId = id;
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
public E getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
|
@ -46,10 +46,11 @@ public class FileImportCache<E extends Parcelable> {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private static final String FILENAME = "key_import.pcl";
|
||||
private final String mFilename;
|
||||
|
||||
public FileImportCache(Context context) {
|
||||
this.mContext = context;
|
||||
public FileImportCache(Context context, String filename) {
|
||||
mContext = context;
|
||||
mFilename = filename;
|
||||
}
|
||||
|
||||
public void writeCache(ArrayList<E> selectedEntries) throws IOException {
|
||||
@ -64,7 +65,7 @@ public class FileImportCache<E extends Parcelable> {
|
||||
throw new IOException("cache dir is null!");
|
||||
}
|
||||
|
||||
File tempFile = new File(mContext.getCacheDir(), FILENAME);
|
||||
File tempFile = new File(mContext.getCacheDir(), mFilename);
|
||||
|
||||
DataOutputStream oos = new DataOutputStream(new FileOutputStream(tempFile));
|
||||
|
||||
@ -91,6 +92,10 @@ public class FileImportCache<E extends Parcelable> {
|
||||
}
|
||||
|
||||
public Iterator<E> readCache() throws IOException {
|
||||
return readCache(true);
|
||||
}
|
||||
|
||||
public Iterator<E> readCache(final boolean deleteAfterRead) throws IOException {
|
||||
|
||||
File cacheDir = mContext.getCacheDir();
|
||||
if (cacheDir == null) {
|
||||
@ -98,7 +103,7 @@ public class FileImportCache<E extends Parcelable> {
|
||||
throw new IOException("cache dir is null!");
|
||||
}
|
||||
|
||||
final File tempFile = new File(cacheDir, FILENAME);
|
||||
final File tempFile = new File(cacheDir, mFilename);
|
||||
final DataInputStream ois = new DataInputStream(new FileInputStream(tempFile));
|
||||
|
||||
return new Iterator<E>() {
|
||||
@ -165,7 +170,10 @@ public class FileImportCache<E extends Parcelable> {
|
||||
if (!closed) {
|
||||
try {
|
||||
ois.close();
|
||||
tempFile.delete();
|
||||
if (deleteAfterRead) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
tempFile.delete();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// nvm
|
||||
}
|
||||
@ -176,4 +184,17 @@ public class FileImportCache<E extends Parcelable> {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public boolean delete() throws IOException {
|
||||
|
||||
File cacheDir = mContext.getCacheDir();
|
||||
if (cacheDir == null) {
|
||||
// https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
|
||||
throw new IOException("cache dir is null!");
|
||||
}
|
||||
|
||||
final File tempFile = new File(cacheDir, mFilename);
|
||||
return tempFile.delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
|
||||
/** This is a simple variant of ProgressScaler which shows a fixed progress message, ignoring
|
||||
* the provided ones.
|
||||
*/
|
||||
public class ProgressFixedScaler extends ProgressScaler {
|
||||
|
||||
final int mResId;
|
||||
|
||||
public ProgressFixedScaler(Progressable wrapped, int from, int to, int max, int resId) {
|
||||
super(wrapped, from, to, max);
|
||||
mResId = resId;
|
||||
}
|
||||
|
||||
public void setProgress(int resourceId, int progress, int max) {
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(mResId, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(mResId, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,20 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal" >
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<android.support.v4.widget.FixedDrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<include layout="@layout/drawer_list"/>
|
||||
<include layout="@layout/drawer_list" />
|
||||
|
||||
</android.support.v4.widget.FixedDrawerLayout>
|
||||
|
||||
<include layout="@layout/api_apps_list_content"/>
|
||||
<include layout="@layout/api_apps_list_content" />
|
||||
|
||||
</FrameLayout>
|
@ -1,19 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v4.widget.FixedDrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<android.support.v4.widget.FixedDrawerLayout
|
||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/drawer_list"/>
|
||||
<include layout="@layout/drawer_list" />
|
||||
|
||||
</android.support.v4.widget.FixedDrawerLayout>
|
||||
|
||||
<include layout="@layout/decrypt_content"/>
|
||||
<include layout="@layout/decrypt_content" />
|
||||
|
||||
</FrameLayout>
|
@ -1,19 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v4.widget.FixedDrawerLayout
|
||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:fontawesometext="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/drawer_list"/>
|
||||
<include layout="@layout/drawer_list" />
|
||||
|
||||
</android.support.v4.widget.FixedDrawerLayout>
|
||||
|
||||
<include layout="@layout/encrypt_content"/>
|
||||
<include layout="@layout/encrypt_content" />
|
||||
</FrameLayout>
|
@ -1,18 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v4.widget.FixedDrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/drawer_list"/>
|
||||
<include layout="@layout/drawer_list" />
|
||||
|
||||
</android.support.v4.widget.FixedDrawerLayout>
|
||||
|
||||
<include layout="@layout/key_list_content"/>
|
||||
<include layout="@layout/key_list_content" />
|
||||
|
||||
</FrameLayout>
|
@ -34,7 +34,7 @@
|
||||
android:padding="4dp" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableRow android:id="@+id/add_subkey_row_size">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -50,6 +50,24 @@
|
||||
android:padding="4dp" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:id="@+id/add_subkey_row_curve"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/label_ecc_curve"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/add_subkey_curve"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right"
|
||||
android:padding="4dp"/>
|
||||
</TableRow>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/add_subkey_custom_key_size_label"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -59,9 +59,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
custom:foldedLabel="@string/api_settings_show_advanced"
|
||||
custom:unFoldedLabel="@string/api_settings_hide_advanced"
|
||||
custom:foldedIcon="fa-chevron-right"
|
||||
custom:unFoldedIcon="fa-chevron-down">
|
||||
custom:unFoldedLabel="@string/api_settings_hide_advanced">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
|
@ -40,9 +40,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
custom:foldedLabel="@string/api_settings_show_info"
|
||||
custom:unFoldedLabel="@string/api_settings_hide_info"
|
||||
custom:foldedIcon="fa-chevron-right"
|
||||
custom:unFoldedIcon="fa-chevron-down">
|
||||
custom:unFoldedLabel="@string/api_settings_hide_info">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
@ -67,7 +65,7 @@
|
||||
android:id="@+id/api_app_settings_package_signature"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Base64 encoded signature"
|
||||
android:text="Base64 encoded hash of signature"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout>
|
||||
|
@ -4,13 +4,13 @@
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<org.sufficientlysecure.htmltextview.HtmlTextView
|
||||
<TextView
|
||||
android:id="@+id/api_select_pub_keys_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:paddingBottom="0dip"
|
||||
android:text="Set in-code!"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<FrameLayout
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@ -8,6 +7,7 @@
|
||||
<include layout="@layout/notify_area" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/certify_scroll_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -26,14 +26,10 @@
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/section_certification_key" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/sign_key_select_key_fragment"
|
||||
android:name="org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment"
|
||||
<org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner
|
||||
android:id="@+id/certify_key_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginTop="4dp"
|
||||
tools:layout="@layout/select_secret_key_layout_fragment" />
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
style="@style/SectionHeader"
|
||||
@ -119,8 +115,7 @@
|
||||
<org.sufficientlysecure.keychain.ui.widget.FixedListView
|
||||
android:id="@+id/view_key_user_ids"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:descendantFocusability="blocksDescendants" />
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
style="@style/SectionHeader"
|
||||
@ -184,7 +179,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
@ -4,6 +4,16 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/edit_subkey_expiry_no_expiry"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:checked="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/btn_no_date" />
|
||||
|
||||
<DatePicker
|
||||
android:id="@+id/edit_subkey_expiry_date_picker"
|
||||
android:layout_gravity="center_horizontal"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user