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:
Dominik Schürmann
2014-08-28 11:00:18 +02:00
171 changed files with 9435 additions and 2107 deletions

View File

@@ -15,6 +15,6 @@ before_install:
- ./prepare-tests.sh - ./prepare-tests.sh
install: echo "Installation done" install: echo "Installation done"
script: script:
- gradle assemble -S -q - ./gradlew assemble -S -q
- gradle --info OpenKeychain-Test:testDebug - ./gradlew --info OpenKeychain-Test:testDebug

View File

@@ -8,6 +8,7 @@
* New icons to show status of key (by Brennan Novak) * New icons to show status of key (by Brennan Novak)
* Important bug fix: Importing of large key collections from a file is now possible * Important bug fix: Importing of large key collections from a file is now possible
* Notification showing cached passphrases * 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 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

View File

@@ -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; package org.sufficientlysecure.keychain.pgp;
import junit.framework.AssertionFailedError; import junit.framework.AssertionFailedError;
@@ -16,11 +34,13 @@ import org.spongycastle.bcpg.SecretSubkeyPacket;
import org.spongycastle.bcpg.SignaturePacket; import org.spongycastle.bcpg.SignaturePacket;
import org.spongycastle.bcpg.UserIDPacket; import org.spongycastle.bcpg.UserIDPacket;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; 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;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
import org.sufficientlysecure.keychain.support.KeyringBuilder; import org.sufficientlysecure.keychain.support.KeyringBuilder;
@@ -31,6 +51,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.security.Security;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
@@ -42,7 +63,7 @@ import java.util.Random;
public class PgpKeyOperationTest { public class PgpKeyOperationTest {
static UncachedKeyRing staticRing; static UncachedKeyRing staticRing;
static String passphrase; final static String passphrase = genPassphrase();
UncachedKeyRing ring; UncachedKeyRing ring;
PgpKeyOperation op; PgpKeyOperation op;
@@ -50,37 +71,29 @@ public class PgpKeyOperationTest {
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>(); ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
ArrayList<RawPacket> onlyB = 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; 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(); SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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( 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( 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("twi");
parcel.mAddUserIds.add("pink"); parcel.mAddUserIds.add("pink");
parcel.mNewPassphrase = passphrase; parcel.mNewPassphrase = passphrase;
PgpKeyOperation op = new PgpKeyOperation(null); 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 // we sleep here for a second, to make sure all new certificates have different timestamps
Thread.sleep(1000); Thread.sleep(1000);
@@ -107,57 +120,55 @@ public class PgpKeyOperationTest {
{ {
parcel.reset(); parcel.reset();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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.mAddUserIds.add("shy");
parcel.mNewPassphrase = passphrase; parcel.mNewPassphrase = passphrase;
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing(); assertFailure("creating ring with < 512 bytes keysize should fail", parcel,
LogType.MSG_CR_ERROR_KEYSIZE_512);
Assert.assertNull("creating ring with < 512 bytes keysize should fail", ring);
} }
{ {
parcel.reset(); parcel.reset();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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.mAddUserIds.add("shy");
parcel.mNewPassphrase = passphrase; parcel.mNewPassphrase = passphrase;
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing(); assertFailure("creating ring with ElGamal master key should fail", parcel,
LogType.MSG_CR_ERROR_FLAGS_ELGAMAL);
Assert.assertNull("creating ring with ElGamal master key should fail", ring);
} }
{ {
parcel.reset(); parcel.reset();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
12345, 1024, KeyFlags.CERTIFY_OTHER, null)); Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, null));
parcel.mAddUserIds.add("shy"); parcel.mAddUserIds.add("lotus");
parcel.mNewPassphrase = passphrase; parcel.mNewPassphrase = passphrase;
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing(); assertFailure("creating master key with null expiry should fail", parcel,
Assert.assertNull("creating ring with bad algorithm choice should fail", ring); LogType.MSG_CR_ERROR_NULL_EXPIRY);
} }
{ {
parcel.reset(); parcel.reset();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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.mAddUserIds.add("shy");
parcel.mNewPassphrase = passphrase; parcel.mNewPassphrase = passphrase;
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing(); assertFailure("creating ring with non-certifying master key should fail", parcel,
Assert.assertNull("creating ring with non-certifying master key should fail", ring); LogType.MSG_CR_ERROR_NO_CERTIFY);
} }
{ {
parcel.reset(); parcel.reset();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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; parcel.mNewPassphrase = passphrase;
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing(); assertFailure("creating ring without user ids should fail", parcel,
Assert.assertNull("creating ring without user ids should fail", ring); LogType.MSG_CR_ERROR_NO_USER_ID);
} }
{ {
@@ -165,8 +176,8 @@ public class PgpKeyOperationTest {
parcel.mAddUserIds.add("shy"); parcel.mAddUserIds.add("shy");
parcel.mNewPassphrase = passphrase; parcel.mNewPassphrase = passphrase;
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing(); assertFailure("creating ring with no master key should fail", parcel,
Assert.assertNull("creating ring without subkeys should fail", ring); LogType.MSG_CR_ERROR_NO_MASTER);
} }
} }
@@ -177,9 +188,9 @@ public class PgpKeyOperationTest {
public void testMasterFlags() throws Exception { public void testMasterFlags() throws Exception {
SaveKeyringParcel parcel = new SaveKeyringParcel(); SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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"); 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", Assert.assertEquals("the keyring should contain only the master key",
1, KeyringTestingHelper.itToList(ring.getPublicKeys()).size()); 1, KeyringTestingHelper.itToList(ring.getPublicKeys()).size());
@@ -239,10 +250,8 @@ public class PgpKeyOperationTest {
parcel.mMasterKeyId = ring.getMasterKeyId() -1; parcel.mMasterKeyId = ring.getMasterKeyId() -1;
parcel.mFingerprint = ring.getFingerprint(); parcel.mFingerprint = ring.getFingerprint();
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); assertModifyFailure("keyring modification with bad master key id should fail",
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); ring, parcel, LogType.MSG_MF_ERROR_KEYID);
Assert.assertNull("keyring modification with bad master key id should fail", modified);
} }
{ {
@@ -251,10 +260,8 @@ public class PgpKeyOperationTest {
parcel.mMasterKeyId = null; parcel.mMasterKeyId = null;
parcel.mFingerprint = ring.getFingerprint(); parcel.mFingerprint = ring.getFingerprint();
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); assertModifyFailure("keyring modification with null master key id should fail",
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); ring, parcel, LogType.MSG_MF_ERROR_KEYID);
Assert.assertNull("keyring modification with null master key id should fail", modified);
} }
{ {
@@ -264,10 +271,8 @@ public class PgpKeyOperationTest {
// some byte, off by one // some byte, off by one
parcel.mFingerprint[5] += 1; parcel.mFingerprint[5] += 1;
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); assertModifyFailure("keyring modification with bad fingerprint should fail",
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); ring, parcel, LogType.MSG_MF_ERROR_FINGERPRINT);
Assert.assertNull("keyring modification with bad fingerprint should fail", modified);
} }
{ {
@@ -275,10 +280,8 @@ public class PgpKeyOperationTest {
parcel.mMasterKeyId = ring.getMasterKeyId(); parcel.mMasterKeyId = ring.getMasterKeyId();
parcel.mFingerprint = null; parcel.mFingerprint = null;
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); assertModifyFailure("keyring modification with null fingerprint should fail",
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); ring, parcel, LogType.MSG_MF_ERROR_FINGERPRINT);
Assert.assertNull("keyring modification with null fingerprint should fail", modified);
} }
{ {
@@ -286,10 +289,9 @@ public class PgpKeyOperationTest {
if (badphrase.equals(passphrase)) { if (badphrase.equals(passphrase)) {
badphrase = "a"; 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; long expiry = new Date().getTime() / 1000 + 159;
int flags = KeyFlags.SIGN_DATA; int flags = KeyFlags.SIGN_DATA;
int bits = 1024 + new Random().nextInt(8); 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); UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
@@ -336,28 +338,32 @@ public class PgpKeyOperationTest {
Assert.assertEquals("added key must have expected flags", Assert.assertEquals("added key must have expected flags",
flags, newKey.getKeyUsage()); flags, newKey.getKeyUsage());
Assert.assertEquals("added key must have expected bitsize", Assert.assertEquals("added key must have expected bitsize",
bits, newKey.getBitStrength()); bits, (int) newKey.getBitStrength());
{ // bad keysize should fail { // bad keysize should fail
parcel.reset(); parcel.reset();
parcel.mAddSubKeys.add(new SubkeyAdd( 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 { // a past expiry should fail
parcel.reset(); 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)); new Date().getTime()/1000-10));
assertModifyFailure("creating subkey with past expiry date should fail", ring, parcel,
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); LogType.MSG_MF_ERROR_PAST_EXPIRY);
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
Assert.assertNull("creating subkey with past expiry date should fail", modified);
} }
} }
@@ -394,6 +400,20 @@ public class PgpKeyOperationTest {
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage()); 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; int flags = KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_COMMS;
parcel.reset(); parcel.reset();
@@ -418,28 +438,171 @@ public class PgpKeyOperationTest {
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000); 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 { // a past expiry should fail
parcel.reset(); parcel.reset();
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, new Date().getTime()/1000-10)); parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, new Date().getTime()/1000-10));
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); assertModifyFailure("setting subkey expiry to a past date should fail", ring, parcel,
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); LogType.MSG_MF_ERROR_PAST_EXPIRY);
Assert.assertNull("setting subkey expiry to a past date should fail", modified);
} }
{ // modifying nonexistent keyring should fail { // modifying nonexistent subkey should fail
parcel.reset(); parcel.reset();
parcel.mChangeSubKeys.add(new SubkeyChange(123, null, null)); parcel.mChangeSubKeys.add(new SubkeyChange(123, null, null));
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); assertModifyFailure("modifying non-existent subkey should fail", ring, parcel,
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); LogType.MSG_MF_ERROR_SUBKEY_MISSING);
Assert.assertNull("modifying non-existent subkey should fail", modified);
} }
} }
@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 @Test
public void testSubkeyRevoke() throws Exception { public void testSubkeyRevoke() throws Exception {
@@ -555,10 +718,8 @@ public class PgpKeyOperationTest {
parcel.reset(); parcel.reset();
parcel.mChangePrimaryUserId = uid; parcel.mChangePrimaryUserId = uid;
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0); assertModifyFailure("setting primary user id to a revoked user id should fail", modified, parcel,
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); LogType.MSG_MF_ERROR_REVOKED_PRIMARY);
Assert.assertNull("setting primary user id to a revoked user id should fail", otherModified);
} }
@@ -596,6 +757,14 @@ public class PgpKeyOperationTest {
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID()); 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 @Test
@@ -603,9 +772,8 @@ public class PgpKeyOperationTest {
{ {
parcel.mAddUserIds.add(""); parcel.mAddUserIds.add("");
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); assertModifyFailure("adding an empty user id should fail", ring, parcel,
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); LogType.MSG_MF_UID_ERROR_EMPTY);
Assert.assertNull("adding an empty user id should fail", modified);
} }
parcel.reset(); parcel.reset();
@@ -673,22 +841,87 @@ public class PgpKeyOperationTest {
parcel.mChangePrimaryUserId += "A"; parcel.mChangePrimaryUserId += "A";
} }
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); assertModifyFailure("changing primary user id to a non-existent one should fail",
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); ring, parcel, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY);
Assert.assertNull("changing primary user id to a non-existent one should fail", modified);
} }
// check for revoked primary user id already done in revoke test // 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, private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
UncachedKeyRing ring, UncachedKeyRing ring,
ArrayList<RawPacket> onlyA, ArrayList<RawPacket> onlyA,
ArrayList<RawPacket> onlyB) { 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 // applies a parcel modification while running some integrity checks
@@ -696,6 +929,7 @@ public class PgpKeyOperationTest {
UncachedKeyRing ring, UncachedKeyRing ring,
ArrayList<RawPacket> onlyA, ArrayList<RawPacket> onlyA,
ArrayList<RawPacket> onlyB, ArrayList<RawPacket> onlyB,
String passphrase,
boolean canonicalize, boolean canonicalize,
boolean constantCanonicalize) { boolean constantCanonicalize) {
@@ -705,8 +939,10 @@ public class PgpKeyOperationTest {
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
PgpKeyOperation op = new PgpKeyOperation(null); PgpKeyOperation op = new PgpKeyOperation(null);
UncachedKeyRing rawModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase);
Assert.assertNotNull("key modification failed", rawModified); Assert.assertTrue("key modification must succeed", result.success());
UncachedKeyRing rawModified = result.getRing();
Assert.assertNotNull("key modification must not return null", rawModified);
if (!canonicalize) { if (!canonicalize) {
Assert.assertTrue("keyring must differ from original", KeyringTestingHelper.diffKeyrings( Assert.assertTrue("keyring must differ from original", KeyringTestingHelper.diffKeyrings(
@@ -758,5 +994,70 @@ public class PgpKeyOperationTest {
Assert.assertEquals(java.util.Arrays.toString(expected), java.util.Arrays.toString(actual)); 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();
}
} }

View File

@@ -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; package org.sufficientlysecure.keychain.pgp;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@@ -13,6 +31,7 @@ import org.spongycastle.bcpg.PacketTags;
import org.spongycastle.bcpg.UserIDPacket; import org.spongycastle.bcpg.UserIDPacket;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey; 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.OperationResultParcel;
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult; import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; 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;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.security.Security;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
@@ -60,15 +81,16 @@ public class UncachedKeyringCanonicalizeTest {
@BeforeClass @BeforeClass
public static void setUpOnce() throws Exception { public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
ShadowLog.stream = System.out; ShadowLog.stream = System.out;
SaveKeyringParcel parcel = new SaveKeyringParcel(); SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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( 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( 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("twi");
parcel.mAddUserIds.add("pink"); parcel.mAddUserIds.add("pink");
@@ -277,7 +299,7 @@ public class UncachedKeyringCanonicalizeTest {
SaveKeyringParcel parcel = new SaveKeyringParcel(); SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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"); parcel.mAddUserIds.add("trix");
PgpKeyOperation op = new PgpKeyOperation(null); PgpKeyOperation op = new PgpKeyOperation(null);

View File

@@ -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; package org.sufficientlysecure.keychain.pgp;
import org.junit.Assert; import org.junit.Assert;
@@ -10,14 +28,17 @@ import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.PacketTags; import org.spongycastle.bcpg.PacketTags;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.service.OperationResultParcel; import org.sufficientlysecure.keychain.service.OperationResultParcel;
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult; import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; 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;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.security.Security;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
@@ -59,14 +80,15 @@ public class UncachedKeyringMergeTest {
@BeforeClass @BeforeClass
public static void setUpOnce() throws Exception { public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
ShadowLog.stream = System.out; ShadowLog.stream = System.out;
{ {
SaveKeyringParcel parcel = new SaveKeyringParcel(); SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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( 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("twi");
parcel.mAddUserIds.add("pink"); parcel.mAddUserIds.add("pink");
@@ -83,7 +105,7 @@ public class UncachedKeyringMergeTest {
{ {
SaveKeyringParcel parcel = new SaveKeyringParcel(); SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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"); parcel.mAddUserIds.add("shy");
// passphrase is tested in PgpKeyOperationTest, just use empty here // passphrase is tested in PgpKeyOperationTest, just use empty here
@@ -189,7 +211,7 @@ public class UncachedKeyringMergeTest {
parcel.reset(); parcel.reset();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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(); modifiedA = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
modifiedB = op.modifySecretKeyRing(secretRing, parcel, "").getRing(); modifiedB = op.modifySecretKeyRing(secretRing, parcel, "").getRing();

View File

@@ -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; package org.sufficientlysecure.keychain.pgp;
import org.junit.Assert; import org.junit.Assert;
@@ -13,6 +31,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult; import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -37,11 +56,11 @@ public class UncachedKeyringTest {
SaveKeyringParcel parcel = new SaveKeyringParcel(); SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( 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( 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( 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("twi");
parcel.mAddUserIds.add("pink"); parcel.mAddUserIds.add("pink");

View File

@@ -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();
}
}

View File

@@ -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 * 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 * 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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.support; package org.sufficientlysecure.keychain.support;
import android.content.Context; import android.content.Context;

View File

@@ -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; package org.sufficientlysecure.keychain.util;
import android.os.Bundle; import android.os.Bundle;
@@ -25,7 +43,7 @@ public class FileImportCacheTest {
@Test @Test
public void testInputOutput() throws Exception { 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>(); ArrayList<Bundle> list = new ArrayList<Bundle>();

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -0,0 +1,3 @@
"Cooperpair" testcase under public domain license, by @coruus:
https://github.com/coruus/cooperpair/tree/master/pgpv4

View File

@@ -3,8 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="org.sufficientlysecure.keychain" package="org.sufficientlysecure.keychain"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="27000" android:versionCode="28000"
android:versionName="2.7"> android:versionName="2.8">
<!-- <!--
General remarks General remarks
@@ -176,7 +176,7 @@
android:label="@string/title_decrypt" android:label="@string/title_decrypt"
android:windowSoftInputMode="stateHidden"> 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"> <intent-filter android:label="@string/intent_send_decrypt">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -184,7 +184,9 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<!-- mime type as defined in http://tools.ietf.org/html/rfc3156 --> <!-- 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> </intent-filter>
<!-- Keychain's own Actions --> <!-- Keychain's own Actions -->
<!-- DECRYPT with text as extra --> <!-- DECRYPT with text as extra -->
@@ -556,6 +558,10 @@
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.KeyListActivity" /> android:value=".ui.KeyListActivity" />
</activity> </activity>
<activity
android:name=".ui.ConsolidateDialogActivity"
android:theme="@android:style/Theme.NoDisplay"
android:label="@string/app_name" />
<activity <activity
android:name=".ui.HelpActivity" android:name=".ui.HelpActivity"
android:label="@string/title_help" /> android:label="@string/title_help" />
@@ -628,7 +634,10 @@
android:resource="@xml/account_desc" /> android:resource="@xml/account_desc" />
</service> </service>
<service android:name=".service.ContactSyncAdapterService"> <service
android:name=".service.ContactSyncAdapterService"
android:exported="true"
android:process=":sync">
<intent-filter> <intent-filter>
<action android:name="android.content.SyncAdapter" /> <action android:name="android.content.SyncAdapter" />
</intent-filter> </intent-filter>

View File

@@ -35,6 +35,9 @@ public final class Constants {
public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain"; 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 // as defined in http://tools.ietf.org/html/rfc3156, section 7
public static final String NFC_MIME = "application/pgp-keys"; 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 KEY_SERVERS_DEFAULT_VERSION = "keyServersDefaultVersion";
public static final String WRITE_VERSION_HEADER = "writeVersionHeader"; public static final String WRITE_VERSION_HEADER = "writeVersionHeader";
public static final String FIRST_TIME = "firstTime"; 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 class Defaults {
public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, subkeys.pgp.net, hkps://pgp.mit.edu"; public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, hkps://pgp.mit.edu";
public static final int KEY_SERVERS_VERSION = 2; public static final int KEY_SERVERS_VERSION = 3;
} }
public static final class DrawerItems { public static final class DrawerItems {

View File

@@ -20,15 +20,20 @@ package org.sufficientlysecure.keychain;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.app.Application; import android.app.Application;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.provider.ContactsContract;
import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.helper.TlsHelper; import org.sufficientlysecure.keychain.helper.TlsHelper;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PRNGFixes; 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"); TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
TemporaryStorageProvider.cleanUp(this); 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) { public static void setupAccountAsNeeded(Context context) {
// 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); AccountManager manager = AccountManager.get(context);
Account[] accounts = manager.getAccountsByType(Constants.PACKAGE_NAME); Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE);
if (accounts == null || accounts.length == 0) { if (accounts == null || accounts.length == 0) {
Account dummy = new Account(context.getString(R.string.app_name), Constants.PACKAGE_NAME); Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
manager.addAccountExplicitly(dummy, null, null); 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!");
}
}
} }
} }

View File

@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.helper;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.annotation.TargetApi;
import android.content.ContentProviderOperation; import android.content.ContentProviderOperation;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentUris; import android.content.ContentUris;
@@ -234,6 +235,25 @@ public class ContactHelper {
return new ArrayList<String>(mails); 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) { public static Uri dataUriFromContactUri(Context context, Uri contactUri) {
Cursor contactMasterKey = context.getContentResolver().query(contactUri, Cursor contactMasterKey = context.getContentResolver().query(contactUri,
new String[]{ContactsContract.Data.DATA2}, null, null, null, null); 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 // Delete fingerprints that are no longer present in OK
for (String fingerprint : contactFingerprints) { for (String fingerprint : contactFingerprints) {
resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, 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) { private static Set<String> getRawContactFingerprints(ContentResolver resolver) {
HashSet<String> result = new HashSet<String>(); HashSet<String> result = new HashSet<String>();
Cursor fingerprints = resolver.query(ContactsContract.RawContacts.CONTENT_URI, SOURCE_ID_PROJECTION, 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) { if (fingerprints != null) {
while (fingerprints.moveToNext()) { while (fingerprints.moveToNext()) {
result.add(fingerprints.getString(0)); result.add(fingerprints.getString(0));
@@ -349,10 +369,11 @@ public class ContactHelper {
* *
* @return raw contact id or -1 if not found * @return raw contact id or -1 if not found
*/ */
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private static int findRawContactId(ContentResolver resolver, String fingerprint) { private static int findRawContactId(ContentResolver resolver, String fingerprint) {
int rawContactId = -1; int rawContactId = -1;
Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, ID_PROJECTION, 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 != null) {
if (raw.moveToNext()) { if (raw.moveToNext()) {
rawContactId = raw.getInt(0); rawContactId = raw.getInt(0);
@@ -367,8 +388,8 @@ public class ContactHelper {
*/ */
private static void insertContact(ArrayList<ContentProviderOperation> ops, Context context, String fingerprint) { private static void insertContact(ArrayList<ContentProviderOperation> ops, Context context, String fingerprint) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, context.getString(R.string.app_name)) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.PACKAGE_NAME) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE)
.withValue(ContactsContract.RawContacts.SOURCE_ID, fingerprint) .withValue(ContactsContract.RawContacts.SOURCE_ID, fingerprint)
.build()); .build());
} }

View File

@@ -25,7 +25,12 @@ import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Constants; 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; import java.util.Vector;
/** /**
@@ -130,6 +135,36 @@ public class Preferences {
editor.commit(); 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() { public boolean isFirstTime() {
return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true); return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
} }
@@ -175,15 +210,24 @@ public class Preferences {
// migrate keyserver to hkps // migrate keyserver to hkps
if (mSharedPreferences.getInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, 0) != if (mSharedPreferences.getInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, 0) !=
Constants.Defaults.KEY_SERVERS_VERSION) { Constants.Defaults.KEY_SERVERS_VERSION) {
String[] servers = getKeyServers(); String[] serversArray = getKeyServers();
for (int i = 0; i < servers.length; i++) { ArrayList<String> servers = new ArrayList<String>(Arrays.asList(serversArray));
if (servers[i].equals("pool.sks-keyservers.net")) { ListIterator<String> it = servers.listIterator();
servers[i] = "hkps://hkps.pool.sks-keyservers.net"; while (it.hasNext()) {
} else if (servers[i].equals("pgp.mit.edu")) { String server = it.next();
servers[i] = "hkps://pgp.mit.edu"; 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() mSharedPreferences.edit()
.putInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, Constants.Defaults.KEY_SERVERS_VERSION) .putInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, Constants.Defaults.KEY_SERVERS_VERSION)
.commit(); .commit();
@@ -193,6 +237,11 @@ public class Preferences {
if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, 0) == 0x21070001) { if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, 0) == 0x21070001) {
setDefaultFileCompression(CompressionAlgorithmTags.UNCOMPRESSED); 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) { public void setWriteVersionHeader(boolean conceal) {

View File

@@ -24,8 +24,10 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.io.DataOutputStream; import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
@@ -281,7 +283,7 @@ public class HkpKeyserver extends Keyserver {
entry.setBitStrength(Integer.parseInt(matcher.group(3))); entry.setBitStrength(Integer.parseInt(matcher.group(3)));
final int algorithmId = Integer.decode(matcher.group(2)); 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 // group 1 contains the full fingerprint (v4) or the long key id if available
// see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr // see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
@@ -352,25 +354,38 @@ public class HkpKeyserver extends Keyserver {
@Override @Override
public void add(String armoredKey) throws AddKeyException { public void add(String armoredKey) throws AddKeyException {
try { try {
String query = getUrlPrefix() + mHost + ":" + mPort + "/pks/add"; String request = "/pks/add";
String params; String params;
try { try {
params = "keytext=" + URLEncoder.encode(armoredKey, "utf8"); params = "keytext=" + URLEncoder.encode(armoredKey, "UTF-8");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new AddKeyException(); 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)); Log.d(Constants.TAG, "hkp keyserver add: " + url.toString());
connection.setRequestMethod("POST"); Log.d(Constants.TAG, "params: " + params);
connection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length)); HttpURLConnection conn = openConnection(url);
connection.setDoOutput(true); conn.setRequestMethod("POST");
DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
wr.writeBytes(params); conn.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length));
wr.flush(); conn.setDoInput(true);
wr.close(); 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) { } catch (IOException e) {
Log.e(Constants.TAG, "IOException", e);
throw new AddKeyException(); throw new AddKeyException();
} }
} }

View File

@@ -39,7 +39,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
private boolean mExpired; private boolean mExpired;
private Date mDate; // TODO: not displayed private Date mDate; // TODO: not displayed
private String mFingerprintHex; private String mFingerprintHex;
private int mBitStrength; private Integer mBitStrength;
private String mCurveOid;
private String mAlgorithm; private String mAlgorithm;
private boolean mSecretKey; private boolean mSecretKey;
private String mPrimaryUserId; private String mPrimaryUserId;
@@ -162,10 +163,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
this.mFingerprintHex = fingerprintHex; this.mFingerprintHex = fingerprintHex;
} }
public int getBitStrength() { public Integer getBitStrength() {
return mBitStrength; return mBitStrength;
} }
public String getCurveOid() {
return mCurveOid;
}
public void setBitStrength(int bitStrength) { public void setBitStrength(int bitStrength) {
this.mBitStrength = bitStrength; this.mBitStrength = bitStrength;
} }
@@ -258,13 +263,15 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mPrimaryUserId = mUserIds.get(0); mPrimaryUserId = mUserIds.get(0);
} }
this.mKeyId = key.getKeyId(); mKeyId = key.getKeyId();
this.mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId); mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId);
this.mRevoked = key.isRevoked(); mRevoked = key.isRevoked();
this.mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint());
this.mBitStrength = key.getBitStrength(); mBitStrength = key.getBitStrength();
mCurveOid = key.getCurveOid();
final int algorithm = key.getAlgorithm(); final int algorithm = key.getAlgorithm();
this.mAlgorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm); mAlgorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm, mBitStrength, mCurveOid);
} }
} }

View File

@@ -75,7 +75,7 @@ public class KeybaseKeyserver extends Keyserver {
entry.setExtraData(username); entry.setExtraData(username);
final int algorithmId = match.getAlgorithmId(); final int algorithmId = match.getAlgorithmId();
entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId, null, null));
final int bitStrength = match.getBitStrength(); final int bitStrength = match.getBitStrength();
entry.setBitStrength(bitStrength); entry.setBitStrength(bitStrength);

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -60,10 +61,6 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
return mRing; return mRing;
} }
public void encode(ArmoredOutputStream stream) throws IOException {
getRing().encode(stream);
}
/** Getter that returns the subkey that should be used for signing. */ /** Getter that returns the subkey that should be used for signing. */
CanonicalizedPublicKey getEncryptionSubKey() throws PgpGeneralException { CanonicalizedPublicKey getEncryptionSubKey() throws PgpGeneralException {
PGPPublicKey key = getRing().getPublicKey(getEncryptId()); PGPPublicKey key = getRing().getPublicKey(getEncryptId());

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -523,13 +523,7 @@ public class PgpDecryptVerify {
// update signature buffer if signature is also present // update signature buffer if signature is also present
if (signature != null) { if (signature != null) {
try {
signature.update(buffer, 0, length); signature.update(buffer, 0, length);
} catch (SignatureException e) {
Log.e(Constants.TAG, "SignatureException -> Not a valid signature!", e);
signatureResultBuilder.validSignature(false);
signature = null;
}
} }
alreadyWritten += length; alreadyWritten += length;

View File

@@ -29,7 +29,6 @@ import org.sufficientlysecure.keychain.util.Log;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.regex.Pattern; 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. * Deletes file securely by overwriting it with random data before deleting it.
* <p/> * <p/>

View File

@@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult; import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult; import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
@@ -43,6 +44,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
public class PgpImportExport { public class PgpImportExport {
@@ -60,10 +62,14 @@ public class PgpImportExport {
private ProviderHelper mProviderHelper; private ProviderHelper mProviderHelper;
public PgpImportExport(Context context, Progressable progressable) { public PgpImportExport(Context context, Progressable progressable) {
this(context, new ProviderHelper(context), progressable);
}
public PgpImportExport(Context context, ProviderHelper providerHelper, Progressable progressable) {
super(); super();
this.mContext = context; this.mContext = context;
this.mProgressable = progressable; this.mProgressable = progressable;
this.mProviderHelper = new ProviderHelper(context); this.mProviderHelper = providerHelper;
} }
public PgpImportExport(Context context, 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(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = null; ArmoredOutputStream aos = null;
try { try {
@@ -103,13 +109,9 @@ public class PgpImportExport {
String armoredKey = bos.toString("UTF-8"); String armoredKey = bos.toString("UTF-8");
server.add(armoredKey); server.add(armoredKey);
return true;
} catch (IOException e) { } catch (IOException e) {
return false; Log.e(Constants.TAG, "IOException", e);
} catch (AddKeyException e) { throw new AddKeyException();
// TODO: tell the user?
return false;
} finally { } finally {
try { try {
if (aos != null) { if (aos != null) {
@@ -124,20 +126,23 @@ public class PgpImportExport {
/** Imports keys from given data. If keyIds is given only those are imported */ /** Imports keys from given data. If keyIds is given only those are imported */
public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries) { 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); updateProgress(R.string.progress_importing, 0, 100);
// If there aren't even any keys, do nothing here. // If there aren't even any keys, do nothing here.
if (entries == null || entries.size() == 0) { if (entries == null || !entries.hasNext()) {
return new ImportKeyResult( 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 position = 0;
int progSteps = 100 / entries.size(); double progSteps = 100.0 / num;
for (ParcelableKeyRing entry : entries) { for (ParcelableKeyRing entry : new IterableIterator<ParcelableKeyRing>(entries)) {
try { try {
UncachedKeyRing key = UncachedKeyRing.decodeFromData(entry.getBytes()); UncachedKeyRing key = UncachedKeyRing.decodeFromData(entry.getBytes());
@@ -157,10 +162,10 @@ public class PgpImportExport {
SaveKeyringResult result; SaveKeyringResult result;
if (key.isSecret()) { if (key.isSecret()) {
result = mProviderHelper.saveSecretKeyRing(key, result = mProviderHelper.saveSecretKeyRing(key,
new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100)); new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100));
} else { } else {
result = mProviderHelper.savePublicKeyRing(key, 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()) { if (!result.success()) {
badKeys += 1; badKeys += 1;
@@ -168,6 +173,9 @@ public class PgpImportExport {
oldKeys += 1; oldKeys += 1;
} else { } else {
newKeys += 1; newKeys += 1;
if (key.isSecret()) {
secret += 1;
}
} }
} catch (IOException e) { } 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);
} }

View File

@@ -24,10 +24,16 @@ import android.text.Spannable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan; 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.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.util.encoders.Hex; import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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 org.sufficientlysecure.keychain.util.Log;
import java.security.DigestException; import java.security.DigestException;
@@ -37,18 +43,14 @@ import java.util.Locale;
public class PgpKeyHelper { public class PgpKeyHelper {
public static String getAlgorithmInfo(int algorithm) { public static String getAlgorithmInfo(int algorithm, Integer keySize, String oid) {
return getAlgorithmInfo(null, algorithm, 0); return getAlgorithmInfo(null, algorithm, keySize, oid);
}
public static String getAlgorithmInfo(Context context, int algorithm) {
return getAlgorithmInfo(context, algorithm, 0);
} }
/** /**
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a> * 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; String algorithmStr;
switch (algorithm) { switch (algorithm) {
@@ -69,10 +71,19 @@ public class PgpKeyHelper {
break; break;
} }
case PublicKeyAlgorithmTags.ECDSA: case PublicKeyAlgorithmTags.ECDSA: {
if (oid == null) {
return "ECDSA";
}
String oidName = PgpKeyHelper.getCurveInfo(context, oid);
return "ECDSA (" + oidName + ")";
}
case PublicKeyAlgorithmTags.ECDH: { case PublicKeyAlgorithmTags.ECDH: {
algorithmStr = "ECC"; if (oid == null) {
break; return "ECDH";
}
String oidName = PgpKeyHelper.getCurveInfo(context, oid);
return "ECDH (" + oidName + ")";
} }
default: { default: {
@@ -90,6 +101,106 @@ public class PgpKeyHelper {
return algorithmStr; 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 * Converts fingerprint to hex
* <p/> * <p/>

View File

@@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -20,12 +20,12 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.Features;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.spec.ElGamalParameterSpec; import org.spongycastle.jce.spec.ElGamalParameterSpec;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyFlags;
import org.spongycastle.openpgp.PGPKeyPair; import org.spongycastle.openpgp.PGPKeyPair;
import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKey;
@@ -34,7 +34,6 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
@@ -53,6 +52,8 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult; import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@@ -67,6 +68,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException; import java.security.NoSuchProviderException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.SignatureException; import java.security.SignatureException;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
@@ -84,15 +86,45 @@ import java.util.Stack;
public class PgpKeyOperation { public class PgpKeyOperation {
private Stack<Progressable> mProgress; private Stack<Progressable> mProgress;
// most preferred is first
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{ private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_256,
SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.CAST5, SymmetricKeyAlgorithmTags.AES_192,
SymmetricKeyAlgorithmTags.TRIPLE_DES}; SymmetricKeyAlgorithmTags.AES_128,
private static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{HashAlgorithmTags.SHA1, SymmetricKeyAlgorithmTags.CAST5
HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160}; };
private static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{
HashAlgorithmTags.SHA512,
HashAlgorithmTags.SHA384,
HashAlgorithmTags.SHA224,
HashAlgorithmTags.SHA256,
HashAlgorithmTags.RIPEMD160
};
private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{ private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{
CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, CompressionAlgorithmTags.ZLIB,
CompressionAlgorithmTags.ZIP}; CompressionAlgorithmTags.BZIP2,
CompressionAlgorithmTags.ZIP
};
/*
* Note: s2kcount is a number between 0 and 0xff that controls the
* number of times to iterate the password hash before use. More
* iterations are useful against offline attacks, as it takes more
* time to check each password. The actual number of iterations is
* rather complex, and also depends on the hash function in use.
* Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give
* you more iterations. As a rough rule of thumb, when using
* SHA256 as the hashing function, 0x10 gives you about 64
* iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0,
* or about 1 million iterations. The maximum you can go to is
* 0xff, or about 2 million iterations. I'll use 0xc0 as a
* default -- about 130,000 iterations.
*
* http://kbsriram.com/2013/01/generating-rsa-keys-with-bouncycastle.html
*/
private static final int SECRET_KEY_ENCRYPTOR_HASH_ALGO = HashAlgorithmTags.SHA512;
private static final int SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO = SymmetricKeyAlgorithmTags.AES_256;
private static final int SECRET_KEY_ENCRYPTOR_S2K_COUNT = 0x60;
public PgpKeyOperation(Progressable progress) { public PgpKeyOperation(Progressable progress) {
super(); super();
@@ -126,31 +158,65 @@ public class PgpKeyOperation {
mProgress.peek().setProgress(message, current, 100); mProgress.peek().setProgress(message, current, 100);
} }
private ECGenParameterSpec getEccParameterSpec(Curve curve) {
switch (curve) {
case NIST_P256: return new ECGenParameterSpec("P-256");
case NIST_P384: return new ECGenParameterSpec("P-384");
case NIST_P521: return new ECGenParameterSpec("P-521");
// @see SaveKeyringParcel
// case BRAINPOOL_P256: return new ECGenParameterSpec("brainpoolp256r1");
// case BRAINPOOL_P384: return new ECGenParameterSpec("brainpoolp384r1");
// case BRAINPOOL_P512: return new ECGenParameterSpec("brainpoolp512r1");
}
throw new RuntimeException("Invalid choice! (can't happen)");
}
/** Creates new secret key. */ /** Creates new secret key. */
private PGPKeyPair createKey(int algorithmChoice, int keySize, OperationLog log, int indent) { private PGPKeyPair createKey(SubkeyAdd add, OperationLog log, int indent) {
try { try {
if (keySize < 512) { // Some safety checks
if (add.mAlgorithm == Algorithm.ECDH || add.mAlgorithm == Algorithm.ECDSA) {
if (add.mCurve == null) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_CURVE, indent);
return null;
}
} else {
if (add.mKeySize == null) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_KEYSIZE, indent);
return null;
}
if (add.mKeySize < 512) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent); log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent);
return null; return null;
} }
}
int algorithm; int algorithm;
KeyPairGenerator keyGen; KeyPairGenerator keyGen;
switch (algorithmChoice) { switch (add.mAlgorithm) {
case PublicKeyAlgorithmTags.DSA: { case DSA: {
if ((add.mFlags & (PGPKeyFlags.CAN_ENCRYPT_COMMS | PGPKeyFlags.CAN_ENCRYPT_STORAGE)) > 0) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_DSA, indent);
return null;
}
progress(R.string.progress_generating_dsa, 30); progress(R.string.progress_generating_dsa, 30);
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(keySize, new SecureRandom()); keyGen.initialize(add.mKeySize, new SecureRandom());
algorithm = PGPPublicKey.DSA; algorithm = PGPPublicKey.DSA;
break; break;
} }
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: { case ELGAMAL: {
if ((add.mFlags & (PGPKeyFlags.CAN_SIGN | PGPKeyFlags.CAN_CERTIFY)) > 0) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_ELGAMAL, indent);
return null;
}
progress(R.string.progress_generating_elgamal, 30); progress(R.string.progress_generating_elgamal, 30);
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME); keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
BigInteger p = Primes.getBestPrime(keySize); BigInteger p = Primes.getBestPrime(add.mKeySize);
BigInteger g = new BigInteger("2"); BigInteger g = new BigInteger("2");
ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g);
@@ -160,15 +226,44 @@ public class PgpKeyOperation {
break; break;
} }
case PublicKeyAlgorithmTags.RSA_GENERAL: { case RSA: {
progress(R.string.progress_generating_rsa, 30); progress(R.string.progress_generating_rsa, 30);
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(keySize, new SecureRandom()); keyGen.initialize(add.mKeySize, new SecureRandom());
algorithm = PGPPublicKey.RSA_GENERAL; algorithm = PGPPublicKey.RSA_GENERAL;
break; break;
} }
case ECDSA: {
if ((add.mFlags & (PGPKeyFlags.CAN_ENCRYPT_COMMS | PGPKeyFlags.CAN_ENCRYPT_STORAGE)) > 0) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_ECDSA, indent);
return null;
}
progress(R.string.progress_generating_ecdsa, 30);
ECGenParameterSpec ecParamSpec = getEccParameterSpec(add.mCurve);
keyGen = KeyPairGenerator.getInstance("ECDSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(ecParamSpec, new SecureRandom());
algorithm = PGPPublicKey.ECDSA;
break;
}
case ECDH: {
// make sure there are no sign or certify flags set
if ((add.mFlags & (PGPKeyFlags.CAN_SIGN | PGPKeyFlags.CAN_CERTIFY)) > 0) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_ECDH, indent);
return null;
}
progress(R.string.progress_generating_ecdh, 30);
ECGenParameterSpec ecParamSpec = getEccParameterSpec(add.mCurve);
keyGen = KeyPairGenerator.getInstance("ECDH", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(ecParamSpec, new SecureRandom());
algorithm = PGPPublicKey.ECDH;
break;
}
default: { default: {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent); log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
return null; return null;
@@ -181,7 +276,8 @@ public class PgpKeyOperation {
} catch(NoSuchProviderException e) { } catch(NoSuchProviderException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} catch(NoSuchAlgorithmException e) { } catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e); log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
return null;
} catch(InvalidAlgorithmParameterException e) { } catch(InvalidAlgorithmParameterException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} catch(PGPException e) { } catch(PGPException e) {
@@ -218,13 +314,13 @@ public class PgpKeyOperation {
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} }
if (add.mAlgorithm == PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT) { if (add.mExpiry == null) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent); log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NULL_EXPIRY, indent);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} }
subProgressPush(10, 30); subProgressPush(10, 30);
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent); PGPKeyPair keyPair = createKey(add, log, indent);
subProgressPop(); subProgressPop();
// return null if this failed (an error will already have been logged by createKey) // return null if this failed (an error will already have been logged by createKey)
@@ -234,13 +330,16 @@ public class PgpKeyOperation {
progress(R.string.progress_building_master_key, 40); progress(R.string.progress_building_master_key, 40);
// define hashing and signing algos // Build key encrypter and decrypter based on passphrase
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(SECRET_KEY_ENCRYPTOR_HASH_ALGO);
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
// NOTE: only SHA1 is supported for key checksum calculations.
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(HashAlgorithmTags.SHA1); .build().get(HashAlgorithmTags.SHA1);
// Build key encrypter and decrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
PGPSecretKey masterSecretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), PGPSecretKey masterSecretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, true, keyEncryptor); sha1Calc, true, keyEncryptor);
@@ -248,7 +347,7 @@ public class PgpKeyOperation {
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator()); masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
subProgressPush(50, 100); subProgressPush(50, 100);
return internal(sKR, masterSecretKey, add.mFlags, saveParcel, "", log); return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, "", log);
} catch (PGPException e) { } catch (PGPException e) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
@@ -314,14 +413,17 @@ public class PgpKeyOperation {
// read masterKeyFlags, and use the same as before. // read masterKeyFlags, and use the same as before.
// since this is the master key, this contains at least CERTIFY_OTHER // since this is the master key, this contains at least CERTIFY_OTHER
int masterKeyFlags = readKeyFlags(masterSecretKey.getPublicKey()) | KeyFlags.CERTIFY_OTHER; PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER;
long masterKeyExpiry = masterPublicKey.getValidSeconds() == 0L ? 0L :
masterPublicKey.getCreationTime().getTime() / 1000 + masterPublicKey.getValidSeconds();
return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log); return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log);
} }
private EditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, private EditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
int masterKeyFlags, int masterKeyFlags, long masterKeyExpiry,
SaveKeyringParcel saveParcel, String passphrase, SaveKeyringParcel saveParcel, String passphrase,
OperationLog log) { OperationLog log) {
@@ -346,9 +448,10 @@ public class PgpKeyOperation {
} }
} }
// work on master secret key
try { try {
{ // work on master secret key
PGPPublicKey modifiedPublicKey = masterPublicKey; PGPPublicKey modifiedPublicKey = masterPublicKey;
// 2a. Add certificates for new user ids // 2a. Add certificates for new user ids
@@ -391,7 +494,7 @@ public class PgpKeyOperation {
&& userId.equals(saveParcel.mChangePrimaryUserId); && userId.equals(saveParcel.mChangePrimaryUserId);
// generate and add new certificate // generate and add new certificate
PGPSignature cert = generateUserIdSignature(masterPrivateKey, PGPSignature cert = generateUserIdSignature(masterPrivateKey,
masterPublicKey, userId, isPrimary, masterKeyFlags); masterPublicKey, userId, isPrimary, masterKeyFlags, masterKeyExpiry);
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
} }
subProgressPop(); subProgressPop();
@@ -404,6 +507,20 @@ public class PgpKeyOperation {
String userId = saveParcel.mRevokeUserIds.get(i); String userId = saveParcel.mRevokeUserIds.get(i);
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent, userId); log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent, userId);
// Make sure the user id exists (yes these are 10 LoC in Java!)
boolean exists = false;
//noinspection unchecked
for (String uid : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) {
if (userId.equals(uid)) {
exists = true;
break;
}
}
if (!exists) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_REVOKE, indent);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
// a duplicate revocation will be removed during canonicalization, so no need to // a duplicate revocation will be removed during canonicalization, so no need to
// take care of that here. // take care of that here.
PGPSignature cert = generateRevocationSignature(masterPrivateKey, PGPSignature cert = generateRevocationSignature(masterPrivateKey,
@@ -479,7 +596,8 @@ public class PgpKeyOperation {
modifiedPublicKey = PGPPublicKey.removeCertification( modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert); modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature( PGPSignature newCert = generateUserIdSignature(
masterPrivateKey, masterPublicKey, userId, false, masterKeyFlags); masterPrivateKey, masterPublicKey, userId, false,
masterKeyFlags, masterKeyExpiry);
modifiedPublicKey = PGPPublicKey.addCertification( modifiedPublicKey = PGPPublicKey.addCertification(
modifiedPublicKey, userId, newCert); modifiedPublicKey, userId, newCert);
continue; continue;
@@ -494,7 +612,8 @@ public class PgpKeyOperation {
modifiedPublicKey = PGPPublicKey.removeCertification( modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert); modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature( PGPSignature newCert = generateUserIdSignature(
masterPrivateKey, masterPublicKey, userId, true, masterKeyFlags); masterPrivateKey, masterPublicKey, userId, true,
masterKeyFlags, masterKeyExpiry);
modifiedPublicKey = PGPPublicKey.addCertification( modifiedPublicKey = PGPPublicKey.addCertification(
modifiedPublicKey, userId, newCert); modifiedPublicKey, userId, newCert);
ok = true; ok = true;
@@ -519,6 +638,8 @@ public class PgpKeyOperation {
sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey); sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey);
} }
}
// 4a. For each subkey change, generate new subkey binding certificate // 4a. For each subkey change, generate new subkey binding certificate
subProgressPush(50, 60); subProgressPush(50, 60);
for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) { for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) {
@@ -528,27 +649,47 @@ public class PgpKeyOperation {
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE, log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,
indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
// TODO allow changes in master key? this implies generating new user id certs...
if (change.mKeyId == masterPublicKey.getKeyID()) {
Log.e(Constants.TAG, "changing the master key not supported");
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId); PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
if (sKey == null) { if (sKey == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING, log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SUBKEY_MISSING,
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} }
PGPPublicKey pKey = sKey.getPublicKey();
// expiry must not be in the past // expiry must not be in the past
if (change.mExpiry != null && new Date(change.mExpiry*1000).before(new Date())) { if (change.mExpiry != null && change.mExpiry != 0 &&
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, new Date(change.mExpiry*1000).before(new Date())) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PAST_EXPIRY,
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} }
// if this is the master key, update uid certificates instead
if (change.mKeyId == masterPublicKey.getKeyID()) {
int flags = change.mFlags == null ? masterKeyFlags : change.mFlags;
long expiry = change.mExpiry == null ? masterKeyExpiry : change.mExpiry;
if ((flags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NO_CERTIFY, indent + 1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
PGPPublicKey pKey =
updateMasterCertificates(masterPrivateKey, masterPublicKey,
flags, expiry, indent, log);
if (pKey == null) {
// error log entry has already been added by updateMasterCertificates itself
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
masterSecretKey = PGPSecretKey.replacePublicKey(masterSecretKey, pKey);
masterPublicKey = pKey;
sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey);
continue;
}
// otherwise, continue working on the public key
PGPPublicKey pKey = sKey.getPublicKey();
// keep old flags, or replace with new ones // keep old flags, or replace with new ones
int flags = change.mFlags == null ? readKeyFlags(pKey) : change.mFlags; int flags = change.mFlags == null ? readKeyFlags(pKey) : change.mFlags;
long expiry; long expiry;
@@ -565,7 +706,7 @@ public class PgpKeyOperation {
//noinspection unchecked //noinspection unchecked
for (PGPSignature sig : new IterableIterator<PGPSignature>(pKey.getSignatures())) { for (PGPSignature sig : new IterableIterator<PGPSignature>(pKey.getSignatures())) {
// special case: if there is a revocation, don't use expiry from before // special case: if there is a revocation, don't use expiry from before
if (change.mExpiry == null if ( (change.mExpiry == null || change.mExpiry == 0L)
&& sig.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) { && sig.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) {
expiry = 0; expiry = 0;
} }
@@ -591,7 +732,7 @@ public class PgpKeyOperation {
PGPSecretKey sKey = sKR.getSecretKey(revocation); PGPSecretKey sKey = sKR.getSecretKey(revocation);
if (sKey == null) { if (sKey == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING, log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SUBKEY_MISSING,
indent+1, PgpKeyHelper.convertKeyIdToHex(revocation)); indent+1, PgpKeyHelper.convertKeyIdToHex(revocation));
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} }
@@ -611,10 +752,16 @@ public class PgpKeyOperation {
progress(R.string.progress_modify_subkeyadd, (i-1) * (100 / saveParcel.mAddSubKeys.size())); progress(R.string.progress_modify_subkeyadd, (i-1) * (100 / saveParcel.mAddSubKeys.size()));
SaveKeyringParcel.SubkeyAdd add = saveParcel.mAddSubKeys.get(i); SaveKeyringParcel.SubkeyAdd add = saveParcel.mAddSubKeys.get(i);
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent); log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent,
PgpKeyHelper.getAlgorithmInfo(add.mAlgorithm, add.mKeySize, add.mCurve) );
if (add.mExpiry != null && new Date(add.mExpiry*1000).before(new Date())) { if (add.mExpiry == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1); log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NULL_EXPIRY, indent +1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
if (add.mExpiry > 0L && new Date(add.mExpiry*1000).before(new Date())) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PAST_EXPIRY, indent +1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} }
@@ -623,9 +770,10 @@ public class PgpKeyOperation {
(i-1) * (100 / saveParcel.mAddSubKeys.size()), (i-1) * (100 / saveParcel.mAddSubKeys.size()),
i * (100 / saveParcel.mAddSubKeys.size()) i * (100 / saveParcel.mAddSubKeys.size())
); );
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent); PGPKeyPair keyPair = createKey(add, log, indent);
subProgressPop(); subProgressPop();
if (keyPair == null) { if (keyPair == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent +1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} }
@@ -633,21 +781,21 @@ public class PgpKeyOperation {
PGPPublicKey pKey = keyPair.getPublicKey(); PGPPublicKey pKey = keyPair.getPublicKey();
PGPSignature cert = generateSubkeyBindingSignature( PGPSignature cert = generateSubkeyBindingSignature(
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
add.mFlags, add.mExpiry == null ? 0 : add.mExpiry); add.mFlags, add.mExpiry);
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
PGPSecretKey sKey; { PGPSecretKey sKey; {
// define hashing and signing algos // Build key encrypter and decrypter based on passphrase
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(SECRET_KEY_ENCRYPTOR_HASH_ALGO);
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
// NOTE: only SHA1 is supported for key checksum calculations.
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(HashAlgorithmTags.SHA1); .build().get(HashAlgorithmTags.SHA1);
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey, sha1Calc, false, keyEncryptor);
// Build key encrypter and decrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
sha1Calc, false, keyEncryptor);
} }
log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID, log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
@@ -662,21 +810,67 @@ public class PgpKeyOperation {
if (saveParcel.mNewPassphrase != null) { if (saveParcel.mNewPassphrase != null) {
progress(R.string.progress_modify_passphrase, 90); progress(R.string.progress_modify_passphrase, 90);
log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent); log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent);
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build() indent += 1;
.get(HashAlgorithmTags.SHA1);
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build()
.get(SECRET_KEY_ENCRYPTOR_HASH_ALGO);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
// Build key encryptor based on new passphrase // Build key encryptor based on new passphrase
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc) SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.mNewPassphrase.toCharArray()); saveParcel.mNewPassphrase.toCharArray());
sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew); // noinspection unchecked
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
log.add(LogLevel.DEBUG, LogType.MSG_MF_PASSPHRASE_KEY, indent,
PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
boolean ok = false;
try {
// try to set new passphrase
sKey = PGPSecretKey.copyWithNewPassword(sKey, keyDecryptor, keyEncryptorNew);
ok = true;
} catch (PGPException e) {
// if this is the master key, error!
if (sKey.getKeyID() == masterPublicKey.getKeyID()) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
// being in here means decrypt failed, likely due to a bad passphrase try
// again with an empty passphrase, maybe we can salvage this
try {
log.add(LogLevel.DEBUG, LogType.MSG_MF_PASSPHRASE_EMPTY_RETRY, indent+1);
PBESecretKeyDecryptor emptyDecryptor =
new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
sKey = PGPSecretKey.copyWithNewPassword(sKey, emptyDecryptor, keyEncryptorNew);
ok = true;
} catch (PGPException e2) {
// non-fatal but not ok, handled below
}
}
if (!ok) {
// for a subkey, it's merely a warning
log.add(LogLevel.WARN, LogType.MSG_MF_PASSPHRASE_FAIL, indent+1,
PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
continue;
}
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
}
indent -= 1;
} }
// This one must only be thrown by
} catch (IOException e) { } catch (IOException e) {
Log.e(Constants.TAG, "encountered IOException while modifying key", e);
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_ENCODE, indent+1); log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_ENCODE, indent+1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} catch (PGPException e) { } catch (PGPException e) {
@@ -684,6 +878,7 @@ public class PgpKeyOperation {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent+1); log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent+1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} catch (SignatureException e) { } catch (SignatureException e) {
Log.e(Constants.TAG, "encountered SignatureException while modifying key", e);
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SIG, indent+1); log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SIG, indent+1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
} }
@@ -694,21 +889,121 @@ public class PgpKeyOperation {
} }
/** Update all (non-revoked) uid signatures with new flags and expiry time. */
private static PGPPublicKey updateMasterCertificates(
PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey,
int flags, long expiry, int indent, OperationLog log)
throws PGPException, IOException, SignatureException {
// keep track if we actually changed one
boolean ok = false;
log.add(LogLevel.DEBUG, LogType.MSG_MF_MASTER, indent);
indent += 1;
PGPPublicKey modifiedPublicKey = masterPublicKey;
// we work on the modifiedPublicKey here, to respect new or newly revoked uids
// noinspection unchecked
for (String userId : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) {
boolean isRevoked = false;
PGPSignature currentCert = null;
// noinspection unchecked
for (PGPSignature cert : new IterableIterator<PGPSignature>(
modifiedPublicKey.getSignaturesForID(userId))) {
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
// foreign certificate?! error error error
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
return null;
}
// we know from canonicalization that if there is any revocation here, it
// is valid and not superseded by a newer certification.
if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) {
isRevoked = true;
continue;
}
// we know from canonicalization that there is only one binding
// certification here, so we can just work with the first one.
if (cert.getSignatureType() == PGPSignature.NO_CERTIFICATION ||
cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION ||
cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION ||
cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) {
currentCert = cert;
}
}
if (currentCert == null) {
// no certificate found?! error error error
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
return null;
}
// we definitely should not update certifications of revoked keys, so just leave it.
if (isRevoked) {
continue;
}
// add shiny new user id certificate
boolean isPrimary = currentCert.getHashedSubPackets() != null &&
currentCert.getHashedSubPackets().isPrimaryUserID();
modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature(
masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry);
modifiedPublicKey = PGPPublicKey.addCertification(
modifiedPublicKey, userId, newCert);
ok = true;
}
if (!ok) {
// might happen, theoretically, if there is a key with no uid..
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_MASTER_NONE, indent);
return null;
}
return modifiedPublicKey;
}
private static PGPSignature generateUserIdSignature( private static PGPSignature generateUserIdSignature(
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary, int flags) PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary,
int flags, long expiry)
throws IOException, PGPException, SignatureException { throws IOException, PGPException, SignatureException {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1) masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, new Date()); PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); {
subHashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); /*
subHashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); * From RFC about critical subpackets:
subHashedPacketsGen.setPrimaryUserID(false, primary); * If a subpacket is encountered that is
subHashedPacketsGen.setKeyFlags(false, flags); * marked critical but is unknown to the evaluating software, the
sGen.setHashedSubpackets(subHashedPacketsGen.generate()); * evaluator SHOULD consider the signature to be in error.
* An evaluator may "recognize" a subpacket, but not implement it. The
* purpose of the critical bit is to allow the signer to tell an
* evaluator that it would prefer a new, unknown feature to generate an
* error than be ignored.
*/
/* non-critical subpackets: */
hashedPacketsGen.setPreferredSymmetricAlgorithms(false, PREFERRED_SYMMETRIC_ALGORITHMS);
hashedPacketsGen.setPreferredHashAlgorithms(false, PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(false, PREFERRED_COMPRESSION_ALGORITHMS);
hashedPacketsGen.setPrimaryUserID(false, primary);
/* critical subpackets: we consider those important for a modern pgp implementation */
hashedPacketsGen.setSignatureCreationTime(true, new Date());
// Request that senders add the MDC to the message (authenticate unsigned messages)
hashedPacketsGen.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
hashedPacketsGen.setKeyFlags(true, flags);
if (expiry > 0) {
hashedPacketsGen.setKeyExpirationTime(
true, expiry - pKey.getCreationTime().getTime() / 1000);
}
}
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
return sGen.generateCertification(userId, pKey); return sGen.generateCertification(userId, pKey);
} }
@@ -717,11 +1012,11 @@ public class PgpKeyOperation {
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId) PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
throws IOException, PGPException, SignatureException { throws IOException, PGPException, SignatureException {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1) masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, new Date()); subHashedPacketsGen.setSignatureCreationTime(true, new Date());
sGen.setHashedSubpackets(subHashedPacketsGen.generate()); sGen.setHashedSubpackets(subHashedPacketsGen.generate());
sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey); sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey);
return sGen.generateCertification(userId, pKey); return sGen.generateCertification(userId, pKey);
@@ -731,11 +1026,11 @@ public class PgpKeyOperation {
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey) PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey)
throws IOException, PGPException, SignatureException { throws IOException, PGPException, SignatureException {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1) masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA512)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, new Date()); subHashedPacketsGen.setSignatureCreationTime(true, new Date());
sGen.setHashedSubpackets(subHashedPacketsGen.generate()); sGen.setHashedSubpackets(subHashedPacketsGen.generate());
// Generate key revocation or subkey revocation, depending on master/subkey-ness // Generate key revocation or subkey revocation, depending on master/subkey-ness
if (masterPublicKey.getKeyID() == pKey.getKeyID()) { if (masterPublicKey.getKeyID() == pKey.getKeyID()) {
@@ -765,38 +1060,38 @@ public class PgpKeyOperation {
throws IOException, PGPException, SignatureException { throws IOException, PGPException, SignatureException {
// date for signing // date for signing
Date todayDate = new Date(); Date creationTime = new Date();
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
// If this key can sign, we need a primary key binding signature // If this key can sign, we need a primary key binding signature
if ((flags & KeyFlags.SIGN_DATA) > 0) { if ((flags & KeyFlags.SIGN_DATA) > 0) {
// cross-certify signing keys // cross-certify signing keys
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, todayDate); subHashedPacketsGen.setSignatureCreationTime(false, creationTime);
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1) pKey.getAlgorithm(), HashAlgorithmTags.SHA512)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey); sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
sGen.setHashedSubpackets(subHashedPacketsGen.generate()); sGen.setHashedSubpackets(subHashedPacketsGen.generate());
PGPSignature certification = sGen.generateCertification(masterPublicKey, pKey); PGPSignature certification = sGen.generateCertification(masterPublicKey, pKey);
unhashedPacketsGen.setEmbeddedSignature(false, certification); unhashedPacketsGen.setEmbeddedSignature(true, certification);
} }
PGPSignatureSubpacketGenerator hashedPacketsGen; PGPSignatureSubpacketGenerator hashedPacketsGen;
{ {
hashedPacketsGen = new PGPSignatureSubpacketGenerator(); hashedPacketsGen = new PGPSignatureSubpacketGenerator();
hashedPacketsGen.setSignatureCreationTime(false, todayDate); hashedPacketsGen.setSignatureCreationTime(true, creationTime);
hashedPacketsGen.setKeyFlags(false, flags); hashedPacketsGen.setKeyFlags(true, flags);
}
if (expiry > 0) { if (expiry > 0) {
long creationTime = pKey.getCreationTime().getTime() / 1000; hashedPacketsGen.setKeyExpirationTime(true,
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationTime); expiry - pKey.getCreationTime().getTime() / 1000);
}
} }
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1) masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA512)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey); sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey);

View File

@@ -70,7 +70,7 @@ public class PgpSignEncrypt {
private long mSignatureMasterKeyId; private long mSignatureMasterKeyId;
private int mSignatureHashAlgorithm; private int mSignatureHashAlgorithm;
private String mSignaturePassphrase; private String mSignaturePassphrase;
private boolean mEncryptToSigner; private long mAdditionalEncryptId;
private boolean mCleartextInput; private boolean mCleartextInput;
private String mOriginalFilename; private String mOriginalFilename;
@@ -103,7 +103,7 @@ public class PgpSignEncrypt {
this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId; this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId;
this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm; this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
this.mSignaturePassphrase = builder.mSignaturePassphrase; this.mSignaturePassphrase = builder.mSignaturePassphrase;
this.mEncryptToSigner = builder.mEncryptToSigner; this.mAdditionalEncryptId = builder.mAdditionalEncryptId;
this.mCleartextInput = builder.mCleartextInput; this.mCleartextInput = builder.mCleartextInput;
this.mNfcSignedHash = builder.mNfcSignedHash; this.mNfcSignedHash = builder.mNfcSignedHash;
this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp;
@@ -127,7 +127,7 @@ public class PgpSignEncrypt {
private long mSignatureMasterKeyId = Constants.key.none; private long mSignatureMasterKeyId = Constants.key.none;
private int mSignatureHashAlgorithm = 0; private int mSignatureHashAlgorithm = 0;
private String mSignaturePassphrase = null; private String mSignaturePassphrase = null;
private boolean mEncryptToSigner = false; private long mAdditionalEncryptId = Constants.key.none;
private boolean mCleartextInput = false; private boolean mCleartextInput = false;
private String mOriginalFilename = ""; private String mOriginalFilename = "";
private byte[] mNfcSignedHash = null; private byte[] mNfcSignedHash = null;
@@ -175,7 +175,7 @@ public class PgpSignEncrypt {
} }
public Builder setSignatureMasterKeyId(long signatureMasterKeyId) { public Builder setSignatureMasterKeyId(long signatureMasterKeyId) {
this.mSignatureMasterKeyId = signatureMasterKeyId; mSignatureMasterKeyId = signatureMasterKeyId;
return this; return this;
} }
@@ -192,11 +192,11 @@ public class PgpSignEncrypt {
/** /**
* Also encrypt with the signing keyring * Also encrypt with the signing keyring
* *
* @param encryptToSigner * @param additionalEncryptId
* @return * @return
*/ */
public Builder setEncryptToSigner(boolean encryptToSigner) { public Builder setAdditionalEncryptId(long additionalEncryptId) {
mEncryptToSigner = encryptToSigner; mAdditionalEncryptId = additionalEncryptId;
return this; return this;
} }
@@ -288,10 +288,10 @@ public class PgpSignEncrypt {
+ "\nenableCompression:" + enableCompression + "\nenableCompression:" + enableCompression
+ "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput); + "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput);
// add signature key id to encryption ids (self-encrypt) // add additional key id to encryption ids (mostly to do self-encryption)
if (enableEncryption && enableSignature && mEncryptToSigner) { if (enableEncryption && mAdditionalEncryptId != Constants.key.none) {
mEncryptionMasterKeyIds = Arrays.copyOf(mEncryptionMasterKeyIds, mEncryptionMasterKeyIds.length + 1); mEncryptionMasterKeyIds = Arrays.copyOf(mEncryptionMasterKeyIds, mEncryptionMasterKeyIds.length + 1);
mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mSignatureMasterKeyId; mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mAdditionalEncryptId;
} }
ArmoredOutputStream armorOut = null; ArmoredOutputStream armorOut = null;

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
@@ -55,7 +58,8 @@ import java.util.TreeSet;
* This class and its relatives UncachedPublicKey and UncachedSecretKey are * This class and its relatives UncachedPublicKey and UncachedSecretKey are
* used to move around pgp key rings in non crypto related (UI, mostly) code. * 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, * 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 * This class is also special in that it can hold either the PGPPublicKeyRing
* or PGPSecretKeyRing derivate of the PGPKeyRing class, since these are * or PGPSecretKeyRing derivate of the PGPKeyRing class, since these are
@@ -118,6 +122,10 @@ public class UncachedKeyRing {
return mRing.getPublicKey().getFingerprint(); return mRing.getPublicKey().getFingerprint();
} }
public int getVersion() {
return mRing.getPublicKey().getVersion();
}
public static UncachedKeyRing decodeFromData(byte[] data) public static UncachedKeyRing decodeFromData(byte[] data)
throws PgpGeneralException, IOException { throws PgpGeneralException, IOException {
@@ -211,8 +219,7 @@ public class UncachedKeyRing {
aos.close(); aos.close();
} }
/** "Canonicalizes" a public key, removing inconsistencies in the process. This variant can be /** "Canonicalizes" a public key, removing inconsistencies in the process.
* applied to public keyrings only.
* *
* More specifically: * More specifically:
* - Remove all non-verifying self-certificates * - 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 the key is a secret key, remove all certificates by foreign keys
* - If no valid user id remains, log an error and return null * - 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") @SuppressWarnings("ConstantConditions")
@@ -241,6 +248,12 @@ public class UncachedKeyRing {
indent, PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())); indent, PgpKeyHelper.convertKeyIdToHex(getMasterKeyId()));
indent += 1; 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(); final Date now = new Date();
int redundantCerts = 0, badCerts = 0; int redundantCerts = 0, badCerts = 0;
@@ -259,13 +272,12 @@ public class UncachedKeyRing {
for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getKeySignatures())) { for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getKeySignatures())) {
int type = zert.getSignatureType(); 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 if (type == PGPSignature.NO_CERTIFICATION
|| type == PGPSignature.DEFAULT_CERTIFICATION || type == PGPSignature.DEFAULT_CERTIFICATION
|| type == PGPSignature.CASUAL_CERTIFICATION || type == PGPSignature.CASUAL_CERTIFICATION
|| type == PGPSignature.POSITIVE_CERTIFICATION || type == PGPSignature.POSITIVE_CERTIFICATION
|| type == PGPSignature.CERTIFICATION_REVOCATION) { || type == PGPSignature.CERTIFICATION_REVOCATION) {
// These should not be here...
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE_UID, indent); log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE_UID, indent);
modified = PGPPublicKey.removeCertification(modified, zert); modified = PGPPublicKey.removeCertification(modified, zert);
badCerts += 1; badCerts += 1;
@@ -328,7 +340,17 @@ public class UncachedKeyRing {
} }
} }
ArrayList<String> processedUserIds = new ArrayList<String>();
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) { 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; PGPSignature selfCert = null;
revocation = null; revocation = null;
@@ -405,13 +427,13 @@ public class UncachedKeyRing {
if (selfCert == null) { if (selfCert == null) {
selfCert = zert; selfCert = zert;
} else if (selfCert.getCreationTime().before(cert.getCreationTime())) { } 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); indent, userId);
modified = PGPPublicKey.removeCertification(modified, userId, selfCert); modified = PGPPublicKey.removeCertification(modified, userId, selfCert);
redundantCerts += 1; redundantCerts += 1;
selfCert = zert; selfCert = zert;
} else { } else {
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP, log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_CERT_DUP,
indent, userId); indent, userId);
modified = PGPPublicKey.removeCertification(modified, userId, zert); modified = PGPPublicKey.removeCertification(modified, userId, zert);
redundantCerts += 1; redundantCerts += 1;
@@ -474,6 +496,10 @@ public class UncachedKeyRing {
// Replace modified key in the keyring // Replace modified key in the keyring
ring = replacePublicKey(ring, modified); ring = replacePublicKey(ring, modified);
if (ring == null) {
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent);
return null;
}
indent -= 1; indent -= 1;
} }
@@ -580,8 +606,8 @@ public class UncachedKeyRing {
} }
// if we already have a cert, and this one is not newer: skip it // if we already have a cert, and this one is older: skip it
if (selfCert != null && selfCert.getCreationTime().before(cert.getCreationTime())) { if (selfCert != null && cert.getCreationTime().before(selfCert.getCreationTime())) {
log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_DUP, indent); log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_DUP, indent);
redundantCerts += 1; redundantCerts += 1;
continue; continue;
@@ -641,6 +667,10 @@ public class UncachedKeyRing {
} }
// replace pubkey in keyring // replace pubkey in keyring
ring = replacePublicKey(ring, modified); ring = replacePublicKey(ring, modified);
if (ring == null) {
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent);
return null;
}
indent -= 1; indent -= 1;
} }
@@ -681,8 +711,9 @@ public class UncachedKeyRing {
long masterKeyId = other.getMasterKeyId(); long masterKeyId = other.getMasterKeyId();
if (getMasterKeyId() != masterKeyId) { if (getMasterKeyId() != masterKeyId
log.add(LogLevel.ERROR, LogType.MSG_MG_HETEROGENEOUS, indent); || !Arrays.equals(getFingerprint(), other.getFingerprint())) {
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_HETEROGENEOUS, indent);
return null; return null;
} }
@@ -729,6 +760,10 @@ public class UncachedKeyRing {
} else { } else {
// otherwise, just insert the public key // otherwise, just insert the public key
result = replacePublicKey(result, key); result = replacePublicKey(result, key);
if (result == null) {
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent);
return null;
}
} }
continue; continue;
} }
@@ -757,6 +792,10 @@ public class UncachedKeyRing {
if (!key.isMasterKey()) { if (!key.isMasterKey()) {
if (modified != resultKey) { if (modified != resultKey) {
result = replacePublicKey(result, modified); result = replacePublicKey(result, modified);
if (result == null) {
log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent);
return null;
}
} }
continue; continue;
} }
@@ -781,6 +820,10 @@ public class UncachedKeyRing {
// If anything changed, save the updated (sub)key // If anything changed, save the updated (sub)key
if (modified != resultKey) { if (modified != resultKey) {
result = replacePublicKey(result, modified); 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); return new UncachedKeyRing(result);
} catch (IOException e) { } 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; return null;
} }
@@ -826,17 +869,20 @@ public class UncachedKeyRing {
*/ */
private static PGPKeyRing replacePublicKey(PGPKeyRing ring, PGPPublicKey key) { private static PGPKeyRing replacePublicKey(PGPKeyRing ring, PGPPublicKey key) {
if (ring instanceof PGPPublicKeyRing) { if (ring instanceof PGPPublicKeyRing) {
return PGPPublicKeyRing.insertPublicKey((PGPPublicKeyRing) ring, key); PGPPublicKeyRing pubRing = (PGPPublicKeyRing) ring;
} return PGPPublicKeyRing.insertPublicKey(pubRing, key);
} else {
PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring; PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring;
PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID()); PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID());
// TODO generate secret key with S2K dummy, if none exists! for now, just die. // TODO generate secret key with S2K dummy, if none exists!
if (sKey == null) { if (sKey == null) {
throw new RuntimeException("dummy secret key generation not yet implemented"); Log.e(Constants.TAG, "dummy secret key generation not yet implemented");
return null;
} }
sKey = PGPSecretKey.replacePublicKey(sKey, key); sKey = PGPSecretKey.replacePublicKey(sKey, key);
return PGPSecretKeyRing.insertSecretKey(secRing, sKey); return PGPSecretKeyRing.insertSecretKey(secRing, sKey);
} }
}
/** This method removes a subkey in a keyring. /** This method removes a subkey in a keyring.
* *

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -17,6 +18,10 @@
package org.sufficientlysecure.keychain.pgp; 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.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignature;
@@ -93,10 +98,23 @@ public class UncachedPublicKey {
return mPublicKey.getAlgorithm(); return mPublicKey.getAlgorithm();
} }
public int getBitStrength() { public Integer getBitStrength() {
if (isEC()) {
return null;
}
return mPublicKey.getBitStrength(); 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. /** 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) * This is an expensive operation, since potentially a lot of certificates (and revocations)
@@ -185,6 +203,10 @@ public class UncachedPublicKey {
return getAlgorithm() == PGPPublicKey.DSA; return getAlgorithm() == PGPPublicKey.DSA;
} }
public boolean isEC() {
return getAlgorithm() == PGPPublicKey.ECDH || getAlgorithm() == PGPPublicKey.ECDSA;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
// TODO make this safe // TODO make this safe
public int getKeyUsage() { public int getKeyUsage() {

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -95,9 +96,6 @@ public class WrappedSignature {
} catch (PGPException e) { } catch (PGPException e) {
// no matter // no matter
Log.e(Constants.TAG, "exception reading embedded signatures", e); 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; return sigs;
} }
@@ -149,27 +147,17 @@ public class WrappedSignature {
} }
} }
public void update(byte[] data, int offset, int length) throws PgpGeneralException { public void update(byte[] data, int offset, int length) {
try {
mSig.update(data, offset, length); mSig.update(data, offset, length);
} catch(SignatureException e) {
throw new PgpGeneralException(e);
}
} }
public void update(byte data) throws PgpGeneralException { public void update(byte data) {
try {
mSig.update(data); mSig.update(data);
} catch(SignatureException e) {
throw new PgpGeneralException(e);
}
} }
public boolean verify() throws PgpGeneralException { public boolean verify() throws PgpGeneralException {
try { try {
return mSig.verify(); return mSig.verify();
} catch(SignatureException e) {
throw new PgpGeneralException(e);
} catch(PGPException e) { } catch(PGPException e) {
throw new PgpGeneralException(e); throw new PgpGeneralException(e);
} }
@@ -178,8 +166,6 @@ public class WrappedSignature {
boolean verifySignature(PGPPublicKey key) throws PgpGeneralException { boolean verifySignature(PGPPublicKey key) throws PgpGeneralException {
try { try {
return mSig.verifyCertification(key); return mSig.verifyCertification(key);
} catch (SignatureException e) {
throw new PgpGeneralException("Sign!", e);
} catch (PGPException e) { } catch (PGPException e) {
throw new PgpGeneralException("Error!", e); throw new PgpGeneralException("Error!", e);
} }
@@ -188,8 +174,6 @@ public class WrappedSignature {
boolean verifySignature(PGPPublicKey masterKey, PGPPublicKey subKey) throws PgpGeneralException { boolean verifySignature(PGPPublicKey masterKey, PGPPublicKey subKey) throws PgpGeneralException {
try { try {
return mSig.verifyCertification(masterKey, subKey); return mSig.verifyCertification(masterKey, subKey);
} catch (SignatureException e) {
throw new PgpGeneralException("Sign!", e);
} catch (PGPException e) { } catch (PGPException e) {
throw new PgpGeneralException("Error!", e); throw new PgpGeneralException("Error!", e);
} }
@@ -198,8 +182,6 @@ public class WrappedSignature {
boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException { boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException {
try { try {
return mSig.verifyCertification(uid, key); return mSig.verifyCertification(uid, key);
} catch (SignatureException e) {
throw new PgpGeneralException("Error!", e);
} catch (PGPException e) { } catch (PGPException e) {
throw new PgpGeneralException("Error!", e); throw new PgpGeneralException("Error!", e);
} }

View File

@@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -38,6 +39,7 @@ public class KeychainContract {
String FINGERPRINT = "fingerprint"; String FINGERPRINT = "fingerprint";
String KEY_SIZE = "key_size"; String KEY_SIZE = "key_size";
String KEY_CURVE_OID = "key_curve_oid";
String CAN_SIGN = "can_sign"; String CAN_SIGN = "can_sign";
String CAN_ENCRYPT = "can_encrypt"; String CAN_ENCRYPT = "can_encrypt";
String CAN_CERTIFY = "can_certify"; 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_ANY_SECRET = "has_any_secret";
public static final String HAS_ENCRYPT = "has_encrypt"; public static final String HAS_ENCRYPT = "has_encrypt";
public static final String HAS_SIGN = "has_sign"; 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 PUBKEY_DATA = "pubkey_data";
public static final String PRIVKEY_DATA = "privkey_data"; public static final String PRIVKEY_DATA = "privkey_data";

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * 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 { public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db"; private static final String DATABASE_NAME = "openkeychain.db";
private static final int DATABASE_VERSION = 2; private static final int DATABASE_VERSION = 3;
static Boolean apgHack = false; static Boolean apgHack = false;
public interface Tables { public interface Tables {
@@ -85,6 +86,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ KeysColumns.KEY_ID + " INTEGER, " + KeysColumns.KEY_ID + " INTEGER, "
+ KeysColumns.KEY_SIZE + " INTEGER, " + KeysColumns.KEY_SIZE + " INTEGER, "
+ KeysColumns.KEY_CURVE_OID + " TEXT, "
+ KeysColumns.ALGORITHM + " INTEGER, " + KeysColumns.ALGORITHM + " INTEGER, "
+ KeysColumns.FINGERPRINT + " BLOB, " + KeysColumns.FINGERPRINT + " BLOB, "
@@ -201,13 +203,20 @@ public class KeychainDatabase extends SQLiteOpenHelper {
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion == 1) {
// add has_secret for all who are upgrading from a beta version // add has_secret for all who are upgrading from a beta version
switch (oldVersion) {
case 1:
try { try {
db.execSQL("ALTER TABLE keys ADD COLUMN has_secret BOOLEAN"); db.execSQL("ALTER TABLE keys ADD COLUMN has_secret BOOLEAN");
} catch(Exception e){ } catch(Exception e){
// never mind, the column probably already existed // 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")) { if (db.equals("apg.db")) {
hasApgDb = true; hasApgDb = true;
} else if (db.equals("apg_old.db")) { } 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) // delete old database
Log.d(Constants.TAG, "All done - moving apg.db to apg_old.db"); context.getDatabasePath("apg.db").delete();
context.getDatabasePath("apg.db").renameTo(context.getDatabasePath("apg_old.db"));
} }
private static void copy(File in, File out) throws IOException { private static void copy(File in, File out) throws IOException {
@@ -349,4 +358,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
copy(in, out); copy(in, out);
} }
// DANGEROUS, use in test code ONLY!
public void clearDatabase() {
getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC);
}
} }

View File

@@ -1,6 +1,7 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> * 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 * 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 * 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.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
projectionMap.put(KeyRings.KEY_ID, Tables.KEYS + "." + Keys.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_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.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED);
projectionMap.put(KeyRings.CAN_CERTIFY, Tables.KEYS + "." + Keys.CAN_CERTIFY); projectionMap.put(KeyRings.CAN_CERTIFY, Tables.KEYS + "." + Keys.CAN_CERTIFY);
projectionMap.put(KeyRings.CAN_ENCRYPT, Tables.KEYS + "." + Keys.CAN_ENCRYPT); 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); "kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT);
projectionMap.put(KeyRings.HAS_SIGN, projectionMap.put(KeyRings.HAS_SIGN,
"kS." + Keys.KEY_ID + " AS " + 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, projectionMap.put(KeyRings.IS_EXPIRED,
"(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY
+ " < " + new Date().getTime() / 1000 + ") AS " + KeyRings.IS_EXPIRED); + " < " + 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 + " AND ( kS." + Keys.EXPIRY + " IS NULL OR kS." + Keys.EXPIRY
+ " >= " + new Date().getTime() / 1000 + " )" + " >= " + 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"); qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0");
// in case there are multiple verifying certificates // 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.RANK, Tables.KEYS + "." + Keys.RANK);
projectionMap.put(Keys.KEY_ID, Keys.KEY_ID); projectionMap.put(Keys.KEY_ID, Keys.KEY_ID);
projectionMap.put(Keys.KEY_SIZE, Keys.KEY_SIZE); 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.IS_REVOKED, Keys.IS_REVOKED);
projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY); projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY);
projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT);
@@ -674,6 +688,11 @@ public class KeychainProvider extends ContentProvider {
final int match = mUriMatcher.match(uri); final int match = mUriMatcher.match(uri);
switch (match) { switch (match) {
// dangerous
case KEY_RINGS_UNIFIED: {
count = db.delete(Tables.KEY_RINGS_PUBLIC, null, null);
break;
}
case KEY_RING_PUBLIC: { case KEY_RING_PUBLIC: {
@SuppressWarnings("ConstantConditions") // ensured by uriMatcher above @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above
String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1); String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1);

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * 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 android.support.v4.util.LongSparseArray;
import org.sufficientlysecure.keychain.Constants; 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.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.NullProgressable; import org.sufficientlysecure.keychain.pgp.NullProgressable;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpImportExport;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; 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.LogLevel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; 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.service.OperationResults.SaveKeyringResult;
import org.sufficientlysecure.keychain.util.FileImportCache;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -63,6 +71,7 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -86,6 +95,10 @@ public class ProviderHelper {
this(context, new OperationLog(), 0); this(context, new OperationLog(), 0);
} }
public ProviderHelper(Context context, OperationLog log) {
this(context, log, 0);
}
public ProviderHelper(Context context, OperationLog log, int indent) { public ProviderHelper(Context context, OperationLog log, int indent) {
mContext = context; mContext = context;
mContentResolver = context.getContentResolver(); mContentResolver = context.getContentResolver();
@@ -93,14 +106,6 @@ public class ProviderHelper {
mIndent = indent; 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() { public OperationLog getLog() {
return mLog; return mLog;
} }
@@ -322,6 +327,7 @@ public class ProviderHelper {
values.put(Keys.KEY_ID, key.getKeyId()); values.put(Keys.KEY_ID, key.getKeyId());
values.put(Keys.KEY_SIZE, key.getBitStrength()); values.put(Keys.KEY_SIZE, key.getBitStrength());
values.put(Keys.KEY_CURVE_OID, key.getCurveOid());
values.put(Keys.ALGORITHM, key.getAlgorithm()); values.put(Keys.ALGORITHM, key.getAlgorithm());
values.put(Keys.FINGERPRINT, key.getFingerprint()); values.put(Keys.FINGERPRINT, key.getFingerprint());
@@ -644,7 +650,7 @@ public class ProviderHelper {
if (publicRing.isSecret()) { if (publicRing.isSecret()) {
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET); 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; 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 this is null, there is an error in the log so we can just return
if (publicRing == null) { 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. // Canonicalize this keyring, to assert a number of assumptions made about it.
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent); canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
if (canPublicRing == null) { if (canPublicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
// Early breakout if nothing changed // Early breakout if nothing changed
if (Arrays.hashCode(publicRing.getEncoded()) if (Arrays.hashCode(publicRing.getEncoded())
== Arrays.hashCode(oldPublicRing.getEncoded())) { == Arrays.hashCode(oldPublicRing.getEncoded())) {
log(LogLevel.OK, LogType.MSG_IP_SUCCESS_IDENTICAL); log(LogLevel.OK, LogType.MSG_IP_SUCCESS_IDENTICAL);
return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog); return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, null);
} }
} catch (NotFoundException e) { } catch (NotFoundException e) {
// Not an issue, just means we are dealing with a new keyring. // 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. // Canonicalize this keyring, to assert a number of assumptions made about it.
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent); canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
if (canPublicRing == null) { 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 // Merge data from new public ring into secret one
secretRing = secretRing.merge(publicRing, mLog, mIndent); secretRing = secretRing.merge(publicRing, mLog, mIndent);
if (secretRing == null) { 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 // This has always been a secret key ring, this is a safe cast
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent); canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
if (canSecretRing == null) { if (canSecretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
} catch (NotFoundException e) { } catch (NotFoundException e) {
@@ -716,11 +722,11 @@ public class ProviderHelper {
} }
} }
return new SaveKeyringResult(result, mLog); return new SaveKeyringResult(result, mLog, canSecretRing);
} catch (IOException e) { } catch (IOException e) {
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC); 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 { } finally {
mIndent -= 1; mIndent -= 1;
} }
@@ -736,7 +742,7 @@ public class ProviderHelper {
if ( ! secretRing.isSecret()) { if ( ! secretRing.isSecret()) {
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC); 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; 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 this is null, there is an error in the log so we can just return
if (secretRing == null) { 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. // 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 // This is a safe cast, because we made sure this is a secret ring above
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent); canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
if (canSecretRing == null) { if (canSecretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
// Early breakout if nothing changed // Early breakout if nothing changed
@@ -765,7 +771,7 @@ public class ProviderHelper {
== Arrays.hashCode(oldSecretRing.getEncoded())) { == Arrays.hashCode(oldSecretRing.getEncoded())) {
log(LogLevel.OK, LogType.MSG_IS_SUCCESS_IDENTICAL, log(LogLevel.OK, LogType.MSG_IS_SUCCESS_IDENTICAL,
PgpKeyHelper.convertKeyIdToHex(masterKeyId) ); PgpKeyHelper.convertKeyIdToHex(masterKeyId) );
return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog); return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, null);
} }
} catch (NotFoundException e) { } catch (NotFoundException e) {
// Not an issue, just means we are dealing with a new keyring // 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 // This is a safe cast, because we made sure this is a secret ring above
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent); canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
if (canSecretRing == null) { 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 // Merge data from new secret ring into public one
publicRing = oldPublicRing.merge(secretRing, mLog, mIndent); publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);
if (publicRing == null) { if (publicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
} catch (NotFoundException e) { } catch (NotFoundException e) {
@@ -797,30 +803,289 @@ public class ProviderHelper {
CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent); CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
if (canPublicRing == null) { if (canPublicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
int result; int result;
result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true); result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true);
if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) { 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); progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
result = saveCanonicalizedSecretKeyRing(canSecretRing); result = saveCanonicalizedSecretKeyRing(canSecretRing);
return new SaveKeyringResult(result, mLog); return new SaveKeyringResult(result, mLog, canSecretRing);
} catch (IOException e) { } catch (IOException e) {
log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC); 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 { } finally {
mIndent -= 1; 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 * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
*/ */
@@ -863,9 +1128,7 @@ public class ProviderHelper {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
String version = PgpHelper.getVersionForHeader(mContext); String version = PgpHelper.getVersionForHeader(mContext);
if (version != null) {
keyRing.encodeArmored(bos, version); keyRing.encodeArmored(bos, version);
}
String armoredKey = bos.toString("UTF-8"); String armoredKey = bos.toString("UTF-8");
Log.d(Constants.TAG, "armoredKey:" + armoredKey); Log.d(Constants.TAG, "armoredKey:" + armoredKey);

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -23,6 +23,7 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.IBinder; import android.os.IBinder;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import org.openintents.openpgp.IOpenPgpService; import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpMetadata; 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.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; 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.ApiAccounts;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
@@ -56,12 +58,17 @@ import java.util.Set;
public class OpenPgpService extends RemoteService { public class OpenPgpService extends RemoteService {
static final String[] KEYRING_PROJECTION = static final String[] EMAIL_SEARCH_PROJECTION = new String[]{
new String[]{
KeyRings._ID, KeyRings._ID,
KeyRings.MASTER_KEY_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. * Search database for key ids based on emails.
* *
@@ -69,18 +76,20 @@ public class OpenPgpService extends RemoteService {
* @return * @return
*/ */
private Intent getKeyIdsFromEmails(Intent data, String[] encryptionUserIds) { private Intent getKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {
// find key ids to given emails in database boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0);
ArrayList<Long> keyIds = new ArrayList<Long>();
boolean missingUserIdsCheck = false; boolean missingUserIdsCheck = false;
boolean duplicateUserIdsCheck = false; boolean duplicateUserIdsCheck = false;
ArrayList<Long> keyIds = new ArrayList<Long>();
ArrayList<String> missingUserIds = new ArrayList<String>(); ArrayList<String> missingUserIds = new ArrayList<String>();
ArrayList<String> duplicateUserIds = new ArrayList<String>(); ArrayList<String> duplicateUserIds = new ArrayList<String>();
if (!noUserIdsCheck) {
for (String email : encryptionUserIds) { for (String email : encryptionUserIds) {
// try to find the key for this specific email
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email); Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
Cursor cursor = getContentResolver().query(uri, KEYRING_PROJECTION, null, null, null); Cursor cursor = getContentResolver().query(uri, EMAIL_SEARCH_PROJECTION, EMAIL_SEARCH_WHERE, null, null);
try { try {
// result should be one entry containing the key id
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID)); long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
keyIds.add(id); keyIds.add(id);
@@ -89,9 +98,14 @@ public class OpenPgpService extends RemoteService {
missingUserIds.add(email); missingUserIds.add(email);
Log.d(Constants.TAG, "user id missing"); 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()) { if (cursor != null && cursor.moveToNext()) {
duplicateUserIdsCheck = true; duplicateUserIdsCheck = true;
duplicateUserIds.add(email); duplicateUserIds.add(email);
// 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"); Log.d(Constants.TAG, "more than one user id with the same email");
} }
} finally { } finally {
@@ -100,21 +114,23 @@ public class OpenPgpService extends RemoteService {
} }
} }
} }
}
// convert to long[] // convert ArrayList<Long> to long[]
long[] keyIdsArray = new long[keyIds.size()]; long[] keyIdsArray = new long[keyIds.size()];
for (int i = 0; i < keyIdsArray.length; i++) { for (int i = 0; i < keyIdsArray.length; i++) {
keyIdsArray[i] = keyIds.get(i); keyIdsArray[i] = keyIds.get(i);
} }
if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) {
// allow the user to verify pub key selection // allow the user to verify pub key selection
if (missingUserIdsCheck || duplicateUserIdsCheck) {
// build PendingIntent
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS); intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray); 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_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); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
@@ -126,10 +142,11 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
return result; return result;
} } else {
// everything was easy, we have exactly one key for every email
if (keyIdsArray.length == 0) { if (keyIdsArray.length == 0) {
return null; Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!");
} }
Intent result = new Intent(); Intent result = new Intent();
@@ -137,6 +154,7 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result; return result;
} }
}
private Intent getNfcIntent(Intent data, byte[] hashToSign, int hashAlgo) { private Intent getNfcIntent(Intent data, byte[] hashToSign, int hashAlgo) {
// build PendingIntent for Yubikey NFC operations // build PendingIntent for Yubikey NFC operations
@@ -191,7 +209,7 @@ public class OpenPgpService extends RemoteService {
} catch (PassphraseCacheService.KeyNotFoundException e) { } catch (PassphraseCacheService.KeyNotFoundException e) {
// secret key that is set for this account is deleted? // secret key that is set for this account is deleted?
// show account config again! // show account config again!
return getCreateAccountIntent(data, data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME)); return getCreateAccountIntent(data, getAccountName(data));
} }
} }
if (passphrase == null) { if (passphrase == null) {
@@ -270,10 +288,9 @@ public class OpenPgpService extends RemoteService {
originalFilename = ""; originalFilename = "";
} }
long[] keyIds; // first try to get key ids from non-ambiguous key id extra
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) { long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS); if (keyIds == null) {
} else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) {
// get key ids based on given user ids // get key ids based on given user ids
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
// give params through to activity... // give params through to activity...
@@ -285,20 +302,8 @@ public class OpenPgpService extends RemoteService {
// if not success -> result contains a PendingIntent for user interaction // if not success -> result contains a PendingIntent for user interaction
return result; 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 // build InputData and write into OutputStream
// Get Input- and OutputStream from ParcelFileDescriptor // Get Input- and OutputStream from ParcelFileDescriptor
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
@@ -315,7 +320,8 @@ public class OpenPgpService extends RemoteService {
.setCompressionId(accSettings.getCompression()) .setCompressionId(accSettings.getCompression())
.setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm()) .setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
.setEncryptionMasterKeyIds(keyIds) .setEncryptionMasterKeyIds(keyIds)
.setOriginalFilename(originalFilename); .setOriginalFilename(originalFilename)
.setAdditionalEncryptId(accSettings.getKeyId()); // add acc key for encryption
if (sign) { if (sign) {
String passphrase; String passphrase;
@@ -334,9 +340,6 @@ public class OpenPgpService extends RemoteService {
builder.setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) builder.setSignatureHashAlgorithm(accSettings.getHashAlgorithm())
.setSignatureMasterKeyId(accSettings.getKeyId()) .setSignatureMasterKeyId(accSettings.getKeyId())
.setSignaturePassphrase(passphrase); .setSignaturePassphrase(passphrase);
} else {
// encrypt only
builder.setSignatureMasterKeyId(Constants.key.none);
} }
try { try {
@@ -448,7 +451,7 @@ public class OpenPgpService extends RemoteService {
// If signature is unknown we return an _additional_ PendingIntent // If signature is unknown we return an _additional_ PendingIntent
// to retrieve the missing key // to retrieve the missing key
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class); 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_KEY_ID, signatureResult.getKeyId());
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data); 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 // If keys are not in db we return an additional PendingIntent
// to retrieve the missing key // to retrieve the missing key
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class); 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_KEY_ID, masterKeyId);
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data); intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
@@ -596,6 +599,16 @@ public class OpenPgpService extends RemoteService {
return null; 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 // TODO: multi-threading
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() { private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
@@ -606,12 +619,7 @@ public class OpenPgpService extends RemoteService {
return errorResult; return errorResult;
} }
String accName; String accName = getAccountName(data);
if (data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME) != null) {
accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME);
} else {
accName = "default";
}
final AccountSettings accSettings = getAccSettings(accName); final AccountSettings accSettings = getAccSettings(accName);
if (accSettings == null) { if (accSettings == null) {
return getCreateAccountIntent(data, accName); return getCreateAccountIntent(data, accName);

View File

@@ -27,6 +27,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature; import android.content.pm.Signature;
import android.net.Uri; import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.text.TextUtils;
import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi;
@@ -160,7 +161,7 @@ public abstract class RemoteService extends Service {
*/ */
protected AccountSettings getAccSettings(String accountName) { protected AccountSettings getAccSettings(String accountName) {
String currentPkg = getCurrentCallingPackage(); String currentPkg = getCurrentCallingPackage();
Log.d(Constants.TAG, "accountName: " + accountName); Log.d(Constants.TAG, "getAccSettings accountName: "+ accountName);
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, 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) { protected Intent getCreateAccountIntent(Intent data, String accountName) {
String packageName = getCurrentCallingPackage(); String packageName = getCurrentCallingPackage();
Log.d(Constants.TAG, "accountName: " + accountName); Log.d(Constants.TAG, "getCreateAccountIntent accountName: " + accountName);
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT); intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT);

View File

@@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.service.OperationResultParcel;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
public class AccountSettingsActivity extends ActionBarActivity { public class AccountSettingsActivity extends ActionBarActivity {
@@ -106,4 +107,15 @@ public class AccountSettingsActivity extends ActionBarActivity {
finish(); 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);
}
}
} }

View File

@@ -36,6 +36,8 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings; 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.CreateKeyActivity;
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment; import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter; import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
@@ -177,24 +179,19 @@ public class AccountSettingsFragment extends Fragment implements
switch (requestCode) { switch (requestCode) {
case REQUEST_CODE_CREATE_KEY: { case REQUEST_CODE_CREATE_KEY: {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
// select newly created key if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) {
try { OperationResults.SaveKeyringResult result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT);
long masterKeyId = new ProviderHelper(getActivity()) mSelectKeyFragment.selectKey(result.mRingMasterKeyId);
.getCachedPublicKeyRing(data.getData()) } else {
.extractOrGetMasterKeyId(); Log.e(Constants.TAG, "missing result!");
mSelectKeyFragment.selectKey(masterKeyId);
} catch (PgpGeneralException e) {
Log.e(Constants.TAG, "key not found!", e);
} }
} }
break; break;
} }
}
default: // execute activity's onActivityResult to show log notify
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
break;
}
} }
/** /**

View File

@@ -18,11 +18,20 @@
package org.sufficientlysecure.keychain.remote.ui; package org.sufficientlysecure.keychain.remote.ui;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.support.v7.app.ActionBarActivity; 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.view.View;
import android.widget.TextView; 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.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.security.Provider;
import java.util.ArrayList; import java.util.ArrayList;
public class RemoteServiceActivity extends ActionBarActivity { public class RemoteServiceActivity extends ActionBarActivity {
@@ -68,7 +76,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
// select pub keys action // select pub keys action
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; 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_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 // error message
public static final String EXTRA_ERROR_MESSAGE = "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)) { } else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true);
ArrayList<String> missingUserIds = intent ArrayList<String> missingUserIds = intent
.getStringArrayListExtra(EXTRA_MISSING_USER_IDS); .getStringArrayListExtra(EXTRA_MISSING_USER_IDS);
ArrayList<String> dublicateUserIds = intent 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) { if (missingUserIds != null && missingUserIds.size() > 0) {
text += getString(R.string.api_select_pub_keys_missing_text); ssb.append("\n\n");
text += "<br/>"; ssb.append(getString(R.string.api_select_pub_keys_missing_text));
text += "<ul>"; ssb.append("\n");
for (String userId : missingUserIds) { 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) { if (dublicateUserIds != null && dublicateUserIds.size() > 0) {
text += getString(R.string.api_select_pub_keys_dublicates_text); ssb.append("\n\n");
text += "<br/>"; ssb.append(getString(R.string.api_select_pub_keys_dublicates_text));
text += "<ul>"; ssb.append("\n");
for (String userId : dublicateUserIds) { 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 // 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); setContentView(R.layout.api_remote_select_pub_keys);
// set text on view // set text on view
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text); TextView textView = (TextView) findViewById(R.id.api_select_pub_keys_text);
textView.setHtmlFromString(text, true); textView.setText(ssb, TextView.BufferType.SPANNABLE);
/* Load select pub keys fragment */ /* Load select pub keys fragment */
// Check that the activity is using the layout version with // Check that the activity is using the layout version with

View File

@@ -21,6 +21,8 @@ import android.accounts.Account;
import android.app.Service; import android.app.Service;
import android.content.AbstractThreadedSyncAdapter; import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SyncResult; import android.content.SyncResult;
import android.os.Bundle; import android.os.Bundle;
@@ -29,9 +31,11 @@ import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.provider.ContactsContract;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.helper.ContactHelper;
import org.sufficientlysecure.keychain.helper.EmailKeyHelper; import org.sufficientlysecure.keychain.helper.EmailKeyHelper;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@@ -42,7 +46,7 @@ public class ContactSyncAdapterService extends Service {
private class ContactSyncAdapter extends AbstractThreadedSyncAdapter { private class ContactSyncAdapter extends AbstractThreadedSyncAdapter {
private final AtomicBoolean importDone = new AtomicBoolean(false); // private final AtomicBoolean importDone = new AtomicBoolean(false);
public ContactSyncAdapter() { public ContactSyncAdapter() {
super(ContactSyncAdapterService.this, true); super(ContactSyncAdapterService.this, true);
@@ -51,47 +55,59 @@ public class ContactSyncAdapterService extends Service {
@Override @Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
final SyncResult syncResult) { final SyncResult syncResult) {
importDone.set(false); Log.d(Constants.TAG, "Performing a sync!");
KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this); // TODO: Import is currently disabled for 2.8, until we implement proper origin management
EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(), // importDone.set(false);
new Handler.Callback() { // KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this);
@Override // EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(),
public boolean handleMessage(Message msg) { // new Handler.Callback() {
Bundle data = msg.getData(); // @Override
switch (msg.arg1) { // public boolean handleMessage(Message msg) {
case KeychainIntentServiceHandler.MESSAGE_OKAY: // Bundle data = msg.getData();
Log.d(Constants.TAG, "Syncing... Done."); // switch (msg.arg1) {
synchronized (importDone) { // case KeychainIntentServiceHandler.MESSAGE_OKAY:
importDone.set(true); // Log.d(Constants.TAG, "Syncing... Done.");
importDone.notifyAll(); // synchronized (importDone) {
} // importDone.set(true);
return true; // importDone.notifyAll();
case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS: // }
if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) && // return true;
data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) { // case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS:
Log.d(Constants.TAG, "Syncing... Progress: " + // if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) &&
data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" + // data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) {
data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)); // Log.d(Constants.TAG, "Syncing... Progress: " +
return false; // data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" +
} // data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX));
default: // return false;
Log.d(Constants.TAG, "Syncing... " + msg.toString()); // }
return false; // default:
} // Log.d(Constants.TAG, "Syncing... " + msg.toString());
} // return false;
}))); // }
synchronized (importDone) { // }
try { // })));
if (!importDone.get()) importDone.wait(); // synchronized (importDone) {
} catch (InterruptedException e) { // try {
Log.w(Constants.TAG, e); // if (!importDone.get()) importDone.wait();
return; // } catch (InterruptedException e) {
} // Log.w(Constants.TAG, e);
} // return;
// }
// }
ContactHelper.writeKeysToContacts(ContactSyncAdapterService.this); 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 @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return new ContactSyncAdapter().getSyncAdapterBinder(); return new ContactSyncAdapter().getSyncAdapterBinder();

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * 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.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; 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.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper; 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.EditKeyResult;
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult; 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.FileImportCache;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; 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_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 */ /* keys for data bundle */
// encrypt, decrypt, import export // encrypt, decrypt, import export
@@ -139,8 +147,13 @@ public class KeychainIntentService extends IntentService
// delete file securely // delete file securely
public static final String DELETE_FILE = "deleteFile"; 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 // import key
public static final String IMPORT_KEY_LIST = "import_key_list"; public static final String IMPORT_KEY_LIST = "import_key_list";
public static final String IMPORT_KEY_FILE = "import_key_file";
// export key // export key
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream"; 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_PUB_KEY_ID = "sign_key_pub_key_id";
public static final String CERTIFY_KEY_UIDS = "sign_key_uids"; 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 * possible data keys as result send over messenger
*/ */
@@ -176,8 +193,6 @@ public class KeychainIntentService extends IntentService
// export // export
public static final String RESULT_EXPORT = "exported"; public static final String RESULT_EXPORT = "exported";
public static final String RESULT_IMPORT = "result";
Messenger mMessenger; Messenger mMessenger;
private boolean mIsCanceled; private boolean mIsCanceled;
@@ -246,26 +261,30 @@ public class KeychainIntentService extends IntentService
String originalFilename = getOriginalFilename(data); String originalFilename = getOriginalFilename(data);
/* Operation */ /* Operation */
PgpSignEncrypt.Builder builder = PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(
new PgpSignEncrypt.Builder(
new ProviderHelper(this), new ProviderHelper(this),
inputData, outStream); inputData, outStream
builder.setProgressable(this); );
builder.setProgressable(this)
builder.setEnableAsciiArmorOutput(useAsciiArmor) .setEnableAsciiArmorOutput(useAsciiArmor)
.setVersionHeader(PgpHelper.getVersionForHeader(this)) .setVersionHeader(PgpHelper.getVersionForHeader(this))
.setCompressionId(compressionId) .setCompressionId(compressionId)
.setSymmetricEncryptionAlgorithm( .setSymmetricEncryptionAlgorithm(
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
.setEncryptionMasterKeyIds(encryptionKeyIds) .setEncryptionMasterKeyIds(encryptionKeyIds)
.setSymmetricPassphrase(symmetricPassphrase) .setSymmetricPassphrase(symmetricPassphrase)
.setSignatureMasterKeyId(signatureKeyId) .setOriginalFilename(originalFilename);
.setEncryptToSigner(true)
.setSignatureHashAlgorithm( try {
Preferences.getPreferences(this).getDefaultHashAlgorithm()) builder.setSignatureMasterKeyId(signatureKeyId)
.setSignaturePassphrase( .setSignaturePassphrase(
PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)) PassphraseCacheService.getCachedPassphrase(this, signatureKeyId))
.setOriginalFilename(originalFilename); .setSignatureHashAlgorithm(
Preferences.getPreferences(this).getDefaultHashAlgorithm())
.setAdditionalEncryptId(signatureKeyId);
} catch (PassphraseCacheService.KeyNotFoundException e) {
// encrypt-only
}
// this assumes that the bytes are cleartext (valid for current implementation!) // this assumes that the bytes are cleartext (valid for current implementation!)
if (source == IO_BYTES) { if (source == IO_BYTES) {
@@ -391,23 +410,41 @@ public class KeychainIntentService extends IntentService
} }
/* Operation */ /* Operation */
ProviderHelper providerHelper = new ProviderHelper(this);
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 60, 100)); PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 60, 100));
EditKeyResult result; EditKeyResult modifyResult;
if (saveParcel.mMasterKeyId != null) { if (saveParcel.mMasterKeyId != null) {
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE); String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
CanonicalizedSecretKeyRing secRing = 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 { } 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 // cache new passphrase
if (saveParcel.mNewPassphrase != null) { if (saveParcel.mNewPassphrase != null) {
@@ -417,8 +454,11 @@ public class KeychainIntentService extends IntentService
setProgress(R.string.progress_done, 100, 100); setProgress(R.string.progress_done, 100, 100);
// make sure new data is synced into contacts
ContactSyncAdapterService.requestSync();
/* Output */ /* Output */
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult);
} catch (Exception e) { } catch (Exception e) {
sendErrorToHandler(e); sendErrorToHandler(e);
} }
@@ -454,17 +494,21 @@ public class KeychainIntentService extends IntentService
} else { } else {
// get entries from cached file // get entries from cached file
FileImportCache<ParcelableKeyRing> cache = FileImportCache<ParcelableKeyRing> cache =
new FileImportCache<ParcelableKeyRing>(this); new FileImportCache<ParcelableKeyRing>(this, "key_import.pcl");
entries = cache.readCacheIntoList(); 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); ImportKeyResult result = pgpImportExport.importKeyRings(entries);
Bundle resultData = new Bundle(); if (result.mSecret > 0) {
resultData.putParcelable(RESULT_IMPORT, result); 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) { } catch (Exception e) {
sendErrorToHandler(e); sendErrorToHandler(e);
} }
@@ -549,8 +593,9 @@ public class KeychainIntentService extends IntentService
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri); CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
PgpImportExport pgpImportExport = new PgpImportExport(this, null); PgpImportExport pgpImportExport = new PgpImportExport(this, null);
boolean uploaded = pgpImportExport.uploadKeyRingToServer(server, keyring); try {
if (!uploaded) { pgpImportExport.uploadKeyRingToServer(server, keyring);
} catch (Keyserver.AddKeyException e) {
throw new PgpGeneralException("Unable to export key to selected server"); throw new PgpGeneralException("Unable to export key to selected server");
} }
@@ -639,7 +684,56 @@ public class KeychainIntentService extends IntentService
} catch (Exception e) { } catch (Exception e) {
sendErrorToHandler(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) { private void sendErrorToHandler(Exception e) {

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * 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 ((resultType & OperationResultParcel.RESULT_ERROR) == 0) {
if (getLog().containsWarnings()) { if (getLog().containsWarnings()) {
duration = 0;
color = Style.ORANGE; color = Style.ORANGE;
} else { } else {
duration = SuperToast.Duration.LONG;
color = Style.GREEN; color = Style.GREEN;
} }
@@ -167,7 +166,6 @@ public class OperationResultParcel implements Parcelable {
} else { } else {
duration = 0;
color = Style.RED; color = Style.RED;
str = "operation failed"; str = "operation failed";
@@ -180,8 +178,8 @@ public class OperationResultParcel implements Parcelable {
button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD, button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD,
Style.getStyle(color, SuperToast.Animations.POPUP)); Style.getStyle(color, SuperToast.Animations.POPUP));
toast.setText(str); toast.setText(str);
toast.setDuration(duration); toast.setDuration(SuperToast.Duration.EXTRA_LONG);
toast.setIndeterminate(duration == 0); toast.setIndeterminate(false);
toast.setSwipeToDismiss(true); toast.setSwipeToDismiss(true);
// If we have a log and it's non-empty, show a View Log button // If we have a log and it's non-empty, show a View Log button
if (button) { if (button) {
@@ -289,6 +287,7 @@ public class OperationResultParcel implements Parcelable {
MSG_IS_SUCCESS (R.string.msg_is_success), MSG_IS_SUCCESS (R.string.msg_is_success),
// keyring canonicalization // keyring canonicalization
MSG_KC_V3_KEY (R.string.msg_kc_v3_key),
MSG_KC_PUBLIC (R.string.msg_kc_public), MSG_KC_PUBLIC (R.string.msg_kc_public),
MSG_KC_SECRET (R.string.msg_kc_secret), MSG_KC_SECRET (R.string.msg_kc_secret),
MSG_KC_FATAL_NO_UID (R.string.msg_kc_fatal_no_uid), 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_TIME (R.string.msg_kc_uid_bad_time),
MSG_KC_UID_BAD_TYPE (R.string.msg_kc_uid_bad_type), 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_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_DUP (R.string.msg_kc_uid_dup),
MSG_KC_UID_FOREIGN (R.string.msg_kc_uid_foreign), MSG_KC_UID_FOREIGN (R.string.msg_kc_uid_foreign),
MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert), MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert),
@@ -333,10 +333,11 @@ public class OperationResultParcel implements Parcelable {
// keyring consolidation // 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_PUBLIC (R.string.msg_mg_public),
MSG_MG_SECRET (R.string.msg_mg_secret), 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_NEW_SUBKEY (R.string.msg_mg_new_subkey),
MSG_MG_FOUND_NEW (R.string.msg_mg_found_new), MSG_MG_FOUND_NEW (R.string.msg_mg_found_new),
MSG_MG_UNCHANGED (R.string.msg_mg_unchanged), 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_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_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_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_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_UNKNOWN_ALGO (R.string.msg_cr_error_unknown_algo),
MSG_CR_ERROR_INTERNAL_PGP (R.string.msg_cr_error_internal_pgp), 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 // secret key modify
MSG_MF (R.string.msg_mr), 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_FINGERPRINT (R.string.msg_mf_error_fingerprint),
MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid), MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid),
MSG_MF_ERROR_INTEGRITY (R.string.msg_mf_error_integrity), 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_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_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_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 (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_REPLACE_OLD (R.string.msg_mf_primary_replace_old),
MSG_MF_PRIMARY_NEW (R.string.msg_mf_primary_new), MSG_MF_PRIMARY_NEW (R.string.msg_mf_primary_new),
MSG_MF_SUBKEY_CHANGE (R.string.msg_mf_subkey_change), 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_ID (R.string.msg_mf_subkey_new_id),
MSG_MF_SUBKEY_NEW (R.string.msg_mf_subkey_new), 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_SUBKEY_REVOKE (R.string.msg_mf_subkey_revoke),
MSG_MF_SUCCESS (R.string.msg_mf_success), MSG_MF_SUCCESS (R.string.msg_mf_success),
MSG_MF_UID_ADD (R.string.msg_mf_uid_add), 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_UID_ERROR_EMPTY (R.string.msg_mf_uid_error_empty),
MSG_MF_UNLOCK_ERROR (R.string.msg_mf_unlock_error), MSG_MF_UNLOCK_ERROR (R.string.msg_mf_unlock_error),
MSG_MF_UNLOCK (R.string.msg_mf_unlock), 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; private final int mMsgId;
@@ -406,8 +448,10 @@ public class OperationResultParcel implements Parcelable {
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mResult); dest.writeInt(mResult);
if (mLog != null) {
dest.writeTypedList(mLog.toList()); dest.writeTypedList(mLog.toList());
} }
}
public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() { public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() {
public OperationResultParcel createFromParcel(final Parcel source) { public OperationResultParcel createFromParcel(final Parcel source) {
@@ -432,6 +476,15 @@ public class OperationResultParcel implements Parcelable {
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null)); 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() { public boolean containsWarnings() {
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) { for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) {
if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) { if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) {

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * 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.OnClickWrapper;
import com.github.johnpersano.supertoasts.util.Style; import com.github.johnpersano.supertoasts.util.Style;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
import org.sufficientlysecure.keychain.ui.LogDisplayFragment; import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
@@ -37,7 +41,7 @@ public abstract class OperationResults {
public static class ImportKeyResult extends OperationResultParcel { public static class ImportKeyResult extends OperationResultParcel {
public final int mNewKeys, mUpdatedKeys, mBadKeys; public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret;
// At least one new key // At least one new key
public static final int RESULT_OK_NEWKEYS = 2; public static final int RESULT_OK_NEWKEYS = 2;
@@ -55,12 +59,15 @@ public abstract class OperationResults {
return (mResult & (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED)) return (mResult & (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED))
== (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED); == (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED);
} }
public boolean isOkNew() { public boolean isOkNew() {
return (mResult & RESULT_OK_NEWKEYS) == RESULT_OK_NEWKEYS; return (mResult & RESULT_OK_NEWKEYS) == RESULT_OK_NEWKEYS;
} }
public boolean isOkUpdated() { public boolean isOkUpdated() {
return (mResult & RESULT_OK_UPDATED) == RESULT_OK_UPDATED; return (mResult & RESULT_OK_UPDATED) == RESULT_OK_UPDATED;
} }
public boolean isFailNothing() { public boolean isFailNothing() {
return (mResult & RESULT_FAIL_NOTHING) == RESULT_FAIL_NOTHING; return (mResult & RESULT_FAIL_NOTHING) == RESULT_FAIL_NOTHING;
} }
@@ -70,14 +77,16 @@ public abstract class OperationResults {
mNewKeys = source.readInt(); mNewKeys = source.readInt();
mUpdatedKeys = source.readInt(); mUpdatedKeys = source.readInt();
mBadKeys = source.readInt(); mBadKeys = source.readInt();
mSecret = source.readInt();
} }
public ImportKeyResult(int result, OperationLog log, public ImportKeyResult(int result, OperationLog log,
int newKeys, int updatedKeys, int badKeys) { int newKeys, int updatedKeys, int badKeys, int secret) {
super(result, log); super(result, log);
mNewKeys = newKeys; mNewKeys = newKeys;
mUpdatedKeys = updatedKeys; mUpdatedKeys = updatedKeys;
mBadKeys = badKeys; mBadKeys = badKeys;
mSecret = secret;
} }
@Override @Override
@@ -86,6 +95,7 @@ public abstract class OperationResults {
dest.writeInt(mNewKeys); dest.writeInt(mNewKeys);
dest.writeInt(mUpdatedKeys); dest.writeInt(mUpdatedKeys);
dest.writeInt(mBadKeys); dest.writeInt(mBadKeys);
dest.writeInt(mSecret);
} }
public static Creator<ImportKeyResult> CREATOR = new Creator<ImportKeyResult>() { public static Creator<ImportKeyResult> CREATOR = new Creator<ImportKeyResult>() {
@@ -185,13 +195,13 @@ public abstract class OperationResults {
public static class EditKeyResult extends OperationResultParcel { public static class EditKeyResult extends OperationResultParcel {
private transient UncachedKeyRing mRing; private transient UncachedKeyRing mRing;
public final Long mRingMasterKeyId; public final long mRingMasterKeyId;
public EditKeyResult(int result, OperationLog log, public EditKeyResult(int result, OperationLog log,
UncachedKeyRing ring) { UncachedKeyRing ring) {
super(result, log); super(result, log);
mRing = ring; mRing = ring;
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : null; mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none;
} }
public UncachedKeyRing getRing() { public UncachedKeyRing getRing() {
@@ -224,8 +234,12 @@ public abstract class OperationResults {
public static class SaveKeyringResult extends OperationResultParcel { 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); super(result, log);
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none;
} }
// Some old key was updated // Some old key was updated
@@ -240,6 +254,34 @@ public abstract class OperationResults {
return (mResult & UPDATED) == UPDATED; 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);
}
} }
} }

View File

@@ -78,7 +78,7 @@ public class PassphraseCacheService extends Service {
private static final int NOTIFICATION_ID = 1; 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_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; private BroadcastReceiver mIntentReceiver;
@@ -170,7 +170,7 @@ public class PassphraseCacheService extends Service {
switch (returnMessage.what) { switch (returnMessage.what) {
case MSG_PASSPHRASE_CACHE_GET_OKAY: case MSG_PASSPHRASE_CACHE_GET_OKAY:
return returnMessage.getData().getString(EXTRA_PASSPHRASE); 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(); throw new KeyNotFoundException();
default: default:
throw new KeyNotFoundException("should not happen!"); throw new KeyNotFoundException("should not happen!");
@@ -322,7 +322,7 @@ public class PassphraseCacheService extends Service {
msg.setData(bundle); msg.setData(bundle);
} catch (ProviderHelper.NotFoundException e) { } catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "PassphraseCacheService: Passphrase for unknown key was requested!"); 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 { try {

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * 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, // performance gain for using Parcelable here would probably be negligible,
// use Serializable instead. // use Serializable instead.
public static class SubkeyAdd implements Serializable { public static class SubkeyAdd implements Serializable {
public int mAlgorithm; public Algorithm mAlgorithm;
public int mKeysize; public Integer mKeySize;
public Curve mCurve;
public int mFlags; public int mFlags;
public Long mExpiry; 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; mAlgorithm = algorithm;
mKeysize = keysize; mKeySize = keySize;
mCurve = curve;
mFlags = flags; mFlags = flags;
mExpiry = expiry; mExpiry = expiry;
} }
@@ -94,7 +97,8 @@ public class SaveKeyringParcel implements Parcelable {
@Override @Override
public String toString() { public String toString() {
String out = "mAlgorithm: " + mAlgorithm + ", "; String out = "mAlgorithm: " + mAlgorithm + ", ";
out += "mKeysize: " + mKeysize + ", "; out += "mKeySize: " + mKeySize + ", ";
out += "mCurve: " + mCurve + ", ";
out += "mFlags: " + mFlags; out += "mFlags: " + mFlags;
out += "mExpiry: " + mExpiry; out += "mExpiry: " + mExpiry;
@@ -213,4 +217,20 @@ public class SaveKeyringParcel implements Parcelable {
return out; 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
}
} }

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2011 Senecaso * Copyright (C) 2011 Senecaso
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; 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.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.OperationResultParcel; import org.sufficientlysecure.keychain.service.OperationResultParcel;
import org.sufficientlysecure.keychain.service.OperationResults;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; 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.Log;
import org.sufficientlysecure.keychain.util.Notify; 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 * Signs the specified public key with the specified secret master key
*/ */
public class CertifyKeyActivity extends ActionBarActivity implements public class CertifyKeyActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks<Cursor> {
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {
private View mCertifyButton; private View mCertifyButton;
private ImageView mActionCertifyImage; private ImageView mActionCertifyImage;
private CheckBox mUploadKeyCheckbox; private CheckBox mUploadKeyCheckbox;
private Spinner mSelectKeyserverSpinner; private Spinner mSelectKeyserverSpinner;
private ScrollView mScrollView;
private SelectSecretKeyLayoutFragment mSelectKeyFragment; private CertifyKeySpinner mCertifyKeySpinner;
private Uri mDataUri; private Uri mDataUri;
private long mPubKeyId = 0; private long mPubKeyId = Constants.key.none;
private long mMasterKeyId = 0; private long mMasterKeyId = Constants.key.none;
private ListView mUserIds; private ListView mUserIds;
private UserIdsAdapter mUserIdsAdapter; private UserIdsAdapter mUserIdsAdapter;
@@ -89,20 +94,24 @@ public class CertifyKeyActivity extends ActionBarActivity implements
setContentView(R.layout.certify_key_activity); setContentView(R.layout.certify_key_activity);
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getSupportFragmentManager() mCertifyKeySpinner = (CertifyKeySpinner) findViewById(R.id.certify_key_spinner);
.findFragmentById(R.id.sign_key_select_key_fragment);
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox); mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox);
mCertifyButton = findViewById(R.id.certify_key_certify_button); mCertifyButton = findViewById(R.id.certify_key_certify_button);
mActionCertifyImage = (ImageView) findViewById(R.id.certify_key_action_certify_image); mActionCertifyImage = (ImageView) findViewById(R.id.certify_key_action_certify_image);
mUserIds = (ListView) findViewById(R.id.view_key_user_ids); mUserIds = (ListView) findViewById(R.id.view_key_user_ids);
mScrollView = (ScrollView) findViewById(R.id.certify_scroll_view);
// make certify image gray, like action icons // make certify image gray, like action icons
mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light), mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
PorterDuff.Mode.SRC_IN); PorterDuff.Mode.SRC_IN);
mSelectKeyFragment.setCallback(this); mCertifyKeySpinner.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
mSelectKeyFragment.setFilterCertify(true); @Override
public void onKeyChanged(long masterKeyId) {
mMasterKeyId = masterKeyId;
}
});
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, Preferences.getPreferences(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) { public void onClick(View v) {
if (mPubKeyId != 0) { if (mPubKeyId != 0) {
if (mMasterKeyId == 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.showNotify(CertifyKeyActivity.this, getString(R.string.select_key_to_certify),
Notify.Style.ERROR); Notify.Style.ERROR);
scrollUp();
} else { } else {
initiateCertifying(); initiateCertifying();
} }
@@ -162,6 +171,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements
getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); 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 USER_IDS_SELECTION = UserIds.IS_REVOKED + " = 0";
static final String[] KEYRING_PROJECTION = static final String[] KEYRING_PROJECTION =
@@ -199,6 +216,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
if (data.moveToFirst()) { if (data.moveToFirst()) {
// TODO: put findViewById in onCreate! // TODO: put findViewById in onCreate!
mPubKeyId = data.getLong(INDEX_MASTER_KEY_ID); mPubKeyId = data.getLong(INDEX_MASTER_KEY_ID);
mCertifyKeySpinner.setHiddenMasterKeyId(mPubKeyId);
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(mPubKeyId); String keyIdStr = PgpKeyHelper.convertKeyIdToHex(mPubKeyId);
((TextView) findViewById(R.id.key_id)).setText(keyIdStr); ((TextView) findViewById(R.id.key_id)).setText(keyIdStr);
@@ -213,6 +231,9 @@ public class CertifyKeyActivity extends ActionBarActivity implements
break; break;
case LOADER_ID_USER_IDS: case LOADER_ID_USER_IDS:
mUserIdsAdapter.swapCursor(data); 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; break;
} }
} }
@@ -292,8 +313,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
Notify.showNotify(CertifyKeyActivity.this, R.string.key_certify_success, // Notify.showNotify(CertifyKeyActivity.this, R.string.key_certify_success,
Notify.Style.INFO); // 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 // check if we need to send the key to the server or not
if (mUploadKeyCheckbox.isChecked()) { if (mUploadKeyCheckbox.isChecked()) {
@@ -345,13 +372,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
Intent intent = new Intent(); //Notify.showNotify(CertifyKeyActivity.this, R.string.key_send_success,
intent.putExtra(OperationResultParcel.EXTRA_RESULT, message.getData()); //Notify.Style.INFO);
Notify.showNotify(CertifyKeyActivity.this, R.string.key_send_success,
Notify.Style.INFO);
setResult(RESULT_OK); OperationResultParcel result = new OperationResultParcel(OperationResultParcel.RESULT_OK, null);
finish(); 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); startService(intent);
} }
/**
* callback from select key fragment
*/
@Override
public void onKeySelected(long secretKeyId) {
mMasterKeyId = secretKeyId;
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {

View File

@@ -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);
}
}

View File

@@ -33,6 +33,7 @@ import android.widget.TextView;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.KeyRing; 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.OperationResultParcel;
import org.sufficientlysecure.keychain.service.OperationResults; import org.sufficientlysecure.keychain.service.OperationResults;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; 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; import org.sufficientlysecure.keychain.util.Notify;
public class CreateKeyFinalFragment extends Fragment { public class CreateKeyFinalFragment extends Fragment {
@@ -125,10 +128,9 @@ public class CreateKeyFinalFragment extends Fragment {
Intent intent = new Intent(getActivity(), KeychainIntentService.class); Intent intent = new Intent(getActivity(), KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING); intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
// Message is received after importing is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
getActivity(), getActivity(),
getString(R.string.progress_importing), getString(R.string.progress_building_key),
ProgressDialog.STYLE_HORIZONTAL) { ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first // handle messages by standard KeychainIntentServiceHandler first
@@ -140,27 +142,22 @@ public class CreateKeyFinalFragment extends Fragment {
if (returnData == null) { if (returnData == null) {
return; return;
} }
final OperationResults.EditKeyResult result = final OperationResults.SaveKeyringResult result =
returnData.getParcelable(OperationResultParcel.EXTRA_RESULT); returnData.getParcelable(OperationResultParcel.EXTRA_RESULT);
if (result == null) { if (result == null) {
Log.e(Constants.TAG, "result == null");
return; return;
} }
if (result.getResult() == OperationResultParcel.RESULT_OK) {
if (mUploadCheckbox.isChecked()) { if (mUploadCheckbox.isChecked()) {
// result will be displayed after upload // result will be displayed after upload
uploadKey(result); uploadKey(result);
} else { } else {
// TODO: return result Intent data = new Intent();
result.createNotify(getActivity()); data.putExtra(OperationResultParcel.EXTRA_RESULT, result);
getActivity().setResult(Activity.RESULT_OK, data);
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish(); getActivity().finish();
} }
} else {
// display result on error without finishing activity
result.createNotify(getActivity());
}
} }
} }
}; };
@@ -169,9 +166,12 @@ public class CreateKeyFinalFragment extends Fragment {
Bundle data = new Bundle(); Bundle data = new Bundle();
SaveKeyringParcel parcel = new SaveKeyringParcel(); SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.CERTIFY_OTHER, null)); parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.SIGN_DATA, null)); Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L));
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.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); String userId = KeyRing.createUserId(mName, mEmail, null);
parcel.mAddUserIds.add(userId); parcel.mAddUserIds.add(userId);
parcel.mChangePrimaryUserId = userId; parcel.mChangePrimaryUserId = userId;
@@ -191,7 +191,7 @@ public class CreateKeyFinalFragment extends Fragment {
getActivity().startService(intent); 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 // Send all information needed to service to upload key in other thread
final Intent intent = new Intent(getActivity(), KeychainIntentService.class); final Intent intent = new Intent(getActivity(), KeychainIntentService.class);
@@ -199,7 +199,7 @@ public class CreateKeyFinalFragment extends Fragment {
// set data uri as path to keyring // set data uri as path to keyring
Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
editKeyResult.mRingMasterKeyId); saveKeyResult.mRingMasterKeyId);
intent.setData(blobUri); intent.setData(blobUri);
// fill values for this action // fill values for this action
@@ -211,7 +211,6 @@ public class CreateKeyFinalFragment extends Fragment {
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after uploading is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
getString(R.string.progress_uploading), ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_uploading), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
@@ -219,20 +218,16 @@ public class CreateKeyFinalFragment extends Fragment {
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// TODO: not supported by upload? // TODO: upload operation needs a result!
// TODO: then combine these results
//if (result.getResult() == OperationResultParcel.RESULT_OK) { //if (result.getResult() == OperationResultParcel.RESULT_OK) {
// TODO: return result //Notify.showNotify(getActivity(), R.string.key_send_success,
editKeyResult.createNotify(getActivity()); //Notify.Style.INFO);
Notify.showNotify(getActivity(), R.string.key_send_success, Intent data = new Intent();
Notify.Style.INFO); data.putExtra(OperationResultParcel.EXTRA_RESULT, saveKeyResult);
getActivity().setResult(Activity.RESULT_OK, data);
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish(); getActivity().finish();
// } else {
// // display result on error without finishing activity
// editKeyResult.createNotify(getActivity());
// }
} }
} }
}; };

View File

@@ -48,8 +48,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.OperationResults; import org.sufficientlysecure.keychain.service.OperationResultParcel;
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
@@ -340,7 +339,8 @@ public class EditKeyFragment extends LoaderFragment implements
} else { } else {
mSaveKeyringParcel.mRevokeUserIds.add(userId); mSaveKeyringParcel.mRevokeUserIds.add(userId);
// not possible to revoke and change to primary user id // 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; mSaveKeyringParcel.mChangePrimaryUserId = null;
} }
} }
@@ -407,10 +407,10 @@ public class EditKeyFragment extends LoaderFragment implements
@Override @Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
switch (message.what) { switch (message.what) {
case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE: case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY:
Long expiry = (Long) message.getData(). mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry =
getSerializable(EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY_DATE); (Long) message.getData().getSerializable(
mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = expiry; EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY);
break; break;
} }
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
@@ -504,7 +504,6 @@ public class EditKeyFragment extends LoaderFragment implements
private void save(String passphrase) { private void save(String passphrase) {
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString()); Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString());
// Message is received after importing is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
getActivity(), getActivity(),
getString(R.string.progress_saving), getString(R.string.progress_saving),
@@ -520,8 +519,8 @@ public class EditKeyFragment extends LoaderFragment implements
if (returnData == null) { if (returnData == null) {
return; return;
} }
final OperationResults.EditKeyResult result = final OperationResultParcel result =
returnData.getParcelable(EditKeyResult.EXTRA_RESULT); returnData.getParcelable(OperationResultParcel.EXTRA_RESULT);
if (result == null) { if (result == null) {
return; return;
} }
@@ -534,7 +533,7 @@ public class EditKeyFragment extends LoaderFragment implements
// if good -> finish, return result to showkey and display there! // if good -> finish, return result to showkey and display there!
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra(EditKeyResult.EXTRA_RESULT, result); intent.putExtra(OperationResultParcel.EXTRA_RESULT, result);
getActivity().setResult(EditKeyActivity.RESULT_OK, intent); getActivity().setResult(EditKeyActivity.RESULT_OK, intent);
getActivity().finish(); getActivity().finish();

View File

@@ -73,16 +73,13 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryption_key_ids"; public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryption_key_ids";
// view // view
ViewPager mViewPagerMode; private int mCurrentMode = PAGER_MODE_ASYMMETRIC;
//PagerTabStrip mPagerTabStripMode; private ViewPager mViewPagerContent;
PagerTabStripAdapter mTabsAdapterMode; private PagerTabStrip mPagerTabStripContent;
ViewPager mViewPagerContent; private PagerTabStripAdapter mTabsAdapterContent;
PagerTabStrip mPagerTabStripContent;
PagerTabStripAdapter mTabsAdapterContent;
// tabs // tabs
int mSwitchToMode = PAGER_MODE_ASYMMETRIC; private int mSwitchToContent = PAGER_CONTENT_MESSAGE;
int mSwitchToContent = PAGER_CONTENT_MESSAGE;
private static final int PAGER_MODE_ASYMMETRIC = 0; private static final int PAGER_MODE_ASYMMETRIC = 0;
private static final int PAGER_MODE_SYMMETRIC = 1; private static final int PAGER_MODE_SYMMETRIC = 1;
@@ -102,7 +99,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
private String mMessage = ""; private String mMessage = "";
public boolean isModeSymmetric() { public boolean isModeSymmetric() {
return PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem(); return PAGER_MODE_SYMMETRIC == mCurrentMode;
} }
public boolean isContentMessage() { public boolean isContentMessage() {
@@ -312,59 +309,60 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
String title = isContentMessage() ? getString(R.string.title_share_message) String title = isContentMessage() ? getString(R.string.title_share_message)
: getString(R.string.title_share_file); : getString(R.string.title_share_file);
// fallback on Android 2.3, otherwise we would get weird results // Disabled, produced an empty list on Huawei U8860 with Android Version 4.0.3
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // // fallback on Android 2.3, otherwise we would get weird results
return Intent.createChooser(prototype, title); // 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"}; // // prevent recursion aka Inception :P
// String[] blacklist = new String[]{Constants.PACKAGE_NAME + ".ui.EncryptActivity"};
List<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>(); //
// List<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>();
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(prototype, 0); //
List<ResolveInfo> resInfoListFiltered = new ArrayList<ResolveInfo>(); // List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(prototype, 0);
if (!resInfoList.isEmpty()) { // List<ResolveInfo> resInfoListFiltered = new ArrayList<ResolveInfo>();
for (ResolveInfo resolveInfo : resInfoList) { // if (!resInfoList.isEmpty()) {
// do not add blacklisted ones // for (ResolveInfo resolveInfo : resInfoList) {
if (resolveInfo.activityInfo == null || Arrays.asList(blacklist).contains(resolveInfo.activityInfo.name)) // // do not add blacklisted ones
continue; // if (resolveInfo.activityInfo == null || Arrays.asList(blacklist).contains(resolveInfo.activityInfo.name))
// continue;
resInfoListFiltered.add(resolveInfo); //
} // resInfoListFiltered.add(resolveInfo);
// }
if (!resInfoListFiltered.isEmpty()) { //
// sorting for nice readability // if (!resInfoListFiltered.isEmpty()) {
Collections.sort(resInfoListFiltered, new Comparator<ResolveInfo>() { // // sorting for nice readability
@Override // Collections.sort(resInfoListFiltered, new Comparator<ResolveInfo>() {
public int compare(ResolveInfo first, ResolveInfo second) { // @Override
String firstName = first.loadLabel(getPackageManager()).toString(); // public int compare(ResolveInfo first, ResolveInfo second) {
String secondName = second.loadLabel(getPackageManager()).toString(); // String firstName = first.loadLabel(getPackageManager()).toString();
return firstName.compareToIgnoreCase(secondName); // String secondName = second.loadLabel(getPackageManager()).toString();
} // return firstName.compareToIgnoreCase(secondName);
}); // }
// });
// create the custom intent list //
for (ResolveInfo resolveInfo : resInfoListFiltered) { // // create the custom intent list
Intent targetedShareIntent = (Intent) prototype.clone(); // for (ResolveInfo resolveInfo : resInfoListFiltered) {
targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName); // Intent targetedShareIntent = (Intent) prototype.clone();
targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name); // targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName);
// targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
LabeledIntent lIntent = new LabeledIntent(targetedShareIntent, //
resolveInfo.activityInfo.packageName, // LabeledIntent lIntent = new LabeledIntent(targetedShareIntent,
resolveInfo.loadLabel(getPackageManager()), // resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.icon); // resolveInfo.loadLabel(getPackageManager()),
targetedShareIntents.add(lIntent); // resolveInfo.activityInfo.icon);
} // targetedShareIntents.add(lIntent);
// }
// Create chooser with only one Intent in it //
Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title); // // Create chooser with only one Intent in it
// append all other Intents // Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title);
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{})); // // append all other Intents
return chooserIntent; // chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{}));
} // return chooserIntent;
// }
} //
// }
// fallback to Android's default chooser // fallback to Android's default chooser
return Intent.createChooser(prototype, title); return Intent.createChooser(prototype, title);
@@ -385,7 +383,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris); sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
} }
sendIntent.setType("application/pgp-encrypted"); sendIntent.setType("application/octet-stream");
} }
if (!isModeSymmetric() && mEncryptionUserIds != null) { if (!isModeSymmetric() && mEncryptionUserIds != null) {
Set<String> users = new HashSet<String>(); Set<String> users = new HashSet<String>();
@@ -474,13 +472,9 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
} }
private void initView() { 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); mViewPagerContent = (ViewPager) findViewById(R.id.encrypt_pager_content);
mPagerTabStripContent = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_content); mPagerTabStripContent = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_content);
mTabsAdapterMode = new PagerTabStripAdapter(this);
mViewPagerMode.setAdapter(mTabsAdapterMode);
mTabsAdapterContent = new PagerTabStripAdapter(this); mTabsAdapterContent = new PagerTabStripAdapter(this);
mViewPagerContent.setAdapter(mTabsAdapterContent); mViewPagerContent.setAdapter(mTabsAdapterContent);
} }
@@ -502,10 +496,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
// Handle intent actions // Handle intent actions
handleActions(getIntent()); handleActions(getIntent());
updateModeFragment();
mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class, null, getString(R.string.label_asymmetric));
mTabsAdapterMode.addTab(EncryptSymmetricFragment.class, null, getString(R.string.label_symmetric));
mViewPagerMode.setCurrentItem(mSwitchToMode);
mTabsAdapterContent.addTab(EncryptMessageFragment.class, null, getString(R.string.label_message)); mTabsAdapterContent.addTab(EncryptMessageFragment.class, null, getString(R.string.label_message));
mTabsAdapterContent.addTab(EncryptFileFragment.class, null, getString(R.string.label_files)); 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); 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 @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.isCheckable()) { if (item.isCheckable()) {
@@ -528,9 +529,8 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
} }
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.check_use_symmetric: case R.id.check_use_symmetric:
mSwitchToMode = item.isChecked() ? PAGER_MODE_SYMMETRIC : PAGER_MODE_ASYMMETRIC; mCurrentMode = item.isChecked() ? PAGER_MODE_SYMMETRIC : PAGER_MODE_ASYMMETRIC;
updateModeFragment();
mViewPagerMode.setCurrentItem(mSwitchToMode);
notifyUpdate(); notifyUpdate();
break; break;
case R.id.check_use_armor: case R.id.check_use_armor:
@@ -604,7 +604,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn
mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
// preselect keys given by intent // preselect keys given by intent
mSwitchToMode = PAGER_MODE_ASYMMETRIC; mCurrentMode = PAGER_MODE_ASYMMETRIC;
/** /**
* Main Actions * Main Actions

View File

@@ -18,35 +18,22 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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 com.tokenautocomplete.TokenCompleteTextView;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList; import java.util.ArrayList;
@@ -54,22 +41,20 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
public class EncryptAsymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener { 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; ProviderHelper mProviderHelper;
// view // view
private Spinner mSign; private KeySpinner mSign;
private EncryptKeyCompletionView mEncryptKeyView; private EncryptKeyCompletionView mEncryptKeyView;
private SelectSignKeyCursorAdapter mSignAdapter = new SelectSignKeyCursorAdapter();
// model // model
private EncryptActivityInterface mEncryptInterface; private EncryptActivityInterface mEncryptInterface;
@Override @Override
public void onNotifyUpdate() { public void onNotifyUpdate() {
if (mSign != null) {
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
}
} }
@Override @Override
@@ -101,17 +86,11 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false); View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
mSign = (Spinner) view.findViewById(R.id.sign); mSign = (KeySpinner) view.findViewById(R.id.sign);
mSign.setAdapter(mSignAdapter); mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
mSign.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onKeyChanged(long masterKeyId) {
setSignatureKeyId(parent.getAdapter().getItemId(position)); setSignatureKeyId(masterKeyId);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
setSignatureKeyId(Constants.key.none);
} }
}); });
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
@@ -128,42 +107,6 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
// preselect keys given // preselect keys given
preselectKeys(); 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() { mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
@Override @Override
public void onTokenAdded(Object token) { public void onTokenAdded(Object token) {
@@ -194,6 +137,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
KeyRings.buildUnifiedKeyRingUri(signatureKey)); KeyRings.buildUnifiedKeyRingUri(signatureKey));
if(keyring.hasAnySecret()) { if(keyring.hasAnySecret()) {
setSignatureKeyId(keyring.getMasterKeyId()); setSignatureKeyId(keyring.getMasterKeyId());
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
} }
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
Log.e(Constants.TAG, "key not found!", 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); Log.e(Constants.TAG, "key not found!", e);
} }
} }
// This is to work-around a rendering bug in TokenCompleteTextView
mEncryptKeyView.requestFocus();
updateEncryptionKeys(); updateEncryptionKeys();
} }
} }
@@ -233,95 +179,4 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
setEncryptionKeyIds(keyIdsArr); setEncryptionKeyIds(keyIdsArr);
setEncryptionUserIds(userIds.toArray(new String[userIds.size()])); 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;
}
}
} }

View File

@@ -51,7 +51,7 @@ public class FirstTimeActivity extends ActionBarActivity {
mSkipSetup.setOnClickListener(new View.OnClickListener() { mSkipSetup.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { 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 (requestCode == REQUEST_CODE_CREATE_OR_IMPORT_KEY) {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
finishSetup(); finishSetup(data);
} }
} else { } else {
Log.e(Constants.TAG, "No valid request code!"); Log.e(Constants.TAG, "No valid request code!");
} }
} }
private void finishSetup() { private void finishSetup(Intent srcData) {
Preferences prefs = Preferences.getPreferences(this); Preferences prefs = Preferences.getPreferences(this);
prefs.setFirstTime(false); prefs.setFirstTime(false);
Intent intent = new Intent(FirstTimeActivity.this, KeyListActivity.class); Intent intent = new Intent(this, KeyListActivity.class);
startActivity(intent); // give intent through to display notify
if (srcData != null) {
intent.putExtras(srcData);
}
startActivityForResult(intent, 0);
finish(); finish();
} }
} }

View File

@@ -45,6 +45,7 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.OperationResultParcel;
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult; import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
@@ -64,7 +65,7 @@ public class ImportKeysActivity extends ActionBarActivity {
+ "IMPORT_KEY_FROM_KEYSERVER"; + "IMPORT_KEY_FROM_KEYSERVER";
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT = public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT =
Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_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"; + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
public static final String ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN = Constants.INTENT_PREFIX public static final String ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_FILE_AND_RETURN"; + "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_KEY_ID = "key_id";
public static final String EXTRA_FINGERPRINT = "fingerprint"; 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"; public static final String EXTRA_PENDING_INTENT_DATA = "data";
private Intent mPendingIntentData; private Intent mPendingIntentData;
@@ -170,7 +171,7 @@ public class ImportKeysActivity extends ActionBarActivity {
startListFragment(savedInstanceState, importData, null, null); startListFragment(savedInstanceState, importData, null, null);
} }
} else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action) } 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)) { || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(action)) {
// only used for OpenPgpService // only used for OpenPgpService
@@ -287,8 +288,10 @@ public class ImportKeysActivity extends ActionBarActivity {
@Override @Override
public void onPageSelected(int position) { public void onPageSelected(int position) {
// cancel loader and clear list // cancel loader and clear list
if (mListFragment != null) {
mListFragment.destroyLoader(); mListFragment.destroyLoader();
} }
}
@Override @Override
public void onPageScrollStateChanged(int state) { public void onPageScrollStateChanged(int state) {
@@ -456,28 +459,25 @@ public class ImportKeysActivity extends ActionBarActivity {
return; return;
} }
final ImportKeyResult result = final ImportKeyResult result =
returnData.getParcelable(KeychainIntentService.RESULT_IMPORT); returnData.getParcelable(OperationResultParcel.EXTRA_RESULT);
if (result == null) { if (result == null) {
Log.e(Constants.TAG, "result == null");
return; 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 intent = new Intent();
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
ImportKeysActivity.this.setResult(RESULT_OK, intent); ImportKeysActivity.this.setResult(RESULT_OK, intent);
ImportKeysActivity.this.finish(); ImportKeysActivity.this.finish();
return; 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.setResult(RESULT_OK, mPendingIntentData);
ImportKeysActivity.this.finish(); ImportKeysActivity.this.finish();
return; 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(); result.createNotify(ImportKeysActivity.this).show();
} }
@@ -503,7 +503,8 @@ public class ImportKeysActivity extends ActionBarActivity {
// to prevent Java Binder problems on heavy imports // to prevent Java Binder problems on heavy imports
// read FileImportCache for more info. // read FileImportCache for more info.
try { try {
FileImportCache<ParcelableKeyRing> cache = new FileImportCache<ParcelableKeyRing>(this); FileImportCache<ParcelableKeyRing> cache =
new FileImportCache<ParcelableKeyRing>(this, "key_import.pcl");
cache.writeCache(selectedEntries); cache.writeCache(selectedEntries);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);

View File

@@ -29,15 +29,19 @@ import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ContactHelper;
import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.util.List;
public class ImportKeysServerFragment extends Fragment { public class ImportKeysServerFragment extends Fragment {
public static final String ARG_QUERY = "query"; public static final String ARG_QUERY = "query";
public static final String ARG_KEYSERVER = "keyserver"; public static final String ARG_KEYSERVER = "keyserver";
@@ -46,7 +50,7 @@ public class ImportKeysServerFragment extends Fragment {
private ImportKeysActivity mImportActivity; private ImportKeysActivity mImportActivity;
private View mSearchButton; private View mSearchButton;
private EditText mQueryEditText; private AutoCompleteTextView mQueryEditText;
private View mConfigButton; private View mConfigButton;
private View mConfigLayout; private View mConfigLayout;
private Spinner mServerSpinner; private Spinner mServerSpinner;
@@ -75,7 +79,7 @@ public class ImportKeysServerFragment extends Fragment {
View view = inflater.inflate(R.layout.import_keys_server_fragment, container, false); View view = inflater.inflate(R.layout.import_keys_server_fragment, container, false);
mSearchButton = view.findViewById(R.id.import_server_search); 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); mConfigButton = view.findViewById(R.id.import_server_config_button);
mConfigLayout = view.findViewById(R.id.import_server_config); mConfigLayout = view.findViewById(R.id.import_server_config);
mServerSpinner = (Spinner) view.findViewById(R.id.import_server_spinner); mServerSpinner = (Spinner) view.findViewById(R.id.import_server_spinner);
@@ -93,6 +97,16 @@ public class ImportKeysServerFragment extends Fragment {
mSearchButton.setEnabled(false); 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() { mSearchButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -17,8 +18,11 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.app.ProgressDialog;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@@ -28,6 +32,10 @@ import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase; 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.Log;
import org.sufficientlysecure.keychain.util.Notify; import org.sufficientlysecure.keychain.util.Notify;
@@ -63,6 +71,7 @@ public class KeyListActivity extends DrawerActivity {
getMenuInflater().inflate(R.menu.key_list, menu); getMenuInflater().inflate(R.menu.key_list, menu);
if (Constants.DEBUG) { 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_read).setVisible(true);
menu.findItem(R.id.menu_key_list_debug_write).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); 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); mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
return true; return true;
case R.id.menu_key_list_debug_cons:
consolidate();
return true;
case R.id.menu_key_list_debug_read: case R.id.menu_key_list_debug_read:
try { try {
KeychainDatabase.debugBackup(this, true); KeychainDatabase.debugBackup(this, true);
@@ -133,7 +146,67 @@ public class KeyListActivity extends DrawerActivity {
private void createKey() { private void createKey() {
Intent intent = new Intent(this, CreateKeyActivity.class); 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);
}
} }
} }

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * 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 KeyListAdapter mAdapter;
private StickyListHeadersListView mStickyList; private StickyListHeadersListView mStickyList;
// saves the mode object for multiselect, needed for reset at some point
private ActionMode mActionMode = null;
private String mQuery; private String mQuery;
private SearchView mSearchView; private SearchView mSearchView;
// empty list layout // empty list layout
private Button mButtonEmptyCreate; private Button mButtonEmptyCreate;
private Button mButtonEmptyImport; private Button mButtonEmptyImport;
public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012;
/** /**
* Load custom layout with StickyListView from library * Load custom layout with StickyListView from library
*/ */
@@ -105,7 +107,7 @@ public class KeyListFragment extends LoaderFragment
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(getActivity(), CreateKeyActivity.class); 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); 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) { public void onClick(View v) {
Intent intent = new Intent(getActivity(), ImportKeysActivity.class); Intent intent = new Intent(getActivity(), ImportKeysActivity.class);
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); 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) { public boolean onCreateActionMode(ActionMode mode, Menu menu) {
android.view.MenuInflater inflater = getActivity().getMenuInflater(); android.view.MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.key_list_multi, menu); inflater.inflate(R.menu.key_list_multi, menu);
mActionMode = mode;
return true; return true;
} }
@@ -193,6 +196,7 @@ public class KeyListFragment extends LoaderFragment
@Override @Override
public void onDestroyActionMode(ActionMode mode) { public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
mAdapter.clearSelection(); mAdapter.clearSelection();
} }
@@ -288,6 +292,13 @@ public class KeyListFragment extends LoaderFragment
// this view is made visible if no data is available // this view is made visible if no data is available
mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty)); 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. // The list should now be shown.
if (isResumed()) { if (isResumed()) {
setContentShown(true); setContentShown(true);
@@ -365,6 +376,7 @@ public class KeyListFragment extends LoaderFragment
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
// Get the searchview // Get the searchview
MenuItem searchItem = menu.findItem(R.id.menu_key_list_search); MenuItem searchItem = menu.findItem(R.id.menu_key_list_search);
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem); mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
// Execute this when searching // Execute this when searching
@@ -383,7 +395,6 @@ public class KeyListFragment extends LoaderFragment
@Override @Override
public boolean onMenuItemActionCollapse(MenuItem item) { public boolean onMenuItemActionCollapse(MenuItem item) {
mQuery = null; mQuery = null;
mSearchView.setQuery("", true);
getLoaderManager().restartLoader(0, null, KeyListFragment.this); getLoaderManager().restartLoader(0, null, KeyListFragment.this);
return true; return true;
} }
@@ -399,11 +410,18 @@ public class KeyListFragment extends LoaderFragment
@Override @Override
public boolean onQueryTextChange(String s) { public boolean onQueryTextChange(String s) {
Log.d(Constants.TAG, "onQueryTextChange s:" + s);
// Called when the action bar search text has changed. Update // Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query // the search filter, and restart the loader to do a new query
// with this filter. // with this filter.
mQuery = !TextUtils.isEmpty(s) ? s : null; // 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); getLoaderManager().restartLoader(0, null, this);
}
return true; return true;
} }
@@ -521,6 +539,7 @@ public class KeyListFragment extends LoaderFragment
return mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0; return mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
} }
public long getMasterKeyId(int id) { public long getMasterKeyId(int id) {
if (!mCursor.moveToPosition(id)) { if (!mCursor.moveToPosition(id)) {
throw new IllegalStateException("couldn't move cursor to position " + id); throw new IllegalStateException("couldn't move cursor to position " + id);

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -317,10 +317,10 @@ public class PreferencesActivity extends PreferenceActivity {
private static void initializeHashAlgorithm private static void initializeHashAlgorithm
(final IntegerListPreference mHashAlgorithm, int[] valueIds, String[] entries, String[] values) { (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.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256,
HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512,}; 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",}; "SHA-512",};
values = new String[valueIds.length]; values = new String[valueIds.length];
for (int i = 0; i < values.length; ++i) { for (int i = 0; i < values.length; ++i) {

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * 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)); 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); mAlgorithm.setText(algorithmStr);
mRowReason.setVisibility(View.GONE); mRowReason.setVisibility(View.GONE);

View File

@@ -54,6 +54,8 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.OperationResultParcel; import org.sufficientlysecure.keychain.service.OperationResultParcel;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
@@ -303,9 +305,11 @@ public class ViewKeyActivity extends ActionBarActivity implements
Handler returnHandler = new Handler() { Handler returnHandler = new Handler() {
@Override @Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
setResult(RESULT_CANCELED); setResult(RESULT_CANCELED);
finish(); finish();
} }
}
}; };
exportHelper.deleteKey(dataUri, returnHandler); exportHelper.deleteKey(dataUri, returnHandler);
@@ -313,6 +317,7 @@ public class ViewKeyActivity extends ActionBarActivity implements
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { 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)) { if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) {
OperationResultParcel result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT); OperationResultParcel result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT);
result.createNotify(this).show(); result.createNotify(this).show();

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@@ -155,8 +155,8 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
// don't show full fingerprint on key import // don't show full fingerprint on key import
holder.fingerprint.setVisibility(View.GONE); holder.fingerprint.setVisibility(View.GONE);
if (entry.getBitStrength() != 0 && entry.getAlgorithm() != null) { if (entry.getAlgorithm() != null) {
holder.algorithm.setText("" + entry.getBitStrength() + "/" + entry.getAlgorithm()); holder.algorithm.setText(entry.getAlgorithm());
holder.algorithm.setVisibility(View.VISIBLE); holder.algorithm.setVisibility(View.VISIBLE);
} else { } else {
holder.algorithm.setVisibility(View.INVISIBLE); holder.algorithm.setVisibility(View.INVISIBLE);

View File

@@ -35,7 +35,9 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.TimeZone;
public class SubkeysAdapter extends CursorAdapter { public class SubkeysAdapter extends CursorAdapter {
private LayoutInflater mInflater; private LayoutInflater mInflater;
@@ -50,6 +52,7 @@ public class SubkeysAdapter extends CursorAdapter {
Keys.RANK, Keys.RANK,
Keys.ALGORITHM, Keys.ALGORITHM,
Keys.KEY_SIZE, Keys.KEY_SIZE,
Keys.KEY_CURVE_OID,
Keys.HAS_SECRET, Keys.HAS_SECRET,
Keys.CAN_CERTIFY, Keys.CAN_CERTIFY,
Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT,
@@ -64,14 +67,15 @@ public class SubkeysAdapter extends CursorAdapter {
private static final int INDEX_RANK = 2; private static final int INDEX_RANK = 2;
private static final int INDEX_ALGORITHM = 3; private static final int INDEX_ALGORITHM = 3;
private static final int INDEX_KEY_SIZE = 4; private static final int INDEX_KEY_SIZE = 4;
private static final int INDEX_HAS_SECRET = 5; private static final int INDEX_KEY_CURVE_OID = 5;
private static final int INDEX_CAN_CERTIFY = 6; private static final int INDEX_HAS_SECRET = 6;
private static final int INDEX_CAN_ENCRYPT = 7; private static final int INDEX_CAN_CERTIFY = 7;
private static final int INDEX_CAN_SIGN = 8; private static final int INDEX_CAN_ENCRYPT = 8;
private static final int INDEX_IS_REVOKED = 9; private static final int INDEX_CAN_SIGN = 9;
private static final int INDEX_CREATION = 10; private static final int INDEX_IS_REVOKED = 10;
private static final int INDEX_EXPIRY = 11; private static final int INDEX_CREATION = 11;
private static final int INDEX_FINGERPRINT = 12; private static final int INDEX_EXPIRY = 12;
private static final int INDEX_FINGERPRINT = 13;
public SubkeysAdapter(Context context, Cursor c, int flags, public SubkeysAdapter(Context context, Cursor c, int flags,
SaveKeyringParcel saveKeyringParcel) { SaveKeyringParcel saveKeyringParcel) {
@@ -139,7 +143,8 @@ public class SubkeysAdapter extends CursorAdapter {
String algorithmStr = PgpKeyHelper.getAlgorithmInfo( String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
context, context,
cursor.getInt(INDEX_ALGORITHM), cursor.getInt(INDEX_ALGORITHM),
cursor.getInt(INDEX_KEY_SIZE) cursor.getInt(INDEX_KEY_SIZE),
cursor.getString(INDEX_KEY_CURVE_OID)
); );
vKeyId.setText(keyIdStr); vKeyId.setText(keyIdStr);
@@ -183,7 +188,7 @@ public class SubkeysAdapter extends CursorAdapter {
SaveKeyringParcel.SubkeyChange subkeyChange = mSaveKeyringParcel.getSubkeyChange(keyId); SaveKeyringParcel.SubkeyChange subkeyChange = mSaveKeyringParcel.getSubkeyChange(keyId);
if (subkeyChange != null) { if (subkeyChange != null) {
if (subkeyChange.mExpiry == null) { if (subkeyChange.mExpiry == null || subkeyChange.mExpiry == 0L) {
expiryDate = null; expiryDate = null;
} else { } else {
expiryDate = new Date(subkeyChange.mExpiry * 1000); expiryDate = new Date(subkeyChange.mExpiry * 1000);
@@ -198,9 +203,13 @@ public class SubkeysAdapter extends CursorAdapter {
boolean isExpired; boolean isExpired;
if (expiryDate != null) { if (expiryDate != null) {
isExpired = expiryDate.before(new Date()); 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) + ": " vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": "
+ DateFormat.getDateFormat(context).format(expiryDate)); + DateFormat.getDateFormat(context).format(expiryCal.getTime()));
} else { } else {
isExpired = false; isExpired = false;

View File

@@ -33,21 +33,19 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.TimeZone;
public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAdd> { public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAdd> {
private LayoutInflater mInflater; private LayoutInflater mInflater;
private Activity mActivity; 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) { public SubkeysAddedAdapter(Activity activity, List<SaveKeyringParcel.SubkeyAdd> data) {
super(activity, -1, data); super(activity, -1, data);
mActivity = activity; mActivity = activity;
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mData = data;
} }
static class ViewHolder { static class ViewHolder {
@@ -101,16 +99,21 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
String algorithmStr = PgpKeyHelper.getAlgorithmInfo( String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
mActivity, mActivity,
holder.mModel.mAlgorithm, holder.mModel.mAlgorithm,
holder.mModel.mKeysize holder.mModel.mKeySize,
holder.mModel.mCurve
); );
holder.vKeyId.setText(R.string.edit_key_new_subkey); holder.vKeyId.setText(R.string.edit_key_new_subkey);
holder.vKeyDetails.setText(algorithmStr); holder.vKeyDetails.setText(algorithmStr);
if (holder.mModel.mExpiry != null) { if (holder.mModel.mExpiry != 0L) {
Date expiryDate = new Date(holder.mModel.mExpiry * 1000); 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) + ": " holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": "
+ DateFormat.getDateFormat(getContext()).format(expiryDate)); + DateFormat.getDateFormat(getContext()).format(expiryCal.getTime()));
} else { } else {
holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": " holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": "
+ getContext().getString(R.string.none)); + getContext().getString(R.string.none));

View File

@@ -20,19 +20,18 @@ package org.sufficientlysecure.keychain.ui.dialog;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.format.DateUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.DatePicker; import android.widget.DatePicker;
@@ -40,17 +39,18 @@ import android.widget.EditText;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TableRow; import android.widget.TableRow;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.util.Choice; import org.sufficientlysecure.keychain.util.Choice;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
public class AddSubkeyDialogFragment extends DialogFragment { public class AddSubkeyDialogFragment extends DialogFragment {
@@ -67,7 +67,10 @@ public class AddSubkeyDialogFragment extends DialogFragment {
private TableRow mExpiryRow; private TableRow mExpiryRow;
private DatePicker mExpiryDatePicker; private DatePicker mExpiryDatePicker;
private Spinner mAlgorithmSpinner; private Spinner mAlgorithmSpinner;
private View mKeySizeRow;
private Spinner mKeySizeSpinner; private Spinner mKeySizeSpinner;
private View mCurveRow;
private Spinner mCurveSpinner;
private TextView mCustomKeyTextView; private TextView mCustomKeyTextView;
private EditText mCustomKeyEditText; private EditText mCustomKeyEditText;
private TextView mCustomKeyInfoTextView; private TextView mCustomKeyInfoTextView;
@@ -76,6 +79,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
private CheckBox mFlagEncrypt; private CheckBox mFlagEncrypt;
private CheckBox mFlagAuthenticate; private CheckBox mFlagAuthenticate;
private boolean mWillBeMasterKey;
public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) { public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) {
mAlgorithmSelectedListener = listener; mAlgorithmSelectedListener = listener;
} }
@@ -96,7 +101,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
final FragmentActivity context = getActivity(); final FragmentActivity context = getActivity();
final LayoutInflater mInflater; final LayoutInflater mInflater;
final boolean willBeMasterKey = getArguments().getBoolean(ARG_WILL_BE_MASTER_KEY); mWillBeMasterKey = getArguments().getBoolean(ARG_WILL_BE_MASTER_KEY);
mInflater = context.getLayoutInflater(); mInflater = context.getLayoutInflater();
CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context); 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); mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker);
mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm); mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm);
mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size); 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); mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label);
mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input); mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input);
mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info); mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info);
@@ -130,29 +138,38 @@ public class AddSubkeyDialogFragment extends DialogFragment {
}); });
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { 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( ArrayList<Choice<Algorithm>> choices = new ArrayList<Choice<Algorithm>>();
choices.add(new Choice<Algorithm>(Algorithm.DSA, getResources().getString(
R.string.dsa))); R.string.dsa)));
if (!willBeMasterKey) { if (!mWillBeMasterKey) {
choices.add(new Choice(PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, getResources().getString( choices.add(new Choice<Algorithm>(Algorithm.ELGAMAL, getResources().getString(
R.string.elgamal))); R.string.elgamal)));
} }
choices.add(new Choice(PublicKeyAlgorithmTags.RSA_GENERAL, getResources().getString( choices.add(new Choice<Algorithm>(Algorithm.RSA, getResources().getString(
R.string.rsa))); R.string.rsa)));
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(context, 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); android.R.layout.simple_spinner_item, choices);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mAlgorithmSpinner.setAdapter(adapter); mAlgorithmSpinner.setAdapter(adapter);
// make RSA the default // make RSA the default
for (int i = 0; i < choices.size(); ++i) { for (int i = 0; i < choices.size(); ++i) {
if (choices.get(i).getId() == PublicKeyAlgorithmTags.RSA_GENERAL) { if (choices.get(i).getId() == Algorithm.RSA) {
mAlgorithmSpinner.setSelection(i); mAlgorithmSpinner.setSelection(i);
break; break;
} }
} }
}
// dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change // dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change
ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<CharSequence>(context, android.R.layout.simple_spinner_item, ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<CharSequence>(context, android.R.layout.simple_spinner_item,
@@ -161,58 +178,42 @@ public class AddSubkeyDialogFragment extends DialogFragment {
mKeySizeSpinner.setAdapter(keySizeAdapter); mKeySizeSpinner.setAdapter(keySizeAdapter);
mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length
{
ArrayList<Choice<Curve>> choices = new ArrayList<Choice<Curve>>();
dialog.setPositiveButton(android.R.string.ok, choices.add(new Choice<Curve>(Curve.NIST_P256, getResources().getString(
new DialogInterface.OnClickListener() { R.string.key_curve_nist_p256)));
public void onClick(DialogInterface di, int id) { choices.add(new Choice<Curve>(Curve.NIST_P384, getResources().getString(
di.dismiss(); R.string.key_curve_nist_p384)));
Choice newKeyAlgorithmChoice = (Choice) mAlgorithmSpinner.getSelectedItem(); choices.add(new Choice<Curve>(Curve.NIST_P521, getResources().getString(
int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(), getSelectedKeyLength()); R.string.key_curve_nist_p521)));
int flags = 0; /* @see SaveKeyringParcel
if (mFlagCertify.isChecked()) { choices.add(new Choice<Curve>(Curve.BRAINPOOL_P256, getResources().getString(
flags |= KeyFlags.CERTIFY_OTHER; R.string.key_curve_bp_p256)));
} choices.add(new Choice<Curve>(Curve.BRAINPOOL_P384, getResources().getString(
if (mFlagSign.isChecked()) { R.string.key_curve_bp_p384)));
flags |= KeyFlags.SIGN_DATA; choices.add(new Choice<Curve>(Curve.BRAINPOOL_P512, getResources().getString(
} R.string.key_curve_bp_p512)));
if (mFlagEncrypt.isChecked()) { */
flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
}
if (mFlagAuthenticate.isChecked()) {
flags |= KeyFlags.AUTHENTICATION;
}
Long expiry; ArrayAdapter<Choice<Curve>> adapter = new ArrayAdapter<Choice<Curve>>(context,
if (mNoExpiryCheckBox.isChecked()) { android.R.layout.simple_spinner_item, choices);
expiry = null; mCurveSpinner.setAdapter(adapter);
} else { // make NIST P-256 the default
Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); for (int i = 0; i < choices.size(); ++i) {
//noinspection ResourceType if (choices.get(i).getId() == Curve.NIST_P256) {
selectedCal.set(mExpiryDatePicker.getYear(), mCurveSpinner.setSelection(i);
mExpiryDatePicker.getMonth(), mExpiryDatePicker.getDayOfMonth()); break;
expiry = selectedCal.getTime().getTime() / 1000; }
}
SaveKeyringParcel.SubkeyAdd newSubkey = new SaveKeyringParcel.SubkeyAdd(
newKeyAlgorithmChoice.getId(),
newKeySize,
flags,
expiry
);
mAlgorithmSelectedListener.onAlgorithmSelected(newSubkey);
} }
} }
);
dialog.setCancelable(true); dialog.setCancelable(true);
dialog.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() { // onClickListener are set in onStart() to override default dismiss behaviour
public void onClick(DialogInterface di, int id) { dialog.setPositiveButton(android.R.string.ok, null);
di.dismiss(); dialog.setNegativeButton(android.R.string.cancel, null);
}
}
);
final AlertDialog alertDialog = dialog.show(); final AlertDialog alertDialog = dialog.show();
@@ -246,7 +247,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setKeyLengthSpinnerValuesForAlgorithm(((Choice) parent.getSelectedItem()).getId()); updateUiForAlgorithm(((Choice<Algorithm>) parent.getSelectedItem()).getId());
setCustomKeyVisibility(); setCustomKeyVisibility();
setOkButtonAvailability(alertDialog); setOkButtonAvailability(alertDialog);
@@ -260,6 +261,79 @@ public class AddSubkeyDialogFragment extends DialogFragment {
return alertDialog; 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() { private int getSelectedKeyLength() {
final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem();
final String customLengthString = getResources().getString(R.string.key_size_custom); 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 * @return correct key length, according to SpongyCastle specification. Returns <code>-1</code>, if key length is
* inappropriate. * inappropriate.
*/ */
private int getProperKeyLength(int algorithmId, int currentKeyLength) { private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) {
final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192}; final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192};
int properKeyLength = -1; int properKeyLength = -1;
switch (algorithmId) { switch (algorithm) {
case PublicKeyAlgorithmTags.RSA_GENERAL: case RSA:
if (currentKeyLength > 1024 && currentKeyLength <= 16384) { if (currentKeyLength > 1024 && currentKeyLength <= 16384) {
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8); properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
} }
break; break;
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: case ELGAMAL:
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length]; int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
for (int i = 0; i < elGamalSupportedLengths.length; i++) { for (int i = 0; i < elGamalSupportedLengths.length; i++) {
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength); elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
@@ -314,7 +388,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
} }
properKeyLength = elGamalSupportedLengths[minimalIndex]; properKeyLength = elGamalSupportedLengths[minimalIndex];
break; break;
case PublicKeyAlgorithmTags.DSA: case DSA:
if (currentKeyLength >= 512 && currentKeyLength <= 1024) { if (currentKeyLength >= 512 && currentKeyLength <= 1024) {
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64); properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
} }
@@ -324,10 +398,10 @@ public class AddSubkeyDialogFragment extends DialogFragment {
} }
private void setOkButtonAvailability(AlertDialog alertDialog) { private void setOkButtonAvailability(AlertDialog alertDialog) {
final Choice selectedAlgorithm = (Choice) mAlgorithmSpinner.getSelectedItem(); Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId();
final int selectedKeySize = getSelectedKeyLength(); //Integer.parseInt((String) mKeySizeSpinner.getSelectedItem()); boolean enabled = algorithm == Algorithm.ECDSA || algorithm == Algorithm.ECDH
final int properKeyLength = getProperKeyLength(selectedAlgorithm.getId(), selectedKeySize); || getProperKeyLength(algorithm, getSelectedKeyLength()) > 0;
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(properKeyLength > 0); alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
} }
private void setCustomKeyVisibility() { private void setCustomKeyVisibility() {
@@ -343,38 +417,104 @@ public class AddSubkeyDialogFragment extends DialogFragment {
// hide keyboard after setting visibility to gone // hide keyboard after setting visibility to gone
if (visibility == View.GONE) { if (visibility == View.GONE) {
InputMethodManager imm = (InputMethodManager) InputMethodManager imm = (InputMethodManager)
getActivity().getSystemService(getActivity().INPUT_METHOD_SERVICE); getActivity().getSystemService(FragmentActivity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mCustomKeyEditText.getWindowToken(), 0); imm.hideSoftInputFromWindow(mCustomKeyEditText.getWindowToken(), 0);
} }
} }
private void setKeyLengthSpinnerValuesForAlgorithm(int algorithmId) { private void updateUiForAlgorithm(Algorithm algorithm) {
final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter(); final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter();
final Object selectedItem = mKeySizeSpinner.getSelectedItem();
keySizeAdapter.clear(); keySizeAdapter.clear();
switch (algorithmId) { switch (algorithm) {
case PublicKeyAlgorithmTags.RSA_GENERAL: case RSA:
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values); 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)); 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; break;
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: case ELGAMAL:
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values); 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 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; break;
case PublicKeyAlgorithmTags.DSA: case DSA:
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values); 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)); 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; break;
} }
keySizeAdapter.notifyDataSetChanged(); 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) @TargetApi(Build.VERSION_CODES.HONEYCOMB)

View File

@@ -18,7 +18,9 @@
package org.sufficientlysecure.keychain.ui.dialog; package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Message; import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
@@ -31,9 +33,10 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper; 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 org.sufficientlysecure.keychain.util.Log;
import java.util.HashMap; import java.util.HashMap;
@@ -48,8 +51,6 @@ public class DeleteKeyDialogFragment extends DialogFragment {
private TextView mMainMessage; private TextView mMainMessage;
private View mInflateView; private View mInflateView;
private Messenger mMessenger;
/** /**
* Creates new instance of this delete file dialog fragment * Creates new instance of this delete file dialog fragment
*/ */
@@ -68,7 +69,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity activity = getActivity(); 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); final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
@@ -83,6 +84,8 @@ public class DeleteKeyDialogFragment extends DialogFragment {
builder.setTitle(R.string.warning); builder.setTitle(R.string.warning);
final boolean hasSecret;
// If only a single key has been selected // If only a single key has been selected
if (masterKeyIds.length == 1) { if (masterKeyIds.length == 1) {
long masterKeyId = masterKeyIds[0]; long masterKeyId = masterKeyIds[0];
@@ -98,7 +101,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
} }
); );
String userId = (String) data.get(KeyRings.USER_ID); 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. // Set message depending on which key it is.
mMainMessage.setText(getString( mMainMessage.setText(getString(
@@ -107,12 +110,12 @@ public class DeleteKeyDialogFragment extends DialogFragment {
userId userId
)); ));
} catch (ProviderHelper.NotFoundException e) { } catch (ProviderHelper.NotFoundException e) {
sendMessageToHandler(MESSAGE_ERROR, null);
dismiss(); dismiss();
return null; return null;
} }
} else { } else {
mMainMessage.setText(R.string.key_deletion_confirmation_multi); mMainMessage.setText(R.string.key_deletion_confirmation_multi);
hasSecret = false;
} }
builder.setIcon(R.drawable.ic_dialog_alert_holo_light); builder.setIcon(R.drawable.ic_dialog_alert_holo_light);
@@ -120,18 +123,45 @@ public class DeleteKeyDialogFragment extends DialogFragment {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
boolean success = false; // Send all information needed to service to import key in other thread
for (long masterKeyId : masterKeyIds) { Intent intent = new Intent(getActivity(), KeychainIntentService.class);
int count = activity.getContentResolver().delete(
KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null intent.setAction(KeychainIntentService.ACTION_DELETE);
);
success = count > 0; // 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);
} }
if (success) {
sendMessageToHandler(MESSAGE_OKAY, null);
} else {
sendMessageToHandler(MESSAGE_ERROR, null);
} }
};
// 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(); dismiss();
} }
}); });
@@ -146,23 +176,4 @@ public class DeleteKeyDialogFragment extends DialogFragment {
return builder.show(); 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);
}
}
} }

View File

@@ -25,9 +25,10 @@ import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.os.RemoteException; import android.os.RemoteException;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.text.format.DateUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.DatePicker; import android.widget.DatePicker;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
@@ -40,17 +41,14 @@ import java.util.TimeZone;
public class EditSubkeyExpiryDialogFragment extends DialogFragment { public class EditSubkeyExpiryDialogFragment extends DialogFragment {
private static final String ARG_MESSENGER = "messenger"; private static final String ARG_MESSENGER = "messenger";
private static final String ARG_CREATION_DATE = "creation_date"; private static final String ARG_CREATION = "creation";
private static final String ARG_EXPIRY_DATE = "expiry_date"; 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 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 Messenger mMessenger;
private Calendar mExpiryCal;
private DatePicker mDatePicker;
/** /**
* Creates new instance of this dialog fragment * Creates new instance of this dialog fragment
@@ -60,8 +58,8 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment {
EditSubkeyExpiryDialogFragment frag = new EditSubkeyExpiryDialogFragment(); EditSubkeyExpiryDialogFragment frag = new EditSubkeyExpiryDialogFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putParcelable(ARG_MESSENGER, messenger); args.putParcelable(ARG_MESSENGER, messenger);
args.putSerializable(ARG_CREATION_DATE, creationDate); args.putSerializable(ARG_CREATION, creationDate);
args.putSerializable(ARG_EXPIRY_DATE, expiryDate); args.putSerializable(ARG_EXPIRY, expiryDate);
frag.setArguments(args); frag.setArguments(args);
@@ -75,15 +73,17 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity(); final Activity activity = getActivity();
mMessenger = getArguments().getParcelable(ARG_MESSENGER); mMessenger = getArguments().getParcelable(ARG_MESSENGER);
Date creationDate = new Date(getArguments().getLong(ARG_CREATION_DATE) * 1000); long creation = getArguments().getLong(ARG_CREATION);
Date expiryDate = new Date(getArguments().getLong(ARG_EXPIRY_DATE) * 1000); long expiry = getArguments().getLong(ARG_EXPIRY);
Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationCal.setTime(creationDate); creationCal.setTime(new Date(creation * 1000));
mExpiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); final Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
mExpiryCal.setTime(expiryDate); 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! // Explicitly not using DatePickerDialog here!
// DatePickerDialog is difficult to customize and has many problems (see old git versions) // 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); View view = inflater.inflate(R.layout.edit_subkey_expiry_dialog, null);
alert.setView(view); 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) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
// will crash with IllegalArgumentException if we set a min date datePicker.setMinDate(creationCal.getTime().getTime());
// 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);
}
} }
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 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) { public void onClick(DialogInterface dialog, int id) {
dismiss(); dismiss();
Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); long expiry;
//noinspection ResourceType if (noExpiry.isChecked()) {
selectedCal.set(mDatePicker.getYear(), mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); expiry = 0L;
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);
}
} else { } else {
Bundle data = new Bundle(); Calendar selectedCal = Calendar.getInstance(TimeZone.getDefault());
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime().getTime() / 1000); //noinspection ResourceType
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); 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() { long numDays = (selectedCal.getTimeInMillis() / 86400000)
@Override - (expiryCal.getTimeInMillis() / 86400000);
public void onClick(DialogInterface dialog, int id) { if (numDays <= 0) {
dismiss(); Log.e(Constants.TAG, "Should not happen! Expiry num of days <= 0!");
throw new RuntimeException();
}
expiry = selectedCal.getTime().getTime() / 1000;
}
Bundle data = new Bundle(); Bundle data = new Bundle();
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, null); data.putSerializable(MESSAGE_DATA_EXPIRY, expiry);
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); sendMessageToHandler(MESSAGE_NEW_EXPIRY, data);
} }
}); });

View File

@@ -25,6 +25,7 @@ import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnKeyListener; import android.content.DialogInterface.OnKeyListener;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent; import android.view.KeyEvent;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
@@ -113,7 +114,12 @@ public class ProgressDialogFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity(); 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.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setCancelable(false); dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false); dialog.setCanceledOnTouchOutside(false);

View File

@@ -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);
}
}

View File

@@ -111,7 +111,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
protected void onAttachedToWindow() { protected void onAttachedToWindow() {
super.onAttachedToWindow(); super.onAttachedToWindow();
if (getContext() instanceof FragmentActivity) { 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 @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// These are the rows that we will retrieve. // These are the rows that we will retrieve.
@@ -143,6 +143,8 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
swapCursor(null); swapCursor(null);
} }
}); });
} else {
Log.e(Constants.TAG, "EncryptKeyCompletionView must be attached to a FragmentActivity, this is " + getContext().getClass());
} }
} }

View File

@@ -24,7 +24,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.animation.AlphaAnimation; import android.view.animation.AlphaAnimation;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.widget.ImageButton; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
@@ -38,9 +38,7 @@ import org.sufficientlysecure.keychain.R;
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
custom:foldedLabel="@string/TEXT_TO_DISPLAY_WHEN_FOLDED" custom:foldedLabel="@string/TEXT_TO_DISPLAY_WHEN_FOLDED"
custom:unFoldedLabel="@string/TEXT_TO_DISPLAY_WHEN_UNFOLDED" 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">
<include layout="@layout/ELEMENTS_TO_BE_FOLDED"/> <include layout="@layout/ELEMENTS_TO_BE_FOLDED"/>
@@ -49,7 +47,7 @@ import org.sufficientlysecure.keychain.R;
*/ */
public class FoldableLinearLayout extends LinearLayout { public class FoldableLinearLayout extends LinearLayout {
private ImageButton mFoldableIcon; private ImageView mFoldableIcon;
private boolean mFolded; private boolean mFolded;
private boolean mHasMigrated = false; private boolean mHasMigrated = false;
private Integer mShortAnimationDuration = null; private Integer mShortAnimationDuration = null;
@@ -139,7 +137,7 @@ public class FoldableLinearLayout extends LinearLayout {
} }
private void initialiseInnerViews() { private void initialiseInnerViews() {
mFoldableIcon = (ImageButton) mFoldableLayout.findViewById(R.id.foldableIcon); mFoldableIcon = (ImageView) mFoldableLayout.findViewById(R.id.foldableIcon);
mFoldableIcon.setImageResource(R.drawable.ic_action_expand); mFoldableIcon.setImageResource(R.drawable.ic_action_expand);
mFoldableTextView = (TextView) mFoldableLayout.findViewById(R.id.foldableText); mFoldableTextView = (TextView) mFoldableLayout.findViewById(R.id.foldableText);
mFoldableTextView.setText(mFoldedLabel); mFoldableTextView.setText(mFoldedLabel);

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -50,7 +50,6 @@ public class AlgorithmNames {
mEncryptionNames.put(PGPEncryptedData.TRIPLE_DES, "Triple DES"); mEncryptionNames.put(PGPEncryptedData.TRIPLE_DES, "Triple DES");
mEncryptionNames.put(PGPEncryptedData.IDEA, "IDEA"); mEncryptionNames.put(PGPEncryptedData.IDEA, "IDEA");
mHashNames.put(HashAlgorithmTags.MD5, "MD5");
mHashNames.put(HashAlgorithmTags.RIPEMD160, "RIPEMD-160"); mHashNames.put(HashAlgorithmTags.RIPEMD160, "RIPEMD-160");
mHashNames.put(HashAlgorithmTags.SHA1, "SHA-1"); mHashNames.put(HashAlgorithmTags.SHA1, "SHA-1");
mHashNames.put(HashAlgorithmTags.SHA224, "SHA-224"); mHashNames.put(HashAlgorithmTags.SHA224, "SHA-224");

View File

@@ -17,21 +17,16 @@
package org.sufficientlysecure.keychain.util; package org.sufficientlysecure.keychain.util;
public class Choice { public class Choice <E> {
private String mName; private String mName;
private int mId; private E mId;
public Choice() { public Choice(E id, String name) {
mId = -1;
mName = "";
}
public Choice(int id, String name) {
mId = id; mId = id;
mName = name; mName = name;
} }
public int getId() { public E getId() {
return mId; return mId;
} }

View File

@@ -46,10 +46,11 @@ public class FileImportCache<E extends Parcelable> {
private Context mContext; private Context mContext;
private static final String FILENAME = "key_import.pcl"; private final String mFilename;
public FileImportCache(Context context) { public FileImportCache(Context context, String filename) {
this.mContext = context; mContext = context;
mFilename = filename;
} }
public void writeCache(ArrayList<E> selectedEntries) throws IOException { 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!"); 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)); DataOutputStream oos = new DataOutputStream(new FileOutputStream(tempFile));
@@ -91,6 +92,10 @@ public class FileImportCache<E extends Parcelable> {
} }
public Iterator<E> readCache() throws IOException { public Iterator<E> readCache() throws IOException {
return readCache(true);
}
public Iterator<E> readCache(final boolean deleteAfterRead) throws IOException {
File cacheDir = mContext.getCacheDir(); File cacheDir = mContext.getCacheDir();
if (cacheDir == null) { if (cacheDir == null) {
@@ -98,7 +103,7 @@ public class FileImportCache<E extends Parcelable> {
throw new IOException("cache dir is null!"); 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)); final DataInputStream ois = new DataInputStream(new FileInputStream(tempFile));
return new Iterator<E>() { return new Iterator<E>() {
@@ -165,7 +170,10 @@ public class FileImportCache<E extends Parcelable> {
if (!closed) { if (!closed) {
try { try {
ois.close(); ois.close();
if (deleteAfterRead) {
//noinspection ResultOfMethodCallIgnored
tempFile.delete(); tempFile.delete();
}
} catch (IOException e) { } catch (IOException e) {
// nvm // 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();
}
} }

View File

@@ -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);
}
}
}

View File

@@ -1,16 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal"> android:orientation="horizontal">
<android.support.v4.widget.FixedDrawerLayout <android.support.v4.widget.FixedDrawerLayout
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<include layout="@layout/drawer_list" /> <include layout="@layout/drawer_list" />
</android.support.v4.widget.FixedDrawerLayout> </android.support.v4.widget.FixedDrawerLayout>

View File

@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.v4.widget.FixedDrawerLayout <android.support.v4.widget.FixedDrawerLayout
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">

View File

@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.v4.widget.FixedDrawerLayout <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:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.v4.widget.FixedDrawerLayout <android.support.v4.widget.FixedDrawerLayout
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -34,7 +34,7 @@
android:padding="4dp" /> android:padding="4dp" />
</TableRow> </TableRow>
<TableRow> <TableRow android:id="@+id/add_subkey_row_size">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -50,6 +50,24 @@
android:padding="4dp" /> android:padding="4dp" />
</TableRow> </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 <TextView
android:id="@+id/add_subkey_custom_key_size_label" android:id="@+id/add_subkey_custom_key_size_label"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -59,9 +59,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
custom:foldedLabel="@string/api_settings_show_advanced" custom:foldedLabel="@string/api_settings_show_advanced"
custom:unFoldedLabel="@string/api_settings_hide_advanced" custom:unFoldedLabel="@string/api_settings_hide_advanced">
custom:foldedIcon="fa-chevron-right"
custom:unFoldedIcon="fa-chevron-down">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -40,9 +40,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
custom:foldedLabel="@string/api_settings_show_info" custom:foldedLabel="@string/api_settings_show_info"
custom:unFoldedLabel="@string/api_settings_hide_info" custom:unFoldedLabel="@string/api_settings_hide_info">
custom:foldedIcon="fa-chevron-right"
custom:unFoldedIcon="fa-chevron-down">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -67,7 +65,7 @@
android:id="@+id/api_app_settings_package_signature" android:id="@+id/api_app_settings_package_signature"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Base64 encoded signature" android:text="Base64 encoded hash of signature"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textAppearance="?android:attr/textAppearanceSmall" />
</org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout> </org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout>

View File

@@ -4,13 +4,13 @@
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical" > android:orientation="vertical" >
<org.sufficientlysecure.htmltextview.HtmlTextView <TextView
android:id="@+id/api_select_pub_keys_text" android:id="@+id/api_select_pub_keys_text"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="8dp" android:paddingTop="8dp"
android:paddingBottom="0dip" android:paddingLeft="8dp"
android:text="Set in-code!" android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textAppearance="?android:attr/textAppearanceSmall" />
<FrameLayout <FrameLayout

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@@ -8,6 +7,7 @@
<include layout="@layout/notify_area" /> <include layout="@layout/notify_area" />
<ScrollView <ScrollView
android:id="@+id/certify_scroll_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent"> android:layout_height="match_parent">
@@ -26,14 +26,10 @@
android:layout_marginTop="14dp" android:layout_marginTop="14dp"
android:text="@string/section_certification_key" /> android:text="@string/section_certification_key" />
<fragment <org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner
android:id="@+id/sign_key_select_key_fragment" android:id="@+id/certify_key_spinner"
android:name="org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
tools:layout="@layout/select_secret_key_layout_fragment" />
<TextView <TextView
style="@style/SectionHeader" style="@style/SectionHeader"
@@ -119,8 +115,7 @@
<org.sufficientlysecure.keychain.ui.widget.FixedListView <org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/view_key_user_ids" android:id="@+id/view_key_user_ids"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:descendantFocusability="blocksDescendants" />
<TextView <TextView
style="@style/SectionHeader" style="@style/SectionHeader"
@@ -184,7 +179,6 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -4,6 +4,16 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <DatePicker
android:id="@+id/edit_subkey_expiry_date_picker" android:id="@+id/edit_subkey_expiry_date_picker"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"

Some files were not shown because too many files have changed in this diff Show More