Merge branch 'master' into yubikey
Conflicts: .gitmodules OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
3
.gitmodules
vendored
@ -7,9 +7,6 @@
|
||||
[submodule "extern/zxing-qr-code"]
|
||||
path = extern/zxing-qr-code
|
||||
url = https://github.com/open-keychain/zxing-qr-code.git
|
||||
[submodule "extern/AppMsg"]
|
||||
path = extern/AppMsg
|
||||
url = https://github.com/open-keychain/Android-AppMsg.git
|
||||
[submodule "extern/spongycastle"]
|
||||
path = extern/spongycastle
|
||||
url = https://github.com/open-keychain/spongycastle.git
|
||||
|
@ -29,6 +29,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@ -61,7 +62,7 @@ public class KeyringTestingHelper {
|
||||
boolean saveSuccess = saveKeyringResult.success();
|
||||
|
||||
// Now re-retrieve the saved key. Should not throw an exception.
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
providerHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
||||
|
||||
// A different ID should still fail
|
||||
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
|
||||
@ -331,13 +332,32 @@ public class KeyringTestingHelper {
|
||||
|
||||
}
|
||||
|
||||
public static <E> E getNth(Iterator<E> it, int position) {
|
||||
while(position-- > 0) {
|
||||
it.next();
|
||||
}
|
||||
return it.next();
|
||||
}
|
||||
|
||||
public static long getSubkeyId(UncachedKeyRing ring, int position) {
|
||||
return getNth(ring.getPublicKeys(), position).getKeyId();
|
||||
}
|
||||
|
||||
private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
|
||||
try {
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
providerHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
||||
throw new AssertionError("Was expecting the previous call to fail!");
|
||||
} catch (ProviderHelper.NotFoundException expectedException) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
public static <E> List<E> itToList(Iterator<E> it) {
|
||||
List<E> result = new ArrayList<E>();
|
||||
while(it.hasNext()) {
|
||||
result.add(it.next());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ package org.sufficientlysecure.keychain.support;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
/**
|
||||
@ -32,8 +32,8 @@ class ProviderHelperStub extends ProviderHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException {
|
||||
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri id) throws NotFoundException {
|
||||
byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
|
||||
return new WrappedPublicKeyRing(data, false, 0);
|
||||
return new CanonicalizedPublicKeyRing(data, 0);
|
||||
}
|
||||
}
|
||||
|
@ -1,297 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) Art O Cathain
|
||||
*
|
||||
* 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.support;
|
||||
|
||||
import org.spongycastle.bcpg.BCPGKey;
|
||||
import org.spongycastle.bcpg.PublicKeyPacket;
|
||||
import org.spongycastle.bcpg.SignatureSubpacket;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by art on 28/06/14.
|
||||
*/
|
||||
public class UncachedKeyringTestingHelper {
|
||||
|
||||
public static boolean compareRing(UncachedKeyRing keyRing1, UncachedKeyRing keyRing2) {
|
||||
OperationResultParcel.OperationLog operationLog = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing canonicalized = keyRing1.canonicalize(operationLog, 0);
|
||||
|
||||
if (canonicalized == null) {
|
||||
throw new AssertionError("Canonicalization failed; messages: [" + operationLog.toList() + "]");
|
||||
}
|
||||
|
||||
return TestDataUtil.iterEquals(canonicalized.getPublicKeys(), keyRing2.getPublicKeys(), new
|
||||
TestDataUtil.EqualityChecker<UncachedPublicKey>() {
|
||||
@Override
|
||||
public boolean areEquals(UncachedPublicKey lhs, UncachedPublicKey rhs) {
|
||||
return comparePublicKey(lhs, rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean comparePublicKey(UncachedPublicKey key1, UncachedPublicKey key2) {
|
||||
boolean equal = true;
|
||||
|
||||
if (key1.canAuthenticate() != key2.canAuthenticate()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.canCertify() != key2.canCertify()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.canEncrypt() != key2.canEncrypt()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.canSign() != key2.canSign()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.getAlgorithm() != key2.getAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.getBitStrength() != key2.getBitStrength()) {
|
||||
return false;
|
||||
}
|
||||
if (!TestDataUtil.equals(key1.getCreationTime(), key2.getCreationTime())) {
|
||||
return false;
|
||||
}
|
||||
if (!TestDataUtil.equals(key1.getExpiryTime(), key2.getExpiryTime())) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(key1.getFingerprint(), key2.getFingerprint())) {
|
||||
return false;
|
||||
}
|
||||
if (key1.getKeyId() != key2.getKeyId()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.getKeyUsage() != key2.getKeyUsage()) {
|
||||
return false;
|
||||
}
|
||||
if (!TestDataUtil.equals(key1.getPrimaryUserId(), key2.getPrimaryUserId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ooops, getPublicKey is due to disappear. But then how to compare?
|
||||
if (!keysAreEqual(key1.getPublicKey(), key2.getPublicKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return equal;
|
||||
}
|
||||
|
||||
public static boolean keysAreEqual(PGPPublicKey a, PGPPublicKey b) {
|
||||
|
||||
if (a.getAlgorithm() != b.getAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getBitStrength() != b.getBitStrength()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.equals(a.getCreationTime(), b.getCreationTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(a.getFingerprint(), b.getFingerprint())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getKeyID() != b.getKeyID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pubKeyPacketsAreEqual(a.getPublicKeyPacket(), b.getPublicKeyPacket())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getVersion() != b.getVersion()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getValidDays() != b.getValidDays()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getValidSeconds() != b.getValidSeconds()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(a.getTrustData(), b.getTrustData())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.iterEquals(a.getUserIDs(), b.getUserIDs())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.iterEquals(a.getUserAttributes(), b.getUserAttributes(),
|
||||
new TestDataUtil.EqualityChecker<PGPUserAttributeSubpacketVector>() {
|
||||
public boolean areEquals(PGPUserAttributeSubpacketVector lhs, PGPUserAttributeSubpacketVector rhs) {
|
||||
// For once, BC defines equals, so we use it implicitly.
|
||||
return TestDataUtil.equals(lhs, rhs);
|
||||
}
|
||||
}
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!TestDataUtil.iterEquals(a.getSignatures(), b.getSignatures(),
|
||||
new TestDataUtil.EqualityChecker<PGPSignature>() {
|
||||
public boolean areEquals(PGPSignature lhs, PGPSignature rhs) {
|
||||
return signaturesAreEqual(lhs, rhs);
|
||||
}
|
||||
}
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean signaturesAreEqual(PGPSignature a, PGPSignature b) {
|
||||
|
||||
if (a.getVersion() != b.getVersion()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getKeyAlgorithm() != b.getKeyAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getHashAlgorithm() != b.getHashAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getSignatureType() != b.getSignatureType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!Arrays.equals(a.getSignature(), b.getSignature())) {
|
||||
return false;
|
||||
}
|
||||
} catch (PGPException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
if (a.getKeyID() != b.getKeyID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.equals(a.getCreationTime(), b.getCreationTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(a.getSignatureTrailer(), b.getSignatureTrailer())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subPacketVectorsAreEqual(a.getHashedSubPackets(), b.getHashedSubPackets())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subPacketVectorsAreEqual(a.getUnhashedSubPackets(), b.getUnhashedSubPackets())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean subPacketVectorsAreEqual(PGPSignatureSubpacketVector aHashedSubPackets, PGPSignatureSubpacketVector bHashedSubPackets) {
|
||||
for (int i = 0; i < Byte.MAX_VALUE; i++) {
|
||||
if (!TestDataUtil.iterEquals(Arrays.asList(aHashedSubPackets.getSubpackets(i)).iterator(),
|
||||
Arrays.asList(bHashedSubPackets.getSubpackets(i)).iterator(),
|
||||
new TestDataUtil.EqualityChecker<SignatureSubpacket>() {
|
||||
@Override
|
||||
public boolean areEquals(SignatureSubpacket lhs, SignatureSubpacket rhs) {
|
||||
return signatureSubpacketsAreEqual(lhs, rhs);
|
||||
}
|
||||
}
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean signatureSubpacketsAreEqual(SignatureSubpacket lhs, SignatureSubpacket rhs) {
|
||||
if (lhs.getType() != rhs.getType()) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(lhs.getData(), rhs.getData())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean pubKeyPacketsAreEqual(PublicKeyPacket a, PublicKeyPacket b) {
|
||||
|
||||
if (a.getAlgorithm() != b.getAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bcpgKeysAreEqual(a.getKey(), b.getKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.equals(a.getTime(), b.getTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getValidDays() != b.getValidDays()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getVersion() != b.getVersion()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean bcpgKeysAreEqual(BCPGKey a, BCPGKey b) {
|
||||
|
||||
if (!TestDataUtil.equals(a.getFormat(), b.getFormat())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(a.getEncoded(), b.getEncoded())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void doTestCanonicalize(UncachedKeyRing inputKeyRing, UncachedKeyRing expectedKeyRing) {
|
||||
if (!compareRing(inputKeyRing, expectedKeyRing)) {
|
||||
throw new AssertionError("Expected [" + inputKeyRing + "] to match [" + expectedKeyRing + "]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -19,12 +19,13 @@ import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Constants.choice.algorithm;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||
@ -82,8 +83,7 @@ public class PgpKeyOperationTest {
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
staticRing = op.createSecretKeyRing(parcel, log, 0);
|
||||
staticRing = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
Assert.assertNotNull("initial test key creation must succeed", staticRing);
|
||||
|
||||
@ -107,9 +107,7 @@ public class PgpKeyOperationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlgorithmChoice() {
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
public void createSecretKeyRingTests() {
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
@ -118,7 +116,7 @@ public class PgpKeyOperationTest {
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
Assert.assertNull("creating ring with < 512 bytes keysize should fail", ring);
|
||||
}
|
||||
@ -130,7 +128,7 @@ public class PgpKeyOperationTest {
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
Assert.assertNull("creating ring with ElGamal master key should fail", ring);
|
||||
}
|
||||
@ -142,7 +140,7 @@ public class PgpKeyOperationTest {
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
Assert.assertNull("creating ring with bad algorithm choice should fail", ring);
|
||||
}
|
||||
|
||||
@ -153,7 +151,7 @@ public class PgpKeyOperationTest {
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
Assert.assertNull("creating ring with non-certifying master key should fail", ring);
|
||||
}
|
||||
|
||||
@ -163,7 +161,7 @@ public class PgpKeyOperationTest {
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
Assert.assertNull("creating ring without user ids should fail", ring);
|
||||
}
|
||||
|
||||
@ -172,7 +170,7 @@ public class PgpKeyOperationTest {
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
Assert.assertNull("creating ring without subkeys should fail", ring);
|
||||
}
|
||||
|
||||
@ -186,11 +184,10 @@ public class PgpKeyOperationTest {
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddUserIds.add("luna");
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
ring = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
Assert.assertEquals("the keyring should contain only the master key",
|
||||
1, ring.getAvailableSubkeys().size());
|
||||
1, KeyringTestingHelper.itToList(ring.getPublicKeys()).size());
|
||||
Assert.assertEquals("first (master) key must have both flags",
|
||||
KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, ring.getPublicKey().getKeyUsage());
|
||||
|
||||
@ -212,7 +209,7 @@ public class PgpKeyOperationTest {
|
||||
2, ring.getPublicKey().getUnorderedUserIds().size());
|
||||
|
||||
Assert.assertEquals("number of subkeys must be three",
|
||||
3, ring.getAvailableSubkeys().size());
|
||||
3, KeyringTestingHelper.itToList(ring.getPublicKeys()).size());
|
||||
|
||||
Assert.assertTrue("key ring should have been created in the last 120 seconds",
|
||||
ring.getPublicKey().getCreationTime().after(new Date(new Date().getTime()-1000*120)));
|
||||
@ -250,9 +247,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.mMasterKeyId = ring.getMasterKeyId() -1;
|
||||
parcel.mFingerprint = ring.getFingerprint();
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with bad master key id should fail", modified);
|
||||
}
|
||||
@ -263,9 +259,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.mMasterKeyId = null;
|
||||
parcel.mFingerprint = ring.getFingerprint();
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with null master key id should fail", modified);
|
||||
}
|
||||
@ -277,9 +272,8 @@ public class PgpKeyOperationTest {
|
||||
// some byte, off by one
|
||||
parcel.mFingerprint[5] += 1;
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with bad fingerprint should fail", modified);
|
||||
}
|
||||
@ -289,17 +283,19 @@ public class PgpKeyOperationTest {
|
||||
parcel.mMasterKeyId = ring.getMasterKeyId();
|
||||
parcel.mFingerprint = null;
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with null fingerprint should fail", modified);
|
||||
}
|
||||
|
||||
{
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, "bad passphrase", log, 0);
|
||||
String badphrase = "";
|
||||
if (badphrase.equals(passphrase)) {
|
||||
badphrase = "a";
|
||||
}
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, badphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with bad passphrase should fail", modified);
|
||||
}
|
||||
@ -352,11 +348,11 @@ public class PgpKeyOperationTest {
|
||||
|
||||
{ // bad keysize should fail
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, 77, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(
|
||||
algorithm.rsa, new Random().nextInt(512), KeyFlags.SIGN_DATA, null));
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
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);
|
||||
}
|
||||
@ -366,9 +362,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA,
|
||||
new Date().getTime()/1000-10));
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("creating subkey with past expiry date should fail", modified);
|
||||
}
|
||||
@ -379,12 +374,7 @@ public class PgpKeyOperationTest {
|
||||
public void testSubkeyModify() throws Exception {
|
||||
|
||||
long expiry = new Date().getTime()/1000 + 1024;
|
||||
long keyId;
|
||||
{
|
||||
Iterator<UncachedPublicKey> it = ring.getPublicKeys();
|
||||
it.next();
|
||||
keyId = it.next().getKeyId();
|
||||
}
|
||||
long keyId = KeyringTestingHelper.getSubkeyId(ring, 1);
|
||||
|
||||
UncachedKeyRing modified = ring;
|
||||
{
|
||||
@ -440,9 +430,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, new Date().getTime()/1000-10));
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("setting subkey expiry to a past date should fail", modified);
|
||||
}
|
||||
@ -451,9 +440,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(123, null, null));
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("modifying non-existent subkey should fail", modified);
|
||||
}
|
||||
@ -463,13 +451,7 @@ public class PgpKeyOperationTest {
|
||||
@Test
|
||||
public void testSubkeyRevoke() throws Exception {
|
||||
|
||||
long keyId;
|
||||
{
|
||||
Iterator<UncachedPublicKey> it = ring.getPublicKeys();
|
||||
it.next();
|
||||
keyId = it.next().getKeyId();
|
||||
}
|
||||
|
||||
long keyId = KeyringTestingHelper.getSubkeyId(ring, 1);
|
||||
int flags = ring.getPublicKey(keyId).getKeyUsage();
|
||||
|
||||
UncachedKeyRing modified;
|
||||
@ -479,9 +461,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.reset();
|
||||
parcel.mRevokeSubKeys.add(123L);
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("revoking a nonexistent subkey should fail", otherModified);
|
||||
|
||||
@ -582,9 +563,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.reset();
|
||||
parcel.mChangePrimaryUserId = uid;
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(modified.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0);
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("setting primary user id to a revoked user id should fail", otherModified);
|
||||
|
||||
@ -629,6 +609,14 @@ public class PgpKeyOperationTest {
|
||||
@Test
|
||||
public void testUserIdAdd() throws Exception {
|
||||
|
||||
{
|
||||
parcel.mAddUserIds.add("");
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
Assert.assertNull("adding an empty user id should fail", modified);
|
||||
}
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add("rainbow");
|
||||
|
||||
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
@ -689,10 +677,12 @@ public class PgpKeyOperationTest {
|
||||
parcel.reset();
|
||||
//noinspection SpellCheckingInspection
|
||||
parcel.mChangePrimaryUserId = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
if (parcel.mChangePrimaryUserId.equals(passphrase)) {
|
||||
parcel.mChangePrimaryUserId += "A";
|
||||
}
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("changing primary user id to a non-existent one should fail", modified);
|
||||
}
|
||||
@ -716,14 +706,14 @@ public class PgpKeyOperationTest {
|
||||
ArrayList<RawPacket> onlyB,
|
||||
boolean canonicalize,
|
||||
boolean constantCanonicalize) {
|
||||
|
||||
try {
|
||||
|
||||
Assert.assertTrue("modified keyring must be secret", ring.isSecret());
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing rawModified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
UncachedKeyRing rawModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
Assert.assertNotNull("key modification failed", rawModified);
|
||||
|
||||
if (!canonicalize) {
|
||||
@ -732,7 +722,7 @@ public class PgpKeyOperationTest {
|
||||
return rawModified;
|
||||
}
|
||||
|
||||
UncachedKeyRing modified = rawModified.canonicalize(log, 0);
|
||||
CanonicalizedKeyRing modified = rawModified.canonicalize(new OperationLog(), 0);
|
||||
if (constantCanonicalize) {
|
||||
Assert.assertTrue("key must be constant through canonicalization",
|
||||
!KeyringTestingHelper.diffKeyrings(
|
||||
@ -741,7 +731,8 @@ public class PgpKeyOperationTest {
|
||||
}
|
||||
Assert.assertTrue("keyring must differ from original", KeyringTestingHelper.diffKeyrings(
|
||||
ring.getEncoded(), modified.getEncoded(), onlyA, onlyB));
|
||||
return modified;
|
||||
|
||||
return modified.getUncachedKeyRing();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new AssertionFailedError("error during encoding!");
|
||||
@ -754,12 +745,8 @@ public class PgpKeyOperationTest {
|
||||
UncachedKeyRing expectedKeyRing = KeyringBuilder.correctRing();
|
||||
UncachedKeyRing inputKeyRing = KeyringBuilder.ringWithExtraIncorrectSignature();
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing canonicalizedRing = inputKeyRing.canonicalize(log, 0);
|
||||
|
||||
if (canonicalizedRing == null) {
|
||||
throw new AssertionError("Canonicalization failed; messages: [" + log + "]");
|
||||
}
|
||||
CanonicalizedKeyRing canonicalized = inputKeyRing.canonicalize(new OperationLog(), 0);
|
||||
Assert.assertNotNull("canonicalization must succeed", canonicalized);
|
||||
|
||||
ArrayList onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList onlyB = new ArrayList<RawPacket>();
|
||||
|
@ -12,20 +12,43 @@ import org.spongycastle.bcpg.Packet;
|
||||
import org.spongycastle.bcpg.PacketTags;
|
||||
import org.spongycastle.bcpg.UserIDPacket;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
/** Tests for the UncachedKeyring.canonicalize method.
|
||||
*
|
||||
* This is a complex and crypto-relevant method, which takes care of sanitizing keyrings.
|
||||
* Test cases are made for all its assertions.
|
||||
*/
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
public class UncachedKeyringCanonicalizeTest {
|
||||
@ -36,6 +59,8 @@ public class UncachedKeyringCanonicalizeTest {
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen;
|
||||
PGPSecretKey secretKey;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
@ -55,9 +80,9 @@ public class UncachedKeyringCanonicalizeTest {
|
||||
parcel.mNewPassphrase = "";
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
staticRing = op.createSecretKeyRing(parcel, log, 0);
|
||||
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
Assert.assertTrue("initial test key creation must succeed", result.success());
|
||||
staticRing = result.getRing();
|
||||
Assert.assertNotNull("initial test key creation must succeed", staticRing);
|
||||
|
||||
// just for later reference
|
||||
@ -71,8 +96,13 @@ public class UncachedKeyringCanonicalizeTest {
|
||||
// show Log.x messages in system.out
|
||||
ShadowLog.stream = System.out;
|
||||
ring = staticRing;
|
||||
|
||||
subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
secretKey = new PGPSecretKeyRing(ring.getEncoded(), new JcaKeyFingerprintCalculator())
|
||||
.getSecretKey();
|
||||
}
|
||||
|
||||
/** Make sure the assumptions made about the generated ring packet structure are valid. */
|
||||
@Test public void testGeneratedRingStructure() throws Exception {
|
||||
|
||||
Iterator<RawPacket> it = KeyringTestingHelper.parseKeyring(ring.getEncoded());
|
||||
@ -107,43 +137,6 @@ public class UncachedKeyringCanonicalizeTest {
|
||||
|
||||
}
|
||||
|
||||
@Test public void testBrokenSignature() throws Exception {
|
||||
|
||||
byte[] brokenSig;
|
||||
{
|
||||
UncachedPublicKey masterKey = ring.getPublicKey();
|
||||
WrappedSignature sig = masterKey.getSignaturesForId("twi").next();
|
||||
brokenSig = sig.getEncoded();
|
||||
// break the signature
|
||||
brokenSig[brokenSig.length - 5] += 1;
|
||||
}
|
||||
|
||||
byte[] reng = ring.getEncoded();
|
||||
for(int i = 0; i < totalPackets; i++) {
|
||||
|
||||
byte[] brokenBytes = KeyringTestingHelper.injectPacket(reng, brokenSig, i);
|
||||
Assert.assertEquals("broken ring must be original + injected size",
|
||||
reng.length + brokenSig.length, brokenBytes.length);
|
||||
|
||||
try {
|
||||
UncachedKeyRing brokenRing = UncachedKeyRing.decodeFromData(brokenBytes);
|
||||
|
||||
brokenRing = brokenRing.canonicalize(log, 0);
|
||||
if (brokenRing == null) {
|
||||
System.out.println("ok, canonicalization failed.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.assertArrayEquals("injected bad signature must be gone after canonicalization",
|
||||
ring.getEncoded(), brokenRing.getEncoded());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("ok, rejected with: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidSignature() throws Exception {
|
||||
|
||||
UncachedPublicKey masterKey = ring.getPublicKey();
|
||||
@ -156,31 +149,29 @@ public class UncachedKeyringCanonicalizeTest {
|
||||
|
||||
{ // bad certificates get stripped
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, brokenSig.getEncoded(), 3);
|
||||
modified = modified.canonicalize(log, 0);
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
|
||||
Assert.assertTrue("canonicalized keyring with invalid extra sig must be same as original one",
|
||||
!KeyringTestingHelper.diffKeyrings(
|
||||
ring.getEncoded(), modified.getEncoded(), onlyA, onlyB));
|
||||
ring.getEncoded(), canonicalized.getEncoded(), onlyA, onlyB));
|
||||
}
|
||||
|
||||
// remove user id certificate for one user
|
||||
final UncachedKeyRing base = KeyringTestingHelper.removePacket(ring, 2);
|
||||
|
||||
{ // user id without certificate should be removed
|
||||
UncachedKeyRing modified = base.canonicalize(log, 0);
|
||||
CanonicalizedKeyRing modified = base.canonicalize(log, 0);
|
||||
Assert.assertTrue("canonicalized keyring must differ", KeyringTestingHelper.diffKeyrings(
|
||||
ring.getEncoded(), modified.getEncoded(), onlyA, onlyB));
|
||||
|
||||
Assert.assertEquals("two packets should be stripped after canonicalization", 2, onlyA.size());
|
||||
Assert.assertEquals("no new packets after canonicalization", 0, onlyB.size());
|
||||
|
||||
Packet p;
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
|
||||
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first stripped packet must be user id", p instanceof UserIDPacket);
|
||||
Assert.assertEquals("missing user id must be the expected one",
|
||||
"twi", ((UserIDPacket) p).getID());
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(1).buf)).readPacket();
|
||||
Assert.assertArrayEquals("second stripped packet must be signature we removed",
|
||||
sig.getEncoded(), onlyA.get(1).buf);
|
||||
|
||||
@ -189,25 +180,448 @@ public class UncachedKeyringCanonicalizeTest {
|
||||
{ // add error to signature
|
||||
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(base, brokenSig.getEncoded(), 3);
|
||||
modified = modified.canonicalize(log, 0);
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
|
||||
Assert.assertTrue("canonicalized keyring must differ", KeyringTestingHelper.diffKeyrings(
|
||||
ring.getEncoded(), modified.getEncoded(), onlyA, onlyB));
|
||||
ring.getEncoded(), canonicalized.getEncoded(), onlyA, onlyB));
|
||||
|
||||
Assert.assertEquals("two packets should be missing after canonicalization", 2, onlyA.size());
|
||||
Assert.assertEquals("no new packets after canonicalization", 0, onlyB.size());
|
||||
|
||||
Packet p;
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
|
||||
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first stripped packet must be user id", p instanceof UserIDPacket);
|
||||
Assert.assertEquals("missing user id must be the expected one",
|
||||
"twi", ((UserIDPacket) p).getID());
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(1).buf)).readPacket();
|
||||
Assert.assertArrayEquals("second stripped packet must be signature we removed",
|
||||
sig.getEncoded(), onlyA.get(1).buf);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidDestroy() throws Exception {
|
||||
|
||||
// signature for "twi"
|
||||
ring = KeyringTestingHelper.removePacket(ring, 2);
|
||||
// signature for "pink"
|
||||
ring = KeyringTestingHelper.removePacket(ring, 3);
|
||||
|
||||
// canonicalization should fail, because there are no valid uids left
|
||||
CanonicalizedKeyRing canonicalized = ring.canonicalize(log, 0);
|
||||
Assert.assertNull("canonicalization of keyring with no valid uids should fail", canonicalized);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testRevocationRedundant() throws Exception {
|
||||
|
||||
PGPSignature revocation = forgeSignature(
|
||||
secretKey, PGPSignature.KEY_REVOCATION, subHashedPacketsGen, secretKey.getPublicKey());
|
||||
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, revocation.getEncoded(), 1);
|
||||
|
||||
// try to add the same packet again, it should be rejected in all positions
|
||||
injectEverywhere(modified, revocation.getEncoded());
|
||||
|
||||
// an older (but different!) revocation should be rejected as well
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
revocation = forgeSignature(
|
||||
secretKey, PGPSignature.KEY_REVOCATION, subHashedPacketsGen, secretKey.getPublicKey());
|
||||
|
||||
injectEverywhere(modified, revocation.getEncoded());
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidRedundant() throws Exception {
|
||||
|
||||
// an older uid certificate should be rejected
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
PGPSignature revocation = forgeSignature(
|
||||
secretKey, PGPSignature.DEFAULT_CERTIFICATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
injectEverywhere(ring, revocation.getEncoded());
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidRevocationOutdated() throws Exception {
|
||||
// an older uid revocation cert should be rejected
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
PGPSignature revocation = forgeSignature(
|
||||
secretKey, PGPSignature.CERTIFICATION_REVOCATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
injectEverywhere(ring, revocation.getEncoded());
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidRevocationRedundant() throws Exception {
|
||||
|
||||
PGPSignature revocation = forgeSignature(
|
||||
secretKey, PGPSignature.CERTIFICATION_REVOCATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
// add that revocation to the base, and check if the redundant one will be rejected as well
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, revocation.getEncoded(), 2);
|
||||
|
||||
injectEverywhere(modified, revocation.getEncoded());
|
||||
|
||||
// an older (but different!) uid revocation should be rejected as well
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
revocation = forgeSignature(
|
||||
secretKey, PGPSignature.CERTIFICATION_REVOCATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
injectEverywhere(modified, revocation.getEncoded());
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSignatureBroken() throws Exception {
|
||||
|
||||
injectEverytype(secretKey, ring, subHashedPacketsGen, true);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testForeignSignature() throws Exception {
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddUserIds.add("trix");
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing foreign = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
Assert.assertNotNull("initial test key creation must succeed", foreign);
|
||||
PGPSecretKey foreignSecretKey =
|
||||
new PGPSecretKeyRing(foreign.getEncoded(), new JcaKeyFingerprintCalculator())
|
||||
.getSecretKey();
|
||||
|
||||
injectEverytype(foreignSecretKey, ring, subHashedPacketsGen);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSignatureFuture() throws Exception {
|
||||
|
||||
// generate future
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() + 1000 * 1000));
|
||||
|
||||
injectEverytype(secretKey, ring, subHashedPacketsGen);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSignatureLocal() throws Exception {
|
||||
|
||||
// generate future
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date());
|
||||
subHashedPacketsGen.setExportable(false, false);
|
||||
|
||||
injectEverytype(secretKey, ring, subHashedPacketsGen);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSubkeyDestroy() throws Exception {
|
||||
|
||||
// signature for second key (first subkey)
|
||||
UncachedKeyRing modified = KeyringTestingHelper.removePacket(ring, 6);
|
||||
|
||||
// canonicalization should fail, because there are no valid uids left
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
Assert.assertTrue("keyring with missing subkey binding sig should differ from intact one after canonicalization",
|
||||
KeyringTestingHelper.diffKeyrings(ring.getEncoded(), canonicalized.getEncoded(),
|
||||
onlyA, onlyB)
|
||||
);
|
||||
|
||||
Assert.assertEquals("canonicalized keyring should have two extra packets", 2, onlyA.size());
|
||||
Assert.assertEquals("canonicalized keyring should have no extra packets", 0, onlyB.size());
|
||||
|
||||
Assert.assertEquals("first missing packet should be the subkey",
|
||||
PacketTags.SECRET_SUBKEY, onlyA.get(0).tag);
|
||||
Assert.assertEquals("second missing packet should be subkey's signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(1).tag);
|
||||
Assert.assertEquals("second missing packet should be next to subkey",
|
||||
onlyA.get(0).position + 1, onlyA.get(1).position);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSubkeyBindingNoPKB() throws Exception {
|
||||
|
||||
UncachedPublicKey pKey = KeyringTestingHelper.getNth(ring.getPublicKeys(), 1);
|
||||
Assert.assertTrue("second subkey must be able to sign", pKey.canSign());
|
||||
|
||||
PGPSignature sig;
|
||||
|
||||
subHashedPacketsGen.setKeyFlags(false, KeyFlags.SIGN_DATA);
|
||||
|
||||
{
|
||||
// forge a (newer) signature, which has the sign flag but no primary key binding sig
|
||||
PGPSignatureSubpacketGenerator unhashedSubs = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
// just add any random signature, because why not
|
||||
unhashedSubs.setEmbeddedSignature(false, forgeSignature(
|
||||
secretKey, PGPSignature.POSITIVE_CERTIFICATION, subHashedPacketsGen,
|
||||
secretKey.getPublicKey()
|
||||
)
|
||||
);
|
||||
|
||||
sig = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen, unhashedSubs,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
// inject in the right position
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 6);
|
||||
|
||||
// canonicalize, and check if we lose the bad signature
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
Assert.assertFalse("subkey binding signature should be gone after canonicalization",
|
||||
KeyringTestingHelper.diffKeyrings(ring.getEncoded(), canonicalized.getEncoded(),
|
||||
onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
{ // now try one with a /bad/ primary key binding signature
|
||||
|
||||
PGPSignatureSubpacketGenerator unhashedSubs = new PGPSignatureSubpacketGenerator();
|
||||
// this one is signed by the primary key itself, not the subkey - but it IS primary binding
|
||||
unhashedSubs.setEmbeddedSignature(false, forgeSignature(
|
||||
secretKey, PGPSignature.PRIMARYKEY_BINDING, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey()
|
||||
)
|
||||
);
|
||||
|
||||
sig = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen, unhashedSubs,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
// inject in the right position
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 6);
|
||||
|
||||
// canonicalize, and check if we lose the bad signature
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
Assert.assertFalse("subkey binding signature should be gone after canonicalization",
|
||||
KeyringTestingHelper.diffKeyrings(ring.getEncoded(), canonicalized.getEncoded(),
|
||||
onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSubkeyBindingRedundant() throws Exception {
|
||||
|
||||
UncachedPublicKey pKey = KeyringTestingHelper.getNth(ring.getPublicKeys(), 2);
|
||||
|
||||
subHashedPacketsGen.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS);
|
||||
PGPSignature sig2 = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
PGPSignature sig1 = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_REVOCATION, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -100*1000));
|
||||
PGPSignature sig3 = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig1.getEncoded(), 8);
|
||||
modified = KeyringTestingHelper.injectPacket(modified, sig2.getEncoded(), 9);
|
||||
modified = KeyringTestingHelper.injectPacket(modified, sig1.getEncoded(), 10);
|
||||
modified = KeyringTestingHelper.injectPacket(modified, sig3.getEncoded(), 11);
|
||||
|
||||
// canonicalize, and check if we lose the bad signature
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
Assert.assertTrue("subkey binding signature should be gone after canonicalization",
|
||||
KeyringTestingHelper.diffKeyrings(modified.getEncoded(), canonicalized.getEncoded(),
|
||||
onlyA, onlyB)
|
||||
);
|
||||
|
||||
Assert.assertEquals("canonicalized keyring should have lost two packets", 3, onlyA.size());
|
||||
Assert.assertEquals("canonicalized keyring should have no extra packets", 0, onlyB.size());
|
||||
|
||||
Assert.assertEquals("first missing packet should be the subkey",
|
||||
PacketTags.SIGNATURE, onlyA.get(0).tag);
|
||||
Assert.assertEquals("second missing packet should be a signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(1).tag);
|
||||
Assert.assertEquals("second missing packet should be a signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(2).tag);
|
||||
|
||||
}
|
||||
|
||||
private static final int[] sigtypes_direct = new int[] {
|
||||
PGPSignature.KEY_REVOCATION,
|
||||
PGPSignature.DIRECT_KEY,
|
||||
};
|
||||
private static final int[] sigtypes_uid = new int[] {
|
||||
PGPSignature.DEFAULT_CERTIFICATION,
|
||||
PGPSignature.NO_CERTIFICATION,
|
||||
PGPSignature.CASUAL_CERTIFICATION,
|
||||
PGPSignature.POSITIVE_CERTIFICATION,
|
||||
PGPSignature.CERTIFICATION_REVOCATION,
|
||||
};
|
||||
private static final int[] sigtypes_subkey = new int[] {
|
||||
PGPSignature.SUBKEY_BINDING,
|
||||
PGPSignature.PRIMARYKEY_BINDING,
|
||||
PGPSignature.SUBKEY_REVOCATION,
|
||||
};
|
||||
|
||||
private static void injectEverytype(PGPSecretKey secretKey,
|
||||
UncachedKeyRing ring,
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen)
|
||||
throws Exception {
|
||||
injectEverytype(secretKey, ring, subHashedPacketsGen, false);
|
||||
}
|
||||
|
||||
private static void injectEverytype(PGPSecretKey secretKey,
|
||||
UncachedKeyRing ring,
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen,
|
||||
boolean breakSig)
|
||||
throws Exception {
|
||||
|
||||
for (int sigtype : sigtypes_direct) {
|
||||
PGPSignature sig = forgeSignature(
|
||||
secretKey, sigtype, subHashedPacketsGen, secretKey.getPublicKey());
|
||||
byte[] encoded = sig.getEncoded();
|
||||
if (breakSig) {
|
||||
encoded[encoded.length-10] += 1;
|
||||
}
|
||||
injectEverywhere(ring, encoded);
|
||||
}
|
||||
|
||||
for (int sigtype : sigtypes_uid) {
|
||||
PGPSignature sig = forgeSignature(
|
||||
secretKey, sigtype, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
byte[] encoded = sig.getEncoded();
|
||||
if (breakSig) {
|
||||
encoded[encoded.length-10] += 1;
|
||||
}
|
||||
injectEverywhere(ring, encoded);
|
||||
}
|
||||
|
||||
for (int sigtype : sigtypes_subkey) {
|
||||
PGPSignature sig = forgeSignature(
|
||||
secretKey, sigtype, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), secretKey.getPublicKey());
|
||||
|
||||
byte[] encoded = sig.getEncoded();
|
||||
if (breakSig) {
|
||||
encoded[encoded.length-10] += 1;
|
||||
}
|
||||
injectEverywhere(ring, encoded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void injectEverywhere(UncachedKeyRing ring, byte[] packet) throws Exception {
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
|
||||
byte[] encodedRing = ring.getEncoded();
|
||||
|
||||
for(int i = 0; i < totalPackets; i++) {
|
||||
|
||||
byte[] brokenEncoded = KeyringTestingHelper.injectPacket(encodedRing, packet, i);
|
||||
|
||||
try {
|
||||
|
||||
UncachedKeyRing brokenRing = UncachedKeyRing.decodeFromData(brokenEncoded);
|
||||
|
||||
CanonicalizedKeyRing canonicalized = brokenRing.canonicalize(log, 0);
|
||||
if (canonicalized == null) {
|
||||
System.out.println("ok, canonicalization failed.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.assertArrayEquals("injected bad signature must be gone after canonicalization",
|
||||
ring.getEncoded(), canonicalized.getEncoded());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("ok, rejected with: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
|
||||
PGPSignatureSubpacketGenerator subpackets,
|
||||
PGPPublicKey publicKey)
|
||||
throws Exception {
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
publicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.setHashedSubpackets(subpackets.generate());
|
||||
sGen.init(type, privateKey);
|
||||
return sGen.generateCertification(publicKey);
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
|
||||
PGPSignatureSubpacketGenerator subpackets,
|
||||
String userId, PGPPublicKey publicKey)
|
||||
throws Exception {
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
publicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.setHashedSubpackets(subpackets.generate());
|
||||
sGen.init(type, privateKey);
|
||||
return sGen.generateCertification(userId, publicKey);
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
|
||||
PGPSignatureSubpacketGenerator subpackets,
|
||||
PGPPublicKey publicKey, PGPPublicKey signedKey)
|
||||
throws Exception {
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
publicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.setHashedSubpackets(subpackets.generate());
|
||||
sGen.init(type, privateKey);
|
||||
return sGen.generateCertification(publicKey, signedKey);
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
|
||||
PGPSignatureSubpacketGenerator hashedSubs,
|
||||
PGPSignatureSubpacketGenerator unhashedSubs,
|
||||
PGPPublicKey publicKey, PGPPublicKey signedKey)
|
||||
throws Exception {
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
publicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.setHashedSubpackets(hashedSubs.generate());
|
||||
sGen.setUnhashedSubpackets(unhashedSubs.generate());
|
||||
sGen.init(type, privateKey);
|
||||
return sGen.generateCertification(publicKey, signedKey);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,397 @@
|
||||
package org.sufficientlysecure.keychain.tests;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.spongycastle.bcpg.PacketTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
/** Tests for the UncachedKeyring.merge method.
|
||||
*
|
||||
* This is another complex, crypto-related method. It merges information from one keyring into
|
||||
* another, keeping information from the base (ie, called object) keyring in case of conflicts.
|
||||
* The types of keys may be Public or Secret and can be mixed, For mixed types the result type
|
||||
* will be the same as the base keyring.
|
||||
*
|
||||
* Test cases:
|
||||
* - Merging keyrings with different masterKeyIds should fail
|
||||
* - Merging a key with itself should be a no-operation
|
||||
* - Merging a key with an extra revocation certificate, it should have that certificate
|
||||
* - Merging a key with an extra user id, it should have that extra user id and its certificates
|
||||
* - Merging a key with an extra user id certificate, it should have that certificate
|
||||
* - Merging a key with an extra subkey, it should have that subkey
|
||||
* - Merging a key with an extra subkey certificate, it should have that certificate
|
||||
* - All of the above operations should work regardless of the key types. This means in particular
|
||||
* that for new subkeys, an equivalent subkey of the proper type must be generated.
|
||||
* - In case of two secret keys with the same id but different S2K, the key of the base keyring
|
||||
* should be preferred (TODO or should it?)
|
||||
*
|
||||
* Note that the merge operation does not care about certificate validity, a bad certificate or
|
||||
* packet will be copied regardless. Filtering out bad packets is done with canonicalization.
|
||||
*
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
public class UncachedKeyringMergeTest {
|
||||
|
||||
static UncachedKeyRing staticRingA, staticRingB;
|
||||
UncachedKeyRing ringA, ringB;
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
PgpKeyOperation op;
|
||||
SaveKeyringParcel parcel;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
|
||||
parcel.mAddUserIds.add("twi");
|
||||
parcel.mAddUserIds.add("pink");
|
||||
// passphrase is tested in PgpKeyOperationTest, just use empty here
|
||||
parcel.mNewPassphrase = "";
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
staticRingA = result.getRing();
|
||||
}
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
|
||||
parcel.mAddUserIds.add("shy");
|
||||
// passphrase is tested in PgpKeyOperationTest, just use empty here
|
||||
parcel.mNewPassphrase = "";
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
staticRingB = result.getRing();
|
||||
}
|
||||
|
||||
Assert.assertNotNull("initial test key creation must succeed", staticRingA);
|
||||
Assert.assertNotNull("initial test key creation must succeed", staticRingB);
|
||||
|
||||
// we sleep here for a second, to make sure all new certificates have different timestamps
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// show Log.x messages in system.out
|
||||
ShadowLog.stream = System.out;
|
||||
ringA = staticRingA;
|
||||
ringB = staticRingB;
|
||||
|
||||
// setting up some parameters just to reduce code duplication
|
||||
op = new PgpKeyOperation(new ProgressScaler(null, 0, 100, 100));
|
||||
|
||||
// set this up, gonna need it more than once
|
||||
parcel = new SaveKeyringParcel();
|
||||
parcel.mMasterKeyId = ringA.getMasterKeyId();
|
||||
parcel.mFingerprint = ringA.getFingerprint();
|
||||
}
|
||||
|
||||
public void testSelfNoOp() throws Exception {
|
||||
|
||||
UncachedKeyRing merged = mergeWithChecks(ringA, ringA, null);
|
||||
Assert.assertArrayEquals("keyring merged with itself must be identical",
|
||||
ringA.getEncoded(), merged.getEncoded()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentMasterKeyIds() throws Exception {
|
||||
|
||||
Assert.assertNotEquals("generated key ids must be different",
|
||||
ringA.getMasterKeyId(), ringB.getMasterKeyId());
|
||||
|
||||
Assert.assertNull("merging keys with differing key ids must fail",
|
||||
ringA.merge(ringB, log, 0));
|
||||
Assert.assertNull("merging keys with differing key ids must fail",
|
||||
ringB.merge(ringA, log, 0));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddedUserId() throws Exception {
|
||||
|
||||
UncachedKeyRing modifiedA, modifiedB; {
|
||||
CanonicalizedSecretKeyRing secretRing =
|
||||
new CanonicalizedSecretKeyRing(ringA.getEncoded(), false, 0);
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add("flim");
|
||||
modifiedA = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add("flam");
|
||||
modifiedB = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
}
|
||||
|
||||
{ // merge A into base
|
||||
UncachedKeyRing merged = mergeWithChecks(ringA, modifiedA);
|
||||
|
||||
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring must have gained two packets", 2, onlyB.size());
|
||||
Assert.assertTrue("merged keyring must contain new user id",
|
||||
merged.getPublicKey().getUnorderedUserIds().contains("flim"));
|
||||
}
|
||||
|
||||
{ // merge A into B
|
||||
UncachedKeyRing merged = mergeWithChecks(modifiedA, modifiedB, ringA);
|
||||
|
||||
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring must have gained four packets", 4, onlyB.size());
|
||||
Assert.assertTrue("merged keyring must contain first new user id",
|
||||
merged.getPublicKey().getUnorderedUserIds().contains("flim"));
|
||||
Assert.assertTrue("merged keyring must contain second new user id",
|
||||
merged.getPublicKey().getUnorderedUserIds().contains("flam"));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddedSubkeyId() throws Exception {
|
||||
|
||||
UncachedKeyRing modifiedA, modifiedB;
|
||||
long subKeyIdA, subKeyIdB;
|
||||
{
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ringA.getEncoded(), false, 0);
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
modifiedA = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
modifiedB = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
|
||||
subKeyIdA = KeyringTestingHelper.getSubkeyId(modifiedA, 2);
|
||||
subKeyIdB = KeyringTestingHelper.getSubkeyId(modifiedB, 2);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = mergeWithChecks(ringA, modifiedA);
|
||||
|
||||
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring must have gained two packets", 2, onlyB.size());
|
||||
|
||||
long mergedKeyId = KeyringTestingHelper.getSubkeyId(merged, 2);
|
||||
Assert.assertEquals("merged keyring must contain the new subkey", subKeyIdA, mergedKeyId);
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = mergeWithChecks(modifiedA, modifiedB, ringA);
|
||||
|
||||
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring must have gained four packets", 4, onlyB.size());
|
||||
|
||||
Iterator<UncachedPublicKey> it = merged.getPublicKeys();
|
||||
it.next(); it.next();
|
||||
Assert.assertEquals("merged keyring must contain the new subkey",
|
||||
subKeyIdA, it.next().getKeyId());
|
||||
Assert.assertEquals("merged keyring must contain both new subkeys",
|
||||
subKeyIdB, it.next().getKeyId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddedKeySignature() throws Exception {
|
||||
|
||||
final UncachedKeyRing modified; {
|
||||
parcel.reset();
|
||||
parcel.mRevokeSubKeys.add(KeyringTestingHelper.getSubkeyId(ringA, 1));
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
|
||||
ringA.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = ringA.merge(modified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertFalse(
|
||||
"merging keyring with extra signatures into its base should yield that same keyring",
|
||||
KeyringTestingHelper.diffKeyrings(merged.getEncoded(), modified.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddedUserIdSignature() throws Exception {
|
||||
|
||||
final UncachedKeyRing pubRing = ringA.extractPublicKeyRing();
|
||||
|
||||
final UncachedKeyRing modified; {
|
||||
CanonicalizedPublicKeyRing publicRing = new CanonicalizedPublicKeyRing(
|
||||
pubRing.getEncoded(), 0);
|
||||
|
||||
CanonicalizedSecretKey secretKey = new CanonicalizedSecretKeyRing(
|
||||
ringB.getEncoded(), false, 0).getSecretKey();
|
||||
secretKey.unlock("");
|
||||
// sign all user ids
|
||||
modified = secretKey.certifyUserIds(publicRing, publicRing.getPublicKey().getUnorderedUserIds());
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = ringA.merge(modified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertArrayEquals("foreign signatures should not be merged into secret key",
|
||||
ringA.getEncoded(), merged.getEncoded()
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
byte[] sig = KeyringTestingHelper.getNth(
|
||||
modified.getPublicKey().getSignaturesForId("twi"), 1).getEncoded();
|
||||
|
||||
// inject the (foreign!) signature into subkey signature position
|
||||
UncachedKeyRing moreModified = KeyringTestingHelper.injectPacket(modified, sig, 1);
|
||||
|
||||
UncachedKeyRing merged = ringA.merge(moreModified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertArrayEquals("foreign signatures should not be merged into secret key",
|
||||
ringA.getEncoded(), merged.getEncoded()
|
||||
);
|
||||
|
||||
merged = pubRing.merge(moreModified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertTrue(
|
||||
"merged keyring should contain new signature",
|
||||
KeyringTestingHelper.diffKeyrings(pubRing.getEncoded(), merged.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
Assert.assertEquals("merged keyring should be missing no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring should contain exactly two more packets", 2, onlyB.size());
|
||||
Assert.assertEquals("first added packet should be a signature",
|
||||
PacketTags.SIGNATURE, onlyB.get(0).tag);
|
||||
Assert.assertEquals("first added packet should be in the position we injected it at",
|
||||
1, onlyB.get(0).position);
|
||||
Assert.assertEquals("second added packet should be a signature",
|
||||
PacketTags.SIGNATURE, onlyB.get(1).tag);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = pubRing.merge(modified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertFalse(
|
||||
"merging keyring with extra signatures into its base should yield that same keyring",
|
||||
KeyringTestingHelper.diffKeyrings(merged.getEncoded(), modified.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private UncachedKeyRing mergeWithChecks(UncachedKeyRing a, UncachedKeyRing b)
|
||||
throws Exception {
|
||||
return mergeWithChecks(a, b, a);
|
||||
}
|
||||
|
||||
private UncachedKeyRing mergeWithChecks(UncachedKeyRing a, UncachedKeyRing b,
|
||||
UncachedKeyRing base)
|
||||
throws Exception {
|
||||
|
||||
Assert.assertTrue("merging keyring must be secret type", a.isSecret());
|
||||
Assert.assertTrue("merged keyring must be secret type", b.isSecret());
|
||||
|
||||
final UncachedKeyRing resultA;
|
||||
UncachedKeyRing resultB;
|
||||
|
||||
{ // sec + sec
|
||||
resultA = a.merge(b, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as sec(a)+sec(b)", resultA);
|
||||
|
||||
resultB = b.merge(a, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as sec(b)+sec(a)", resultB);
|
||||
|
||||
// check commutativity, if requested
|
||||
Assert.assertFalse("result of merge must be commutative",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
resultA.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
final UncachedKeyRing pubA = a.extractPublicKeyRing();
|
||||
final UncachedKeyRing pubB = b.extractPublicKeyRing();
|
||||
|
||||
{ // sec + pub, pub + sec, and pub + pub
|
||||
|
||||
try {
|
||||
resultB = a.merge(pubB, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as sec(a)+pub(b)", resultA);
|
||||
|
||||
Assert.assertFalse("result of sec(a)+pub(b) must be same as sec(a)+sec(b)",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
resultA.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
} catch (RuntimeException e) {
|
||||
System.out.println("special case, dummy key generation not in yet");
|
||||
}
|
||||
|
||||
final UncachedKeyRing pubResult = resultA.extractPublicKeyRing();
|
||||
|
||||
resultB = pubA.merge(b, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as pub(a)+sec(b)", resultA);
|
||||
|
||||
Assert.assertFalse("result of pub(a)+sec(b) must be same as pub(sec(a)+sec(b))",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
pubResult.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
|
||||
resultB = pubA.merge(pubB, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as pub(a)+pub(b)", resultA);
|
||||
|
||||
Assert.assertFalse("result of pub(a)+pub(b) must be same as pub(sec(a)+sec(b))",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
pubResult.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if (base != null) {
|
||||
// set up onlyA and onlyB to be a diff to the base
|
||||
Assert.assertTrue("merged keyring must differ from base",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
base.getEncoded(), resultA.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
return resultA;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package org.sufficientlysecure.keychain.tests;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
public class UncachedKeyringTest {
|
||||
|
||||
static UncachedKeyRing staticRing, staticPubRing;
|
||||
UncachedKeyRing ring, pubRing;
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
PgpKeyOperation op;
|
||||
SaveKeyringParcel parcel;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.ENCRYPT_COMMS, null));
|
||||
|
||||
parcel.mAddUserIds.add("twi");
|
||||
parcel.mAddUserIds.add("pink");
|
||||
// passphrase is tested in PgpKeyOperationTest, just use empty here
|
||||
parcel.mNewPassphrase = "";
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
staticRing = result.getRing();
|
||||
staticPubRing = staticRing.extractPublicKeyRing();
|
||||
|
||||
Assert.assertNotNull("initial test key creation must succeed", staticRing);
|
||||
|
||||
// we sleep here for a second, to make sure all new certificates have different timestamps
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// show Log.x messages in system.out
|
||||
ShadowLog.stream = System.out;
|
||||
ring = staticRing;
|
||||
pubRing = staticPubRing;
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testPublicKeyItRemove() throws Exception {
|
||||
Iterator<UncachedPublicKey> it = ring.getPublicKeys();
|
||||
it.remove();
|
||||
}
|
||||
|
||||
@Test(expected = PgpGeneralException.class)
|
||||
public void testDecodeFromEmpty() throws Exception {
|
||||
UncachedKeyRing.decodeFromData(new byte[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArmorIdentity() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ring.encodeArmored(out, "OpenKeychain");
|
||||
|
||||
Assert.assertArrayEquals("armor encoded and decoded ring should be identical to original",
|
||||
ring.getEncoded(),
|
||||
UncachedKeyRing.decodeFromData(out.toByteArray()).getEncoded());
|
||||
}
|
||||
|
||||
@Test(expected = PgpGeneralException.class)
|
||||
public void testDecodeEncodeMulti() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
// encode secret and public ring in here
|
||||
ring.encodeArmored(out, "OpenKeychain");
|
||||
pubRing.encodeArmored(out, "OpenKeychain");
|
||||
|
||||
Iterator<UncachedKeyRing> it =
|
||||
UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray()));
|
||||
Assert.assertTrue("there should be two rings in the stream", it.hasNext());
|
||||
Assert.assertArrayEquals("first ring should be the first we put in",
|
||||
ring.getEncoded(), it.next().getEncoded());
|
||||
Assert.assertTrue("there should be two rings in the stream", it.hasNext());
|
||||
Assert.assertArrayEquals("second ring should be the second we put in",
|
||||
pubRing.getEncoded(), it.next().getEncoded());
|
||||
Assert.assertFalse("there should be two rings in the stream", it.hasNext());
|
||||
|
||||
// this should fail with PgpGeneralException, since it expects exactly one ring
|
||||
UncachedKeyRing.decodeFromData(out.toByteArray());
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testPublicExtractPublic() throws Exception {
|
||||
// can't do this, either!
|
||||
pubRing.extractPublicKeyRing();
|
||||
}
|
||||
|
||||
}
|
@ -15,7 +15,6 @@ dependencies {
|
||||
compile project(':extern:spongycastle:pg')
|
||||
compile project(':extern:spongycastle:pkix')
|
||||
compile project(':extern:spongycastle:prov')
|
||||
compile project(':extern:AppMsg:library')
|
||||
compile project(':extern:SuperToasts:supertoasts')
|
||||
compile project(':extern:minidns')
|
||||
compile project(':extern:KeybaseLib:Lib')
|
||||
|
@ -31,7 +31,7 @@
|
||||
For OI Filemanager it makes no difference, gpg files can't be associated
|
||||
-->
|
||||
|
||||
<!-- Specified in buid.gradle -->
|
||||
<!-- Specified in build.gradle -->
|
||||
<!--<uses-sdk-->
|
||||
<!--android:minSdkVersion="9"-->
|
||||
<!--android:targetSdkVersion="19" />-->
|
||||
@ -53,10 +53,10 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
|
||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_PROFILE" />
|
||||
@ -84,22 +84,23 @@
|
||||
android:name=".ui.FirstTimeActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/app_name"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||
<activity
|
||||
android:name=".ui.CreateKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_create_key"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".ui.EditKeyActivityOld"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_edit_key"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
android:label="@string/title_create_key">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ui.KeyListActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.EditKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_edit_key"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
android:label="@string/title_edit_key" />
|
||||
<activity
|
||||
android:name=".ui.QrCodeActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/share_qr_code_dialog_title" />
|
||||
<activity
|
||||
android:name=".ui.ViewKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
@ -222,28 +223,8 @@
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<!-- Workaround to match files in pathes with dots in them, like /cdcard/my.folder/test.gpg -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="@string/intent_decrypt_file">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="*/*" />
|
||||
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data -->
|
||||
<data android:pathPattern=".*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.asc" />
|
||||
@ -254,6 +235,101 @@
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<!-- GnuPG binary encrypted/signed data, binary format -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<!-- PGP encrypted data, binary format -->
|
||||
<data android:pathPattern=".*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<!-- on some mail clients, PGP attachments show up as *.bin -->
|
||||
<data android:pathPattern=".*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Some apps will only respect these file associations
|
||||
if the mimeType is not set, and other apps will only respect them if mimeType is set
|
||||
to */*. Therefore we have two whole copies of the same thing, besides setting the mimeType.
|
||||
-->
|
||||
<intent-filter android:label="@string/intent_decrypt_file">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
|
||||
<data android:mimeType="*/*" />
|
||||
|
||||
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data -->
|
||||
<data android:pathPattern=".*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<!-- GnuPG binary encrypted/signed data, binary format -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<!-- PGP encrypted data, binary format -->
|
||||
<data android:pathPattern=".*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<!-- on some mail clients, PGP attachments show up as *.bin -->
|
||||
<data android:pathPattern=".*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
@ -323,6 +399,9 @@
|
||||
|
||||
<!-- mime type as defined in http://tools.ietf.org/html/rfc3156 -->
|
||||
<data android:mimeType="application/pgp-keys" />
|
||||
<!-- also link to text/plain, AOSP mail and K-9 mail only give mimeType text/plain
|
||||
when the key file has been manually attached -->
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<!-- NFC: Handle NFC tags detected from outside our application -->
|
||||
<intent-filter>
|
||||
@ -342,28 +421,8 @@
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
</intent-filter>
|
||||
<!-- VIEW with file endings: *.asc -->
|
||||
<intent-filter android:label="@string/intent_import_key">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="*/*" />
|
||||
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data -->
|
||||
<data android:pathPattern=".*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.asc" />
|
||||
@ -374,7 +433,103 @@
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<!-- GnuPG binary encrypted/signed data, binary format -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<!-- PGP encrypted data, binary format -->
|
||||
<data android:pathPattern=".*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<!-- on some mail clients, PGP attachments show up as *.bin -->
|
||||
<data android:pathPattern=".*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Some apps will only respect these file associations
|
||||
if the mimeType is not set, and other apps will only respect them if mimeType is set
|
||||
to */*. Therefore we have two whole copies of the same thing, besides setting the mimeType.
|
||||
-->
|
||||
<intent-filter android:label="@string/intent_import_key">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
|
||||
<data android:mimeType="*/*" />
|
||||
|
||||
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data -->
|
||||
<data android:pathPattern=".*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<!-- GnuPG binary encrypted/signed data, binary format -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<!-- PGP encrypted data, binary format -->
|
||||
<data android:pathPattern=".*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<!-- on some mail clients, PGP attachments show up as *.bin -->
|
||||
<data android:pathPattern=".*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Keychain's own Actions -->
|
||||
<!-- IMPORT_KEY with files TODO: does this work? -->
|
||||
<intent-filter android:label="@string/intent_import_key">
|
||||
@ -469,24 +624,24 @@
|
||||
|
||||
<service android:name=".service.DummyAccountService">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator"/>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/account_desc"/>
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/account_desc" />
|
||||
</service>
|
||||
|
||||
<service android:name=".service.ContactSyncAdapterService">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter"/>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_adapter_desc"/>
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_adapter_desc" />
|
||||
<meta-data
|
||||
android:name="android.provider.CONTACTS_STRUCTURE"
|
||||
android:resource="@xml/custom_pgp_contacts_structure"/>
|
||||
android:name="android.provider.CONTACTS_STRUCTURE"
|
||||
android:resource="@xml/custom_pgp_contacts_structure" />
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
@ -129,7 +129,7 @@ public class Preferences {
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getFirstTime() {
|
||||
public boolean isFirstTime() {
|
||||
return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.keyimport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.KeychainApplication;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* When sending large data (over 1MB) through Androids Binder IPC you get
|
||||
* JavaBinder E !!! FAILED BINDER TRANSACTION !!!
|
||||
* <p/>
|
||||
* To overcome this problem, we cache large Parcelables into a file in our private cache directory
|
||||
* instead of sending them through IPC.
|
||||
*/
|
||||
public class FileImportCache {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private static final String FILENAME = "key_import.pcl";
|
||||
private static final String BUNDLE_DATA = "data";
|
||||
|
||||
public FileImportCache(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
public void writeCache(ArrayList<ParcelableKeyRing> selectedEntries) throws IOException {
|
||||
Bundle in = new Bundle();
|
||||
in.putParcelableArrayList(BUNDLE_DATA, selectedEntries);
|
||||
File cacheDir = mContext.getCacheDir();
|
||||
if (cacheDir == null) {
|
||||
// https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
|
||||
throw new IOException("cache dir is null!");
|
||||
}
|
||||
File tempFile = new File(mContext.getCacheDir(), FILENAME);
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(tempFile);
|
||||
Parcel p = Parcel.obtain(); // creating empty parcel object
|
||||
in.writeToParcel(p, 0); // saving bundle as parcel
|
||||
fos.write(p.marshall()); // writing parcel to file
|
||||
fos.flush();
|
||||
fos.close();
|
||||
}
|
||||
|
||||
public List<ParcelableKeyRing> readCache() throws IOException {
|
||||
Parcel parcel = Parcel.obtain(); // creating empty parcel object
|
||||
Bundle out;
|
||||
File cacheDir = mContext.getCacheDir();
|
||||
if (cacheDir == null) {
|
||||
// https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
|
||||
throw new IOException("cache dir is null!");
|
||||
}
|
||||
|
||||
File tempFile = new File(cacheDir, FILENAME);
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(tempFile);
|
||||
byte[] array = new byte[(int) fis.getChannel().size()];
|
||||
fis.read(array, 0, array.length);
|
||||
fis.close();
|
||||
|
||||
parcel.unmarshall(array, 0, array.length);
|
||||
parcel.setDataPosition(0);
|
||||
out = parcel.readBundle(KeychainApplication.class.getClassLoader());
|
||||
out.putAll(out);
|
||||
|
||||
return out.getParcelableArrayList(BUNDLE_DATA);
|
||||
} finally {
|
||||
parcel.recycle();
|
||||
// delete temp file
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import de.measite.minidns.Client;
|
||||
import de.measite.minidns.Question;
|
||||
import de.measite.minidns.Record;
|
||||
import de.measite.minidns.record.SRV;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.helper.TlsHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
@ -102,8 +103,9 @@ public class HkpKeyserver extends Keyserver {
|
||||
*/
|
||||
public static final Pattern PUB_KEY_LINE = Pattern
|
||||
.compile("pub:([0-9a-fA-F]+):([0-9]+):([0-9]+):([0-9]+):([0-9]*):([rde]*)[ \n\r]*" // pub line
|
||||
+ "((uid:([^:]*):([0-9]+):([0-9]*):([rde]*)[ \n\r]*)+)", // one or more uid lines
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
+ "((uid:([^:]*):([0-9]+):([0-9]*):([rde]*)[ \n\r]*)+)", // one or more uid lines
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
/**
|
||||
* uid:%escaped uid string%:%creationdate%:%expirationdate%:%flags%
|
||||
@ -215,10 +217,18 @@ public class HkpKeyserver extends Keyserver {
|
||||
throw new HttpError(response, data);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new QueryFailedException("querying server(s) for '" + mHost + "' failed");
|
||||
throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Results are sorted by creation date of key!
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
* @throws QueryFailedException
|
||||
* @throws QueryNeedsRepairException
|
||||
*/
|
||||
@Override
|
||||
public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
|
||||
QueryNeedsRepairException {
|
||||
@ -240,18 +250,26 @@ public class HkpKeyserver extends Keyserver {
|
||||
try {
|
||||
data = query(request);
|
||||
} catch (HttpError e) {
|
||||
if (e.getCode() == 404) {
|
||||
return results;
|
||||
} else {
|
||||
if (e.getData() != null) {
|
||||
Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.US));
|
||||
|
||||
if (e.getData().toLowerCase(Locale.US).contains("no keys found")) {
|
||||
// NOTE: This is also a 404 error for some keyservers!
|
||||
return results;
|
||||
} else if (e.getData().toLowerCase(Locale.US).contains("too many")) {
|
||||
throw new TooManyResponsesException();
|
||||
} else if (e.getData().toLowerCase(Locale.US).contains("insufficient")) {
|
||||
throw new QueryTooShortException();
|
||||
} else if (e.getCode() == 404) {
|
||||
// NOTE: handle this 404 at last, maybe it was a "no keys found" error
|
||||
throw new QueryFailedException("Keyserver '" + mHost + "' not found. Error 404");
|
||||
} else {
|
||||
// NOTE: some keyserver do not provide a more detailed error response
|
||||
throw new QueryTooShortOrTooManyResponsesException();
|
||||
}
|
||||
}
|
||||
throw new QueryFailedException("querying server(s) for '" + mHost + "' failed");
|
||||
|
||||
throw new QueryFailedException("Querying server(s) for '" + mHost + "' failed.");
|
||||
}
|
||||
|
||||
final Matcher matcher = PUB_KEY_LINE.matcher(data);
|
||||
@ -267,9 +285,9 @@ public class HkpKeyserver extends Keyserver {
|
||||
|
||||
// group 1 contains the full fingerprint (v4) or the long key id if available
|
||||
// see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
|
||||
String fingerprintOrKeyId = matcher.group(1);
|
||||
String fingerprintOrKeyId = matcher.group(1).toLowerCase(Locale.US);
|
||||
if (fingerprintOrKeyId.length() > 16) {
|
||||
entry.setFingerprintHex(fingerprintOrKeyId.toLowerCase(Locale.US));
|
||||
entry.setFingerprintHex(fingerprintOrKeyId);
|
||||
entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length()
|
||||
- 16, fingerprintOrKeyId.length()));
|
||||
} else {
|
||||
@ -291,7 +309,7 @@ public class HkpKeyserver extends Keyserver {
|
||||
while (uidMatcher.find()) {
|
||||
String tmp = uidMatcher.group(1).trim();
|
||||
if (tmp.contains("%")) {
|
||||
if(tmp.contains("%%")) {
|
||||
if (tmp.contains("%%")) {
|
||||
// This is a fix for issue #683
|
||||
// The server encodes a percent sign as %%, so it is swapped out with its
|
||||
// urlencoded counterpart to prevent errors
|
||||
|
@ -44,6 +44,13 @@ public abstract class Keyserver {
|
||||
private static final long serialVersionUID = 2703768928624654514L;
|
||||
}
|
||||
|
||||
/**
|
||||
* query too short _or_ too many responses
|
||||
*/
|
||||
public static class QueryTooShortOrTooManyResponsesException extends QueryNeedsRepairException {
|
||||
private static final long serialVersionUID = 2703768928624654514L;
|
||||
}
|
||||
|
||||
public static class AddKeyException extends Exception {
|
||||
private static final long serialVersionUID = -507574859137295530L;
|
||||
}
|
||||
|
@ -16,13 +16,11 @@ import java.io.OutputStream;
|
||||
* getter method.
|
||||
*
|
||||
*/
|
||||
public abstract class WrappedKeyRing extends KeyRing {
|
||||
public abstract class CanonicalizedKeyRing extends KeyRing {
|
||||
|
||||
private final boolean mHasAnySecret;
|
||||
private final int mVerified;
|
||||
|
||||
WrappedKeyRing(boolean hasAnySecret, int verified) {
|
||||
mHasAnySecret = hasAnySecret;
|
||||
CanonicalizedKeyRing(int verified) {
|
||||
mVerified = verified;
|
||||
}
|
||||
|
||||
@ -30,10 +28,6 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
return getRing().getPublicKey().getKeyID();
|
||||
}
|
||||
|
||||
public boolean hasAnySecret() {
|
||||
return mHasAnySecret;
|
||||
}
|
||||
|
||||
public int getVerified() {
|
||||
return mVerified;
|
||||
}
|
||||
@ -56,7 +50,7 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
}
|
||||
|
||||
public long getEncryptId() throws PgpGeneralException {
|
||||
for(WrappedPublicKey key : publicKeyIterator()) {
|
||||
for(CanonicalizedPublicKey key : publicKeyIterator()) {
|
||||
if(key.canEncrypt()) {
|
||||
return key.getKeyId();
|
||||
}
|
||||
@ -74,7 +68,7 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
}
|
||||
|
||||
public long getSignId() throws PgpGeneralException {
|
||||
for(WrappedPublicKey key : publicKeyIterator()) {
|
||||
for(CanonicalizedPublicKey key : publicKeyIterator()) {
|
||||
if(key.canSign()) {
|
||||
return key.getKeyId();
|
||||
}
|
||||
@ -103,14 +97,14 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
|
||||
abstract PGPKeyRing getRing();
|
||||
|
||||
abstract public IterableIterator<WrappedPublicKey> publicKeyIterator();
|
||||
abstract public IterableIterator<CanonicalizedPublicKey> publicKeyIterator();
|
||||
|
||||
public WrappedPublicKey getPublicKey() {
|
||||
return new WrappedPublicKey(this, getRing().getPublicKey());
|
||||
public CanonicalizedPublicKey getPublicKey() {
|
||||
return new CanonicalizedPublicKey(this, getRing().getPublicKey());
|
||||
}
|
||||
|
||||
public WrappedPublicKey getPublicKey(long id) {
|
||||
return new WrappedPublicKey(this, getRing().getPublicKey(id));
|
||||
public CanonicalizedPublicKey getPublicKey(long id) {
|
||||
return new CanonicalizedPublicKey(this, getRing().getPublicKey(id));
|
||||
}
|
||||
|
||||
public byte[] getEncoded() throws IOException {
|
@ -14,12 +14,12 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
* stored in the database.
|
||||
*
|
||||
*/
|
||||
public class WrappedPublicKey extends UncachedPublicKey {
|
||||
public class CanonicalizedPublicKey extends UncachedPublicKey {
|
||||
|
||||
// this is the parent key ring
|
||||
final KeyRing mRing;
|
||||
|
||||
WrappedPublicKey(KeyRing ring, PGPPublicKey key) {
|
||||
CanonicalizedPublicKey(KeyRing ring, PGPPublicKey key) {
|
||||
super(key);
|
||||
mRing = ring;
|
||||
}
|
@ -1,42 +1,45 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class WrappedPublicKeyRing extends WrappedKeyRing {
|
||||
public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
||||
|
||||
private PGPPublicKeyRing mRing;
|
||||
private final byte[] mPubKey;
|
||||
|
||||
public WrappedPublicKeyRing(byte[] blob, boolean hasAnySecret, int verified) {
|
||||
super(hasAnySecret, verified);
|
||||
mPubKey = blob;
|
||||
CanonicalizedPublicKeyRing(PGPPublicKeyRing ring, int verified) {
|
||||
super(verified);
|
||||
mRing = ring;
|
||||
}
|
||||
|
||||
public CanonicalizedPublicKeyRing(byte[] blob, int verified) {
|
||||
super(verified);
|
||||
if(mRing == null) {
|
||||
// get first object in block
|
||||
PGPObjectFactory factory = new PGPObjectFactory(blob);
|
||||
try {
|
||||
Object obj = factory.nextObject();
|
||||
if (! (obj instanceof PGPPublicKeyRing)) {
|
||||
throw new RuntimeException("Error constructing CanonicalizedPublicKeyRing, should never happen!");
|
||||
}
|
||||
mRing = (PGPPublicKeyRing) obj;
|
||||
if (factory.nextObject() != null) {
|
||||
throw new RuntimeException("Encountered trailing data after keyring, should never happen!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IO Error constructing CanonicalizedPublicKeyRing, should never happen!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PGPPublicKeyRing getRing() {
|
||||
if(mRing == null) {
|
||||
PGPObjectFactory factory = new PGPObjectFactory(mPubKey);
|
||||
PGPKeyRing keyRing = null;
|
||||
try {
|
||||
if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) {
|
||||
Log.e(Constants.TAG, "No keys given!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error while converting to PGPKeyRing!", e);
|
||||
}
|
||||
|
||||
mRing = (PGPPublicKeyRing) keyRing;
|
||||
}
|
||||
return mRing;
|
||||
}
|
||||
|
||||
@ -45,10 +48,10 @@ public class WrappedPublicKeyRing extends WrappedKeyRing {
|
||||
}
|
||||
|
||||
/** Getter that returns the subkey that should be used for signing. */
|
||||
WrappedPublicKey getEncryptionSubKey() throws PgpGeneralException {
|
||||
CanonicalizedPublicKey getEncryptionSubKey() throws PgpGeneralException {
|
||||
PGPPublicKey key = getRing().getPublicKey(getEncryptId());
|
||||
if(key != null) {
|
||||
WrappedPublicKey cKey = new WrappedPublicKey(this, key);
|
||||
CanonicalizedPublicKey cKey = new CanonicalizedPublicKey(this, key);
|
||||
if(!cKey.canEncrypt()) {
|
||||
throw new PgpGeneralException("key error");
|
||||
}
|
||||
@ -57,18 +60,18 @@ public class WrappedPublicKeyRing extends WrappedKeyRing {
|
||||
throw new PgpGeneralException("no encryption key available");
|
||||
}
|
||||
|
||||
public IterableIterator<WrappedPublicKey> publicKeyIterator() {
|
||||
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
|
||||
@SuppressWarnings("unchecked")
|
||||
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
|
||||
return new IterableIterator<WrappedPublicKey>(new Iterator<WrappedPublicKey>() {
|
||||
return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedPublicKey next() {
|
||||
return new WrappedPublicKey(WrappedPublicKeyRing.this, it.next());
|
||||
public CanonicalizedPublicKey next() {
|
||||
return new CanonicalizedPublicKey(CanonicalizedPublicKeyRing.this, it.next());
|
||||
}
|
||||
|
||||
@Override
|
@ -43,7 +43,7 @@ import java.util.List;
|
||||
* properly imported secret keys only.
|
||||
*
|
||||
*/
|
||||
public class WrappedSecretKey extends WrappedPublicKey {
|
||||
public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
|
||||
private final PGPSecretKey mSecretKey;
|
||||
private PGPPrivateKey mPrivateKey = null;
|
||||
@ -53,21 +53,13 @@ public class WrappedSecretKey extends WrappedPublicKey {
|
||||
private static int PRIVATE_KEY_STATE_UNLOCKED = 1;
|
||||
private static int PRIVATE_KEY_STATE_DIVERT_TO_CARD = 2;
|
||||
|
||||
WrappedSecretKey(WrappedSecretKeyRing ring, PGPSecretKey key) {
|
||||
CanonicalizedSecretKey(CanonicalizedSecretKeyRing ring, PGPSecretKey key) {
|
||||
super(ring, key.getPublicKey());
|
||||
mSecretKey = key;
|
||||
}
|
||||
|
||||
public WrappedSecretKeyRing getRing() {
|
||||
return (WrappedSecretKeyRing) mRing;
|
||||
}
|
||||
|
||||
/** Returns the wrapped PGPSecretKeyRing.
|
||||
* This function is for compatibility only, should not be used anymore and will be removed
|
||||
*/
|
||||
@Deprecated
|
||||
public PGPSecretKey getKeyExternal() {
|
||||
return mSecretKey;
|
||||
public CanonicalizedSecretKeyRing getRing() {
|
||||
return (CanonicalizedSecretKeyRing) mRing;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,7 +181,7 @@ public class WrappedSecretKey extends WrappedPublicKey {
|
||||
* @param userIds User IDs to certify, must not be null or empty
|
||||
* @return A keyring with added certifications
|
||||
*/
|
||||
public UncachedKeyRing certifyUserIds(WrappedPublicKeyRing publicKeyRing, List<String> userIds)
|
||||
public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, List<String> userIds)
|
||||
throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||
PGPException, SignatureException {
|
||||
|
@ -1,10 +1,12 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.S2K;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
@ -15,15 +17,21 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class WrappedSecretKeyRing extends WrappedKeyRing {
|
||||
public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
|
||||
|
||||
private PGPSecretKeyRing mRing;
|
||||
|
||||
public WrappedSecretKeyRing(byte[] blob, boolean isRevoked, int verified)
|
||||
CanonicalizedSecretKeyRing(PGPSecretKeyRing ring, int verified) {
|
||||
super(verified);
|
||||
mRing = ring;
|
||||
}
|
||||
|
||||
public CanonicalizedSecretKeyRing(byte[] blob, boolean isRevoked, int verified)
|
||||
{
|
||||
super(isRevoked, verified);
|
||||
super(verified);
|
||||
PGPObjectFactory factory = new PGPObjectFactory(blob);
|
||||
PGPKeyRing keyRing = null;
|
||||
try {
|
||||
@ -41,19 +49,32 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
|
||||
return mRing;
|
||||
}
|
||||
|
||||
public WrappedSecretKey getSecretKey() {
|
||||
return new WrappedSecretKey(this, mRing.getSecretKey());
|
||||
public CanonicalizedSecretKey getSecretKey() {
|
||||
return new CanonicalizedSecretKey(this, mRing.getSecretKey());
|
||||
}
|
||||
|
||||
public WrappedSecretKey getSecretKey(long id) {
|
||||
return new WrappedSecretKey(this, mRing.getSecretKey(id));
|
||||
public CanonicalizedSecretKey getSecretKey(long id) {
|
||||
return new CanonicalizedSecretKey(this, mRing.getSecretKey(id));
|
||||
}
|
||||
|
||||
public HashSet<Long> getAvailableSubkeys() {
|
||||
HashSet<Long> result = new HashSet<Long>();
|
||||
// then, mark exactly the keys we have available
|
||||
for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(getRing().getSecretKeys())) {
|
||||
S2K s2k = sub.getS2K();
|
||||
// add key, except if the private key has been stripped (GNU extension)
|
||||
if(s2k == null || (s2k.getProtectionMode() != S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY)) {
|
||||
result.add(sub.getKeyID());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Getter that returns the subkey that should be used for signing. */
|
||||
WrappedSecretKey getSigningSubKey() throws PgpGeneralException {
|
||||
CanonicalizedSecretKey getSigningSubKey() throws PgpGeneralException {
|
||||
PGPSecretKey key = mRing.getSecretKey(getSignId());
|
||||
if(key != null) {
|
||||
WrappedSecretKey cKey = new WrappedSecretKey(this, key);
|
||||
CanonicalizedSecretKey cKey = new CanonicalizedSecretKey(this, key);
|
||||
if(!cKey.canSign()) {
|
||||
throw new PgpGeneralException("key error");
|
||||
}
|
||||
@ -88,17 +109,17 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
public IterableIterator<WrappedSecretKey> secretKeyIterator() {
|
||||
public IterableIterator<CanonicalizedSecretKey> secretKeyIterator() {
|
||||
final Iterator<PGPSecretKey> it = mRing.getSecretKeys();
|
||||
return new IterableIterator<WrappedSecretKey>(new Iterator<WrappedSecretKey>() {
|
||||
return new IterableIterator<CanonicalizedSecretKey>(new Iterator<CanonicalizedSecretKey>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedSecretKey next() {
|
||||
return new WrappedSecretKey(WrappedSecretKeyRing.this, it.next());
|
||||
public CanonicalizedSecretKey next() {
|
||||
return new CanonicalizedSecretKey(CanonicalizedSecretKeyRing.this, it.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,17 +129,17 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
|
||||
});
|
||||
}
|
||||
|
||||
public IterableIterator<WrappedPublicKey> publicKeyIterator() {
|
||||
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
|
||||
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
|
||||
return new IterableIterator<WrappedPublicKey>(new Iterator<WrappedPublicKey>() {
|
||||
return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedPublicKey next() {
|
||||
return new WrappedPublicKey(WrappedSecretKeyRing.this, it.next());
|
||||
public CanonicalizedPublicKey next() {
|
||||
return new CanonicalizedPublicKey(CanonicalizedSecretKeyRing.this, it.next());
|
||||
}
|
||||
|
||||
@Override
|
@ -12,7 +12,7 @@ import java.util.regex.Pattern;
|
||||
* keyring should in all cases agree on the output of all methods described
|
||||
* here.
|
||||
*
|
||||
* @see org.sufficientlysecure.keychain.pgp.WrappedKeyRing
|
||||
* @see CanonicalizedKeyRing
|
||||
* @see org.sufficientlysecure.keychain.provider.CachedPublicKeyRing
|
||||
*
|
||||
*/
|
||||
|
@ -231,7 +231,7 @@ public class PgpDecryptVerify {
|
||||
|
||||
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
|
||||
PGPPBEEncryptedData encryptedDataSymmetric = null;
|
||||
WrappedSecretKey secretEncryptionKey = null;
|
||||
CanonicalizedSecretKey secretEncryptionKey = null;
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
boolean asymmetricPacketFound = false;
|
||||
boolean symmetricPacketFound = false;
|
||||
@ -243,10 +243,10 @@ public class PgpDecryptVerify {
|
||||
|
||||
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
|
||||
|
||||
WrappedSecretKeyRing secretKeyRing;
|
||||
CanonicalizedSecretKeyRing secretKeyRing;
|
||||
try {
|
||||
// get actual keyring object based on master key id
|
||||
secretKeyRing = mProviderHelper.getWrappedSecretKeyRing(
|
||||
secretKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(encData.getKeyID())
|
||||
);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
@ -365,8 +365,8 @@ public class PgpDecryptVerify {
|
||||
Object dataChunk = plainFact.nextObject();
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
int signatureIndex = -1;
|
||||
WrappedPublicKeyRing signingRing = null;
|
||||
WrappedPublicKey signingKey = null;
|
||||
CanonicalizedPublicKeyRing signingRing = null;
|
||||
CanonicalizedPublicKey signingKey = null;
|
||||
|
||||
if (dataChunk instanceof PGPCompressedData) {
|
||||
updateProgress(R.string.progress_decompressing_data, currentProgress, 100);
|
||||
@ -390,7 +390,7 @@ public class PgpDecryptVerify {
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
signingRing = mProviderHelper.getWrappedPublicKeyRing(
|
||||
signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
@ -566,8 +566,8 @@ public class PgpDecryptVerify {
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
WrappedPublicKeyRing signingRing = null;
|
||||
WrappedPublicKey signingKey = null;
|
||||
CanonicalizedPublicKeyRing signingRing = null;
|
||||
CanonicalizedPublicKey signingKey = null;
|
||||
int signatureIndex = -1;
|
||||
|
||||
// go through all signatures
|
||||
@ -575,7 +575,7 @@ public class PgpDecryptVerify {
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
signingRing = mProviderHelper.getWrappedPublicKeyRing(
|
||||
signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
|
@ -34,7 +34,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
@ -93,7 +93,7 @@ public class PgpImportExport {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean uploadKeyRingToServer(HkpKeyserver server, WrappedPublicKeyRing keyring) {
|
||||
public boolean uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream aos = null;
|
||||
try {
|
||||
@ -123,14 +123,14 @@ public class PgpImportExport {
|
||||
}
|
||||
|
||||
/** Imports keys from given data. If keyIds is given only those are imported */
|
||||
public ImportResult importKeyRings(List<ParcelableKeyRing> entries) {
|
||||
public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries) {
|
||||
|
||||
updateProgress(R.string.progress_importing, 0, 100);
|
||||
|
||||
// If there aren't even any keys, do nothing here.
|
||||
if (entries == null || entries.size() == 0) {
|
||||
return new ImportResult(
|
||||
ImportResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0);
|
||||
return new ImportKeyResult(
|
||||
ImportKeyResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0);
|
||||
}
|
||||
|
||||
int newKeys = 0, oldKeys = 0, badKeys = 0;
|
||||
@ -185,26 +185,26 @@ public class PgpImportExport {
|
||||
int resultType = 0;
|
||||
// special return case: no new keys at all
|
||||
if (badKeys == 0 && newKeys == 0 && oldKeys == 0) {
|
||||
resultType = ImportResult.RESULT_FAIL_NOTHING;
|
||||
resultType = ImportKeyResult.RESULT_FAIL_NOTHING;
|
||||
} else {
|
||||
if (newKeys > 0) {
|
||||
resultType |= ImportResult.RESULT_OK_NEWKEYS;
|
||||
resultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
|
||||
}
|
||||
if (oldKeys > 0) {
|
||||
resultType |= ImportResult.RESULT_OK_UPDATED;
|
||||
resultType |= ImportKeyResult.RESULT_OK_UPDATED;
|
||||
}
|
||||
if (badKeys > 0) {
|
||||
resultType |= ImportResult.RESULT_WITH_ERRORS;
|
||||
resultType |= ImportKeyResult.RESULT_WITH_ERRORS;
|
||||
if (newKeys == 0 && oldKeys == 0) {
|
||||
resultType |= ImportResult.RESULT_ERROR;
|
||||
resultType |= ImportKeyResult.RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
if (log.containsWarnings()) {
|
||||
resultType |= ImportResult.RESULT_WITH_WARNINGS;
|
||||
resultType |= ImportKeyResult.RESULT_WITH_WARNINGS;
|
||||
}
|
||||
}
|
||||
|
||||
return new ImportResult(resultType, log, newKeys, oldKeys, badKeys);
|
||||
return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys);
|
||||
|
||||
}
|
||||
|
||||
@ -235,7 +235,7 @@ public class PgpImportExport {
|
||||
updateProgress(progress * 100 / masterKeyIdsSize, 100);
|
||||
|
||||
try {
|
||||
WrappedPublicKeyRing ring = mProviderHelper.getWrappedPublicKeyRing(
|
||||
CanonicalizedPublicKeyRing ring = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingUri(pubKeyMasterId)
|
||||
);
|
||||
|
||||
@ -263,8 +263,8 @@ public class PgpImportExport {
|
||||
updateProgress(progress * 100 / masterKeyIdsSize, 100);
|
||||
|
||||
try {
|
||||
WrappedSecretKeyRing secretKeyRing =
|
||||
mProviderHelper.getWrappedSecretKeyRing(secretKeyMasterId);
|
||||
CanonicalizedSecretKeyRing secretKeyRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(secretKeyMasterId);
|
||||
secretKeyRing.encode(arOutStream);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
|
@ -46,15 +46,17 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Primes;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
@ -67,6 +69,7 @@ import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* This class is the single place where ALL operations that actually modify a PGP public or secret
|
||||
@ -78,7 +81,7 @@ import java.util.Iterator;
|
||||
* This indicator may be null.
|
||||
*/
|
||||
public class PgpKeyOperation {
|
||||
private Progressable mProgress;
|
||||
private Stack<Progressable> mProgress;
|
||||
|
||||
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
|
||||
SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192,
|
||||
@ -92,13 +95,34 @@ public class PgpKeyOperation {
|
||||
|
||||
public PgpKeyOperation(Progressable progress) {
|
||||
super();
|
||||
this.mProgress = progress;
|
||||
if (progress != null) {
|
||||
mProgress = new Stack<Progressable>();
|
||||
mProgress.push(progress);
|
||||
}
|
||||
}
|
||||
|
||||
void updateProgress(int message, int current, int total) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgress(message, current, total);
|
||||
private void subProgressPush(int from, int to) {
|
||||
if (mProgress == null) {
|
||||
return;
|
||||
}
|
||||
mProgress.push(new ProgressScaler(mProgress.peek(), from, to, 100));
|
||||
}
|
||||
private void subProgressPop() {
|
||||
if (mProgress == null) {
|
||||
return;
|
||||
}
|
||||
if (mProgress.size() == 1) {
|
||||
throw new RuntimeException("Tried to pop progressable without prior push! "
|
||||
+ "This is a programming error, please file a bug report.");
|
||||
}
|
||||
mProgress.pop();
|
||||
}
|
||||
|
||||
private void progress(int message, int current) {
|
||||
if (mProgress == null) {
|
||||
return;
|
||||
}
|
||||
mProgress.peek().setProgress(message, current, 100);
|
||||
}
|
||||
|
||||
/** Creates new secret key. */
|
||||
@ -115,6 +139,7 @@ public class PgpKeyOperation {
|
||||
|
||||
switch (algorithmChoice) {
|
||||
case Constants.choice.algorithm.dsa: {
|
||||
progress(R.string.progress_generating_dsa, 30);
|
||||
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
algorithm = PGPPublicKey.DSA;
|
||||
@ -122,6 +147,7 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
case Constants.choice.algorithm.elgamal: {
|
||||
progress(R.string.progress_generating_elgamal, 30);
|
||||
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
BigInteger p = Primes.getBestPrime(keySize);
|
||||
BigInteger g = new BigInteger("2");
|
||||
@ -134,6 +160,7 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
case Constants.choice.algorithm.rsa: {
|
||||
progress(R.string.progress_generating_rsa, 30);
|
||||
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
|
||||
@ -163,43 +190,49 @@ public class PgpKeyOperation {
|
||||
}
|
||||
}
|
||||
|
||||
public UncachedKeyRing createSecretKeyRing(SaveKeyringParcel saveParcel, OperationLog log,
|
||||
int indent) {
|
||||
public EditKeyResult createSecretKeyRing(SaveKeyringParcel saveParcel) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
int indent = 0;
|
||||
|
||||
try {
|
||||
|
||||
log.add(LogLevel.START, LogType.MSG_CR, indent);
|
||||
progress(R.string.progress_building_key, 0);
|
||||
indent += 1;
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
|
||||
if (saveParcel.mAddSubKeys.isEmpty()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_MASTER, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
if (saveParcel.mAddUserIds.isEmpty()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_USER_ID, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
SubkeyAdd add = saveParcel.mAddSubKeys.remove(0);
|
||||
if ((add.mFlags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_CERTIFY, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
if (add.mAlgorithm == Constants.choice.algorithm.elgamal) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
subProgressPush(10, 30);
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
|
||||
subProgressPop();
|
||||
|
||||
// return null if this failed (an error will already have been logged by createKey)
|
||||
if (keyPair == null) {
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
progress(R.string.progress_building_master_key, 40);
|
||||
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(HashAlgorithmTags.SHA1);
|
||||
@ -213,15 +246,16 @@ public class PgpKeyOperation {
|
||||
PGPSecretKeyRing sKR = new PGPSecretKeyRing(
|
||||
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
|
||||
|
||||
return internal(sKR, masterSecretKey, add.mFlags, saveParcel, "", log, indent);
|
||||
subProgressPush(50, 100);
|
||||
return internal(sKR, masterSecretKey, add.mFlags, saveParcel, "", log);
|
||||
|
||||
} catch (PGPException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
Log.e(Constants.TAG, "pgp error encoding key", e);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "io error encoding key", e);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -237,8 +271,11 @@ public class PgpKeyOperation {
|
||||
* are changed by adding new certificates, which implicitly override older certificates.
|
||||
*
|
||||
*/
|
||||
public UncachedKeyRing modifySecretKeyRing(WrappedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
|
||||
String passphrase, OperationLog log, int indent) {
|
||||
public EditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
|
||||
String passphrase) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
int indent = 0;
|
||||
|
||||
/*
|
||||
* 1. Unlock private key
|
||||
@ -251,14 +288,15 @@ public class PgpKeyOperation {
|
||||
* 6. If requested, change passphrase
|
||||
*/
|
||||
|
||||
log.add(LogLevel.START, LogType.MSG_MF, indent);
|
||||
log.add(LogLevel.START, LogType.MSG_MF, indent,
|
||||
PgpKeyHelper.convertKeyIdToHex(wsKR.getMasterKeyId()));
|
||||
indent += 1;
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
progress(R.string.progress_building_key, 0);
|
||||
|
||||
// Make sure this is called with a proper SaveKeyringParcel
|
||||
if (saveParcel.mMasterKeyId == null || saveParcel.mMasterKeyId != wsKR.getMasterKeyId()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_KEYID, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// We work on bouncycastle object level here
|
||||
@ -270,27 +308,30 @@ public class PgpKeyOperation {
|
||||
|| !Arrays.equals(saveParcel.mFingerprint,
|
||||
masterSecretKey.getPublicKey().getFingerprint())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_FINGERPRINT, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// read masterKeyFlags, and use the same as before.
|
||||
// since this is the master key, this contains at least CERTIFY_OTHER
|
||||
int masterKeyFlags = readKeyFlags(masterSecretKey.getPublicKey()) | KeyFlags.CERTIFY_OTHER;
|
||||
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log, indent);
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log);
|
||||
|
||||
}
|
||||
|
||||
private UncachedKeyRing internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
|
||||
private EditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
|
||||
int masterKeyFlags,
|
||||
SaveKeyringParcel saveParcel, String passphrase,
|
||||
OperationLog log, int indent) {
|
||||
OperationLog log) {
|
||||
|
||||
updateProgress(R.string.progress_certifying_master_key, 20, 100);
|
||||
int indent = 1;
|
||||
|
||||
progress(R.string.progress_modify, 0);
|
||||
|
||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||
|
||||
// 1. Unlock private key
|
||||
progress(R.string.progress_modify_unlock, 10);
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_UNLOCK, indent);
|
||||
PGPPrivateKey masterPrivateKey;
|
||||
{
|
||||
@ -300,7 +341,7 @@ public class PgpKeyOperation {
|
||||
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
|
||||
} catch (PGPException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -310,9 +351,18 @@ public class PgpKeyOperation {
|
||||
PGPPublicKey modifiedPublicKey = masterPublicKey;
|
||||
|
||||
// 2a. Add certificates for new user ids
|
||||
for (String userId : saveParcel.mAddUserIds) {
|
||||
subProgressPush(15, 25);
|
||||
for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_adduid, (i-1) * (100 / saveParcel.mAddUserIds.size()));
|
||||
String userId = saveParcel.mAddUserIds.get(i);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent);
|
||||
|
||||
if (userId.equals("")) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_UID_ERROR_EMPTY, indent+1);
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// this operation supersedes all previous binding and revocation certificates,
|
||||
// so remove those to retain assertions from canonicalization for later operations
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -322,7 +372,7 @@ public class PgpKeyOperation {
|
||||
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
|
||||
// foreign certificate?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION
|
||||
|| cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
|
||||
@ -343,19 +393,27 @@ public class PgpKeyOperation {
|
||||
masterPublicKey, userId, isPrimary, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 2b. Add revocations for revoked user ids
|
||||
for (String userId : saveParcel.mRevokeUserIds) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent);
|
||||
subProgressPush(25, 40);
|
||||
for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_revokeuid, (i-1) * (100 / saveParcel.mRevokeUserIds.size()));
|
||||
String userId = saveParcel.mRevokeUserIds.get(i);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent, userId);
|
||||
|
||||
// a duplicate revocation will be removed during canonicalization, so no need to
|
||||
// take care of that here.
|
||||
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
|
||||
masterPublicKey, userId);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 3. If primary user id changed, generate new certificates for both old and new
|
||||
if (saveParcel.mChangePrimaryUserId != null) {
|
||||
progress(R.string.progress_modify_primaryuid, 40);
|
||||
|
||||
// keep track if we actually changed one
|
||||
boolean ok = false;
|
||||
@ -373,7 +431,7 @@ public class PgpKeyOperation {
|
||||
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
|
||||
// foreign certificate?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
// we know from canonicalization that if there is any revocation here, it
|
||||
// is valid and not superseded by a newer certification.
|
||||
@ -394,7 +452,7 @@ public class PgpKeyOperation {
|
||||
if (currentCert == null) {
|
||||
// no certificate found?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// we definitely should not update certifications of revoked keys, so just leave it.
|
||||
@ -402,13 +460,14 @@ public class PgpKeyOperation {
|
||||
// revoked user ids cannot be primary!
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this is~ the/a primary user id
|
||||
if (currentCert.hasSubpackets() && currentCert.getHashedSubPackets().isPrimaryUserID()) {
|
||||
if (currentCert.getHashedSubPackets() != null
|
||||
&& currentCert.getHashedSubPackets().isPrimaryUserID()) {
|
||||
// if it's the one we want, just leave it as is
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
ok = true;
|
||||
@ -448,7 +507,7 @@ public class PgpKeyOperation {
|
||||
|
||||
if (!ok) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -460,21 +519,25 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
// 4a. For each subkey change, generate new subkey binding certificate
|
||||
for (SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {
|
||||
subProgressPush(50, 60);
|
||||
for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_subkeychange, (i-1) * (100 / saveParcel.mChangeSubKeys.size()));
|
||||
SaveKeyringParcel.SubkeyChange change = saveParcel.mChangeSubKeys.get(i);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,
|
||||
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 null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
|
||||
if (sKey == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
|
||||
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
PGPPublicKey pKey = sKey.getPublicKey();
|
||||
|
||||
@ -482,7 +545,7 @@ public class PgpKeyOperation {
|
||||
if (change.mExpiry != null && new Date(change.mExpiry*1000).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY,
|
||||
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// keep old flags, or replace with new ones
|
||||
@ -514,16 +577,22 @@ public class PgpKeyOperation {
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 4b. For each subkey revocation, generate new subkey revocation certificate
|
||||
for (long revocation : saveParcel.mRevokeSubKeys) {
|
||||
subProgressPush(60, 70);
|
||||
for (int i = 0; i < saveParcel.mRevokeSubKeys.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_subkeyrevoke, (i-1) * (100 / saveParcel.mRevokeSubKeys.size()));
|
||||
long revocation = saveParcel.mRevokeSubKeys.get(i);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_REVOKE,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(revocation));
|
||||
|
||||
PGPSecretKey sKey = sKR.getSecretKey(revocation);
|
||||
if (sKey == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
|
||||
indent+1, PgpKeyHelper.convertKeyIdToHex(revocation));
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
PGPPublicKey pKey = sKey.getPublicKey();
|
||||
|
||||
@ -533,21 +602,30 @@ public class PgpKeyOperation {
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 5. Generate and add new subkeys
|
||||
for (SaveKeyringParcel.SubkeyAdd add : saveParcel.mAddSubKeys) {
|
||||
subProgressPush(70, 90);
|
||||
for (int i = 0; i < saveParcel.mAddSubKeys.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_subkeyadd, (i-1) * (100 / saveParcel.mAddSubKeys.size()));
|
||||
SaveKeyringParcel.SubkeyAdd add = saveParcel.mAddSubKeys.get(0);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
|
||||
|
||||
if (add.mExpiry != null && new Date(add.mExpiry*1000).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
|
||||
|
||||
// generate a new secret key (privkey only for now)
|
||||
subProgressPush(
|
||||
(i-1) * (100 / saveParcel.mAddSubKeys.size()),
|
||||
i * (100 / saveParcel.mAddSubKeys.size())
|
||||
);
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
|
||||
subProgressPop();
|
||||
if(keyPair == null) {
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// add subkey binding signature (making this a sub rather than master key)
|
||||
@ -577,9 +655,11 @@ public class PgpKeyOperation {
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 6. If requested, change passphrase
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
progress(R.string.progress_modify_passphrase, 90);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent);
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
|
||||
.get(HashAlgorithmTags.SHA1);
|
||||
@ -597,18 +677,19 @@ public class PgpKeyOperation {
|
||||
// This one must only be thrown by
|
||||
} catch (IOException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_ENCODE, indent+1);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "encountered pgp error while modifying key", e);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent+1);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
} catch (SignatureException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SIG, indent+1);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
progress(R.string.progress_done, 100);
|
||||
log.add(LogLevel.OK, LogType.MSG_MF_SUCCESS, indent);
|
||||
return new UncachedKeyRing(sKR);
|
||||
return new EditKeyResult(OperationResultParcel.RESULT_OK, log, new UncachedKeyRing(sKR));
|
||||
|
||||
}
|
||||
|
||||
@ -735,7 +816,7 @@ public class PgpKeyOperation {
|
||||
int flags = 0;
|
||||
//noinspection unchecked
|
||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
if (!sig.hasSubpackets()) {
|
||||
if (sig.getHashedSubPackets() == null) {
|
||||
continue;
|
||||
}
|
||||
flags |= sig.getHashedSubPackets().getKeyFlags();
|
||||
|
@ -278,11 +278,11 @@ public class PgpSignEncrypt {
|
||||
}
|
||||
|
||||
/* Get keys for signature generation for later usage */
|
||||
WrappedSecretKey signingKey = null;
|
||||
CanonicalizedSecretKey signingKey = null;
|
||||
if (enableSignature) {
|
||||
WrappedSecretKeyRing signingKeyRing;
|
||||
CanonicalizedSecretKeyRing signingKeyRing;
|
||||
try {
|
||||
signingKeyRing = mProviderHelper.getWrappedSecretKeyRing(mSignatureMasterKeyId);
|
||||
signingKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(mSignatureMasterKeyId);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
throw new NoSigningKeyException();
|
||||
}
|
||||
@ -337,9 +337,9 @@ public class PgpSignEncrypt {
|
||||
// Asymmetric encryption
|
||||
for (long id : mEncryptionMasterKeyIds) {
|
||||
try {
|
||||
WrappedPublicKeyRing keyRing = mProviderHelper.getWrappedPublicKeyRing(
|
||||
CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingUri(id));
|
||||
WrappedPublicKey key = keyRing.getEncryptionSubKey();
|
||||
CanonicalizedPublicKey key = keyRing.getEncryptionSubKey();
|
||||
cPk.addMethod(key.getPubKeyEncryptionGenerator());
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.bcpg.S2K;
|
||||
import org.spongycastle.bcpg.SignatureSubpacketTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPKeyFlags;
|
||||
@ -14,28 +13,25 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureList;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Vector;
|
||||
|
||||
/** Wrapper around PGPKeyRing class, to be constructed from bytes.
|
||||
*
|
||||
@ -49,7 +45,7 @@ import java.util.Vector;
|
||||
* treated equally for most purposes in UI code. It is up to the programmer to
|
||||
* take care of the differences.
|
||||
*
|
||||
* @see org.sufficientlysecure.keychain.pgp.WrappedKeyRing
|
||||
* @see CanonicalizedKeyRing
|
||||
* @see org.sufficientlysecure.keychain.pgp.UncachedPublicKey
|
||||
* @see org.sufficientlysecure.keychain.pgp.UncachedSecretKey
|
||||
*
|
||||
@ -59,18 +55,10 @@ public class UncachedKeyRing {
|
||||
|
||||
final PGPKeyRing mRing;
|
||||
final boolean mIsSecret;
|
||||
final boolean mIsCanonicalized;
|
||||
|
||||
UncachedKeyRing(PGPKeyRing ring) {
|
||||
mRing = ring;
|
||||
mIsSecret = ring instanceof PGPSecretKeyRing;
|
||||
mIsCanonicalized = false;
|
||||
}
|
||||
|
||||
private UncachedKeyRing(PGPKeyRing ring, boolean canonicalized) {
|
||||
mRing = ring;
|
||||
mIsSecret = ring instanceof PGPSecretKeyRing;
|
||||
mIsCanonicalized = canonicalized;
|
||||
}
|
||||
|
||||
public long getMasterKeyId() {
|
||||
@ -89,7 +77,7 @@ public class UncachedKeyRing {
|
||||
final Iterator<PGPPublicKey> it = mRing.getPublicKeys();
|
||||
return new Iterator<UncachedPublicKey>() {
|
||||
public void remove() {
|
||||
it.remove();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public UncachedPublicKey next() {
|
||||
return new UncachedPublicKey(it.next());
|
||||
@ -105,10 +93,6 @@ public class UncachedKeyRing {
|
||||
return mIsSecret;
|
||||
}
|
||||
|
||||
public boolean isCanonicalized() {
|
||||
return mIsCanonicalized;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() throws IOException {
|
||||
return mRing.getEncoded();
|
||||
}
|
||||
@ -119,43 +103,86 @@ public class UncachedKeyRing {
|
||||
|
||||
public static UncachedKeyRing decodeFromData(byte[] data)
|
||||
throws PgpGeneralException, IOException {
|
||||
BufferedInputStream bufferedInput =
|
||||
new BufferedInputStream(new ByteArrayInputStream(data));
|
||||
if (bufferedInput.available() > 0) {
|
||||
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
||||
|
||||
// get first object in block
|
||||
Object obj;
|
||||
if ((obj = objectFactory.nextObject()) != null && obj instanceof PGPKeyRing) {
|
||||
return new UncachedKeyRing((PGPKeyRing) obj);
|
||||
} else {
|
||||
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
} else {
|
||||
Iterator<UncachedKeyRing> parsed = fromStream(new ByteArrayInputStream(data));
|
||||
|
||||
if ( ! parsed.hasNext()) {
|
||||
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
|
||||
UncachedKeyRing ring = parsed.next();
|
||||
|
||||
if (parsed.hasNext()) {
|
||||
throw new PgpGeneralException("Expected single keyring in stream, found at least two");
|
||||
}
|
||||
|
||||
return ring;
|
||||
|
||||
}
|
||||
|
||||
public static List<UncachedKeyRing> fromStream(InputStream stream)
|
||||
throws PgpGeneralException, IOException {
|
||||
public static Iterator<UncachedKeyRing> fromStream(final InputStream stream) throws IOException {
|
||||
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream));
|
||||
return new Iterator<UncachedKeyRing>() {
|
||||
|
||||
List<UncachedKeyRing> result = new Vector<UncachedKeyRing>();
|
||||
UncachedKeyRing mNext = null;
|
||||
PGPObjectFactory mObjectFactory = null;
|
||||
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
private void cacheNext() {
|
||||
if (mNext != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
while(stream.available() > 0) {
|
||||
// if there are no objects left from the last factory, create a new one
|
||||
if (mObjectFactory == null) {
|
||||
mObjectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream));
|
||||
}
|
||||
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = mObjectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
if (!(obj instanceof PGPKeyRing)) {
|
||||
Log.i(Constants.TAG,
|
||||
"Skipping object of bad type " + obj.getClass().getName() + " in stream");
|
||||
// skip object
|
||||
continue;
|
||||
}
|
||||
mNext = new UncachedKeyRing((PGPKeyRing) obj);
|
||||
return;
|
||||
}
|
||||
// if we are past the while loop, that means the objectFactory had no next
|
||||
mObjectFactory = null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException while processing stream", e);
|
||||
}
|
||||
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
result.add(new UncachedKeyRing((PGPKeyRing) obj));
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
cacheNext();
|
||||
return mNext != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UncachedKeyRing next() {
|
||||
try {
|
||||
cacheNext();
|
||||
return mNext;
|
||||
} finally {
|
||||
mNext = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public void encodeArmored(OutputStream out, String version) throws IOException {
|
||||
@ -165,27 +192,6 @@ public class UncachedKeyRing {
|
||||
aos.close();
|
||||
}
|
||||
|
||||
public HashSet<Long> getAvailableSubkeys() {
|
||||
if(!isSecret()) {
|
||||
throw new RuntimeException("Tried to find available subkeys from non-secret keys. " +
|
||||
"This is a programming error and should never happen!");
|
||||
}
|
||||
|
||||
HashSet<Long> result = new HashSet<Long>();
|
||||
// then, mark exactly the keys we have available
|
||||
for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(
|
||||
((PGPSecretKeyRing) mRing).getSecretKeys())) {
|
||||
S2K s2k = sub.getS2K();
|
||||
// add key, except if the private key has been stripped (GNU extension)
|
||||
if(s2k == null || (s2k.getProtectionMode() != S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY)) {
|
||||
result.add(sub.getKeyID());
|
||||
} else {
|
||||
Log.d(Constants.TAG, "S2K GNU extension!, mode: " + s2k.getProtectionMode());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** "Canonicalizes" a public key, removing inconsistencies in the process. This variant can be
|
||||
* applied to public keyrings only.
|
||||
*
|
||||
@ -195,7 +201,7 @@ public class UncachedKeyRing {
|
||||
* - Remove all certificates flagged as "local"
|
||||
* - Remove all certificates which are superseded by a newer one on the same target,
|
||||
* including revocations with later re-certifications.
|
||||
* - Remove all certificates of unknown type:
|
||||
* - Remove all certificates in other positions if not of known type:
|
||||
* - key revocation signatures on the master key
|
||||
* - subkey binding signatures for subkeys
|
||||
* - certifications and certification revocations for user ids
|
||||
@ -210,7 +216,7 @@ public class UncachedKeyRing {
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public UncachedKeyRing canonicalize(OperationLog log, int indent) {
|
||||
public CanonicalizedKeyRing canonicalize(OperationLog log, int indent) {
|
||||
|
||||
log.add(LogLevel.START, isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(getMasterKeyId()));
|
||||
@ -292,14 +298,14 @@ public class UncachedKeyRing {
|
||||
revocation = zert;
|
||||
// more revocations? at least one is superfluous, then.
|
||||
} else if (revocation.getCreationTime().before(zert.getCreationTime())) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, revocation);
|
||||
redundantCerts += 1;
|
||||
log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, indent);
|
||||
revocation = zert;
|
||||
} else {
|
||||
log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
redundantCerts += 1;
|
||||
log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, indent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,20 +329,21 @@ public class UncachedKeyRing {
|
||||
indent, "0x" + Integer.toString(zert.getSignatureType(), 16));
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cert.getCreationTime().after(now)) {
|
||||
// Creation date in the future? No way!
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TIME, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cert.isLocal()) {
|
||||
// Creation date in the future? No way!
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_LOCAL, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
@ -379,35 +386,35 @@ public class UncachedKeyRing {
|
||||
if (selfCert == null) {
|
||||
selfCert = zert;
|
||||
} else if (selfCert.getCreationTime().before(cert.getCreationTime())) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP,
|
||||
indent, userId);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, selfCert);
|
||||
redundantCerts += 1;
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP,
|
||||
indent, userId);
|
||||
selfCert = zert;
|
||||
} else {
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
redundantCerts += 1;
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP,
|
||||
indent, userId);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
redundantCerts += 1;
|
||||
}
|
||||
// If there is a revocation certificate, and it's older than this, drop it
|
||||
if (revocation != null
|
||||
&& revocation.getCreationTime().before(selfCert.getCreationTime())) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD,
|
||||
indent, userId);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, revocation);
|
||||
revocation = null;
|
||||
redundantCerts += 1;
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD,
|
||||
indent, userId);
|
||||
}
|
||||
break;
|
||||
|
||||
case PGPSignature.CERTIFICATION_REVOCATION:
|
||||
// If this is older than the (latest) self cert, drop it
|
||||
if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) {
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
redundantCerts += 1;
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD,
|
||||
indent, userId);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
redundantCerts += 1;
|
||||
continue;
|
||||
}
|
||||
// first revocation? remember it.
|
||||
@ -415,16 +422,16 @@ public class UncachedKeyRing {
|
||||
revocation = zert;
|
||||
// more revocations? at least one is superfluous, then.
|
||||
} else if (revocation.getCreationTime().before(cert.getCreationTime())) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP,
|
||||
indent, userId);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, revocation);
|
||||
redundantCerts += 1;
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP,
|
||||
indent, userId);
|
||||
revocation = zert;
|
||||
} else {
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
redundantCerts += 1;
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP,
|
||||
indent, userId);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId, zert);
|
||||
redundantCerts += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -434,9 +441,9 @@ public class UncachedKeyRing {
|
||||
|
||||
// If no valid certificate (if only a revocation) remains, drop it
|
||||
if (selfCert == null && revocation == null) {
|
||||
modified = PGPPublicKey.removeCertification(modified, userId);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_KC_UID_REMOVE,
|
||||
indent, userId);
|
||||
modified = PGPPublicKey.removeCertification(modified, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -515,14 +522,17 @@ public class UncachedKeyRing {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (zert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) {
|
||||
// if this certificate says it allows signing for the key
|
||||
if (zert.getHashedSubPackets() != null &&
|
||||
zert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) {
|
||||
|
||||
int flags = ((KeyFlags) zert.getHashedSubPackets()
|
||||
.getSubpacket(SignatureSubpacketTags.KEY_FLAGS)).getFlags();
|
||||
// If this subkey is allowed to sign data,
|
||||
if ((flags & PGPKeyFlags.CAN_SIGN) == PGPKeyFlags.CAN_SIGN) {
|
||||
boolean ok = false;
|
||||
// it MUST have an embedded primary key binding signature
|
||||
try {
|
||||
PGPSignatureList list = zert.getUnhashedSubPackets().getEmbeddedSignatures();
|
||||
boolean ok = false;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
WrappedSignature subsig = new WrappedSignature(list.get(i));
|
||||
if (subsig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
|
||||
@ -536,17 +546,19 @@ public class UncachedKeyRing {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
// if it doesn't, get rid of this!
|
||||
if (!ok) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if we already have a cert, and this one is not newer: skip it
|
||||
@ -559,6 +571,8 @@ public class UncachedKeyRing {
|
||||
selfCert = zert;
|
||||
// if this is newer than a possibly existing revocation, drop that one
|
||||
if (revocation != null && selfCert.getCreationTime().after(revocation.getCreationTime())) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_REVOKE_DUP, indent);
|
||||
redundantCerts += 1;
|
||||
revocation = null;
|
||||
}
|
||||
|
||||
@ -592,7 +606,7 @@ public class UncachedKeyRing {
|
||||
|
||||
// it is not properly bound? error!
|
||||
if (selfCert == null) {
|
||||
ring = replacePublicKey(ring, modified);
|
||||
ring = removeSubKey(ring, key);
|
||||
|
||||
log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(key.getKeyID()));
|
||||
@ -625,7 +639,8 @@ public class UncachedKeyRing {
|
||||
log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS, indent);
|
||||
}
|
||||
|
||||
return new UncachedKeyRing(ring, true);
|
||||
return isSecret() ? new CanonicalizedSecretKeyRing((PGPSecretKeyRing) ring, 1)
|
||||
: new CanonicalizedPublicKeyRing((PGPPublicKeyRing) ring, 0);
|
||||
}
|
||||
|
||||
/** This operation merges information from a different keyring, returning a combined
|
||||
@ -660,7 +675,7 @@ public class UncachedKeyRing {
|
||||
return left.length - right.length;
|
||||
}
|
||||
// compare byte-by-byte
|
||||
for (int i = 0; i < left.length && i < right.length; i++) {
|
||||
for (int i = 0; i < left.length; i++) {
|
||||
if (left[i] != right[i]) {
|
||||
return (left[i] & 0xff) - (right[i] & 0xff);
|
||||
}
|
||||
@ -688,7 +703,14 @@ public class UncachedKeyRing {
|
||||
final PGPPublicKey resultKey = result.getPublicKey(key.getKeyID());
|
||||
if (resultKey == null) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MG_NEW_SUBKEY, indent);
|
||||
result = replacePublicKey(result, key);
|
||||
// special case: if both rings are secret, copy over the secret key
|
||||
if (isSecret() && other.isSecret()) {
|
||||
PGPSecretKey sKey = ((PGPSecretKeyRing) candidate).getSecretKey(key.getKeyID());
|
||||
result = PGPSecretKeyRing.insertSecretKey((PGPSecretKeyRing) result, sKey);
|
||||
} else {
|
||||
// otherwise, just insert the public key
|
||||
result = replacePublicKey(result, key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -696,17 +718,7 @@ public class UncachedKeyRing {
|
||||
PGPPublicKey modified = resultKey;
|
||||
|
||||
// Iterate certifications
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
int type = cert.getSignatureType();
|
||||
// Disregard certifications on user ids, we will deal with those later
|
||||
if (type == PGPSignature.NO_CERTIFICATION
|
||||
|| type == PGPSignature.DEFAULT_CERTIFICATION
|
||||
|| type == PGPSignature.CASUAL_CERTIFICATION
|
||||
|| type == PGPSignature.POSITIVE_CERTIFICATION
|
||||
|| type == PGPSignature.CERTIFICATION_REVOCATION) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getKeySignatures())) {
|
||||
// Don't merge foreign stuff into secret keys
|
||||
if (cert.getKeyID() != masterKeyId && isSecret()) {
|
||||
continue;
|
||||
@ -770,19 +782,20 @@ public class UncachedKeyRing {
|
||||
|
||||
}
|
||||
|
||||
public UncachedKeyRing extractPublicKeyRing() {
|
||||
public UncachedKeyRing extractPublicKeyRing() throws IOException {
|
||||
if(!isSecret()) {
|
||||
throw new RuntimeException("Tried to extract public keyring from non-secret keyring. " +
|
||||
"This is a programming error and should never happen!");
|
||||
}
|
||||
|
||||
ArrayList<PGPPublicKey> keys = new ArrayList();
|
||||
Iterator<PGPPublicKey> it = mRing.getPublicKeys();
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream(2048);
|
||||
while (it.hasNext()) {
|
||||
keys.add(it.next());
|
||||
stream.write(it.next().getEncoded());
|
||||
}
|
||||
|
||||
return new UncachedKeyRing(new PGPPublicKeyRing(keys));
|
||||
return new UncachedKeyRing(
|
||||
new PGPPublicKeyRing(stream.toByteArray(), new JcaKeyFingerprintCalculator()));
|
||||
}
|
||||
|
||||
/** This method replaces a public key in a keyring.
|
||||
@ -806,4 +819,20 @@ public class UncachedKeyRing {
|
||||
return PGPSecretKeyRing.insertSecretKey(secRing, sKey);
|
||||
}
|
||||
|
||||
/** This method removes a subkey in a keyring.
|
||||
*
|
||||
* This method essentially wraps PGP*KeyRing.remove*Key, where the keyring may be of either
|
||||
* the secret or public subclass.
|
||||
*
|
||||
* @return the resulting PGPKeyRing of the same type as the input
|
||||
*/
|
||||
private static PGPKeyRing removeSubKey(PGPKeyRing ring, PGPPublicKey key) {
|
||||
if (ring instanceof PGPPublicKeyRing) {
|
||||
return PGPPublicKeyRing.removePublicKey((PGPPublicKeyRing) ring, key);
|
||||
} else {
|
||||
PGPSecretKey sKey = ((PGPSecretKeyRing) ring).getSecretKey(key.getKeyID());
|
||||
return PGPSecretKeyRing.removeSecretKey((PGPSecretKeyRing) ring, sKey);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -63,13 +63,17 @@ public class WrappedSignature {
|
||||
}
|
||||
try {
|
||||
PGPSignatureList list;
|
||||
list = mSig.getHashedSubPackets().getEmbeddedSignatures();
|
||||
for(int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
if (mSig.getHashedSubPackets() != null) {
|
||||
list = mSig.getHashedSubPackets().getEmbeddedSignatures();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
}
|
||||
}
|
||||
list = mSig.getUnhashedSubPackets().getEmbeddedSignatures();
|
||||
for(int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
if (mSig.getUnhashedSubPackets() != null) {
|
||||
list = mSig.getUnhashedSubPackets().getEmbeddedSignatures();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
}
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
// no matter
|
||||
@ -97,6 +101,9 @@ public class WrappedSignature {
|
||||
if(!isRevocation()) {
|
||||
throw new PgpGeneralException("Not a revocation signature.");
|
||||
}
|
||||
if (mSig.getHashedSubPackets() == null) {
|
||||
return null;
|
||||
}
|
||||
SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(
|
||||
SignatureSubpacketTags.REVOCATION_REASON);
|
||||
// For some reason, this is missing in SignatureSubpacketInputStream:146
|
||||
@ -106,7 +113,7 @@ public class WrappedSignature {
|
||||
return ((RevocationReason) p).getRevocationDescription();
|
||||
}
|
||||
|
||||
public void init(WrappedPublicKey key) throws PgpGeneralException {
|
||||
public void init(CanonicalizedPublicKey key) throws PgpGeneralException {
|
||||
init(key.getPublicKey());
|
||||
}
|
||||
|
||||
@ -184,7 +191,7 @@ public class WrappedSignature {
|
||||
public boolean verifySignature(UncachedPublicKey key, String uid) throws PgpGeneralException {
|
||||
return verifySignature(key.getPublicKey(), uid);
|
||||
}
|
||||
public boolean verifySignature(WrappedPublicKey key, String uid) throws PgpGeneralException {
|
||||
public boolean verifySignature(CanonicalizedPublicKey key, String uid) throws PgpGeneralException {
|
||||
return verifySignature(key.getPublicKey(), uid);
|
||||
}
|
||||
|
||||
@ -205,7 +212,7 @@ public class WrappedSignature {
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
if (!mSig.hasSubpackets()
|
||||
if (mSig.getHashedSubPackets() == null
|
||||
|| !mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.EXPORTABLE)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
@ -33,6 +34,7 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMasterKeyId() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
@ -59,10 +61,21 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
return getMasterKeyId();
|
||||
}
|
||||
|
||||
public byte[] getFingerprint() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
return (byte[]) data;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
throw new PgpGeneralException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrimaryUserId() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.USER_ID,
|
||||
ProviderHelper.FIELD_TYPE_STRING);
|
||||
return (String) data;
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
@ -74,10 +87,11 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
return getPrimaryUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRevoked() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.IS_REVOKED,
|
||||
ProviderHelper.FIELD_TYPE_INTEGER);
|
||||
return (Long) data > 0;
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
@ -85,10 +99,11 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCertify() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.CAN_CERTIFY,
|
||||
ProviderHelper.FIELD_TYPE_INTEGER);
|
||||
return (Long) data > 0;
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
@ -96,21 +111,32 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEncryptId() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
ProviderHelper.FIELD_TYPE_INTEGER);
|
||||
return (Long) data;
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
Cursor subkeys = getSubkeys();
|
||||
if (subkeys != null) {
|
||||
try {
|
||||
while (subkeys.moveToNext()) {
|
||||
if (subkeys.getInt(subkeys.getColumnIndexOrThrow(KeychainContract.Keys.CAN_ENCRYPT)) != 0) {
|
||||
return subkeys.getLong(subkeys.getColumnIndexOrThrow(KeychainContract.Keys.KEY_ID));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
subkeys.close();
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
throw new PgpGeneralException(e);
|
||||
}
|
||||
throw new PgpGeneralException("No encrypt key found");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEncrypt() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.HAS_ENCRYPT,
|
||||
ProviderHelper.FIELD_TYPE_INTEGER);
|
||||
return (Long) data > 0;
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
@ -118,21 +144,32 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSignId() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
ProviderHelper.FIELD_TYPE_INTEGER);
|
||||
return (Long) data;
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
Cursor subkeys = getSubkeys();
|
||||
if (subkeys != null) {
|
||||
try {
|
||||
while (subkeys.moveToNext()) {
|
||||
if (subkeys.getInt(subkeys.getColumnIndexOrThrow(KeychainContract.Keys.CAN_SIGN)) != 0) {
|
||||
return subkeys.getLong(subkeys.getColumnIndexOrThrow(KeychainContract.Keys.KEY_ID));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
subkeys.close();
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
throw new PgpGeneralException(e);
|
||||
}
|
||||
throw new PgpGeneralException("No sign key found");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSign() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.HAS_SIGN,
|
||||
ProviderHelper.FIELD_TYPE_INTEGER);
|
||||
return (Long) data > 0;
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
@ -140,10 +177,11 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVerified() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.VERIFIED,
|
||||
ProviderHelper.FIELD_TYPE_INTEGER);
|
||||
return (Integer) data;
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
@ -154,12 +192,16 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
public boolean hasAnySecret() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.HAS_ANY_SECRET,
|
||||
ProviderHelper.FIELD_TYPE_INTEGER);
|
||||
return (Long) data > 0;
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
throw new PgpGeneralException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Cursor getSubkeys() throws PgpGeneralException {
|
||||
Uri keysUri = KeychainContract.Keys.buildKeysUri(Long.toString(extractOrGetMasterKeyId()));
|
||||
return mProviderHelper.getContentResolver().query(keysUri, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,12 @@ import android.os.RemoteException;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.NullProgressable;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
@ -39,8 +41,6 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
@ -180,7 +180,7 @@ public class ProviderHelper {
|
||||
return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types);
|
||||
}
|
||||
|
||||
private LongSparseArray<WrappedPublicKey> getTrustedMasterKeys() {
|
||||
private LongSparseArray<CanonicalizedPublicKey> getTrustedMasterKeys() {
|
||||
Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[] {
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
// we pick from cache only information that is not easily available from keyrings
|
||||
@ -190,16 +190,15 @@ public class ProviderHelper {
|
||||
}, KeyRings.HAS_ANY_SECRET + " = 1", null, null);
|
||||
|
||||
try {
|
||||
LongSparseArray<WrappedPublicKey> result = new LongSparseArray<WrappedPublicKey>();
|
||||
LongSparseArray<CanonicalizedPublicKey> result = new LongSparseArray<CanonicalizedPublicKey>();
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) do {
|
||||
long masterKeyId = cursor.getLong(0);
|
||||
boolean hasAnySecret = cursor.getInt(1) > 0;
|
||||
int verified = cursor.getInt(2);
|
||||
byte[] blob = cursor.getBlob(3);
|
||||
if (blob != null) {
|
||||
result.put(masterKeyId,
|
||||
new WrappedPublicKeyRing(blob, hasAnySecret, verified).getPublicKey());
|
||||
new CanonicalizedPublicKeyRing(blob, verified).getPublicKey());
|
||||
}
|
||||
} while (cursor.moveToNext());
|
||||
|
||||
@ -217,23 +216,23 @@ public class ProviderHelper {
|
||||
return new CachedPublicKeyRing(this, queryUri);
|
||||
}
|
||||
|
||||
public WrappedPublicKeyRing getWrappedPublicKeyRing(long id) throws NotFoundException {
|
||||
return (WrappedPublicKeyRing) getWrappedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), false);
|
||||
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long id) throws NotFoundException {
|
||||
return (CanonicalizedPublicKeyRing) getCanonicalizedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), false);
|
||||
}
|
||||
|
||||
public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri queryUri) throws NotFoundException {
|
||||
return (WrappedPublicKeyRing) getWrappedKeyRing(queryUri, false);
|
||||
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri queryUri) throws NotFoundException {
|
||||
return (CanonicalizedPublicKeyRing) getCanonicalizedKeyRing(queryUri, false);
|
||||
}
|
||||
|
||||
public WrappedSecretKeyRing getWrappedSecretKeyRing(long id) throws NotFoundException {
|
||||
return (WrappedSecretKeyRing) getWrappedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), true);
|
||||
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long id) throws NotFoundException {
|
||||
return (CanonicalizedSecretKeyRing) getCanonicalizedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), true);
|
||||
}
|
||||
|
||||
public WrappedSecretKeyRing getWrappedSecretKeyRing(Uri queryUri) throws NotFoundException {
|
||||
return (WrappedSecretKeyRing) getWrappedKeyRing(queryUri, true);
|
||||
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri queryUri) throws NotFoundException {
|
||||
return (CanonicalizedSecretKeyRing) getCanonicalizedKeyRing(queryUri, true);
|
||||
}
|
||||
|
||||
private KeyRing getWrappedKeyRing(Uri queryUri, boolean secret) throws NotFoundException {
|
||||
private KeyRing getCanonicalizedKeyRing(Uri queryUri, boolean secret) throws NotFoundException {
|
||||
Cursor cursor = mContentResolver.query(queryUri,
|
||||
new String[]{
|
||||
// we pick from cache only information that is not easily available from keyrings
|
||||
@ -252,8 +251,8 @@ public class ProviderHelper {
|
||||
throw new NotFoundException("Secret key not available!");
|
||||
}
|
||||
return secret
|
||||
? new WrappedSecretKeyRing(blob, true, verified)
|
||||
: new WrappedPublicKeyRing(blob, hasAnySecret, verified);
|
||||
? new CanonicalizedSecretKeyRing(blob, true, verified)
|
||||
: new CanonicalizedPublicKeyRing(blob, verified);
|
||||
} else {
|
||||
throw new NotFoundException("Key not found!");
|
||||
}
|
||||
@ -271,16 +270,8 @@ public class ProviderHelper {
|
||||
* and need to be saved externally to be preserved past the operation.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private int internalSavePublicKeyRing(UncachedKeyRing keyRing,
|
||||
Progressable progress, boolean selfCertsAreTrusted) {
|
||||
if (keyRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
if (!keyRing.isCanonicalized()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
private int saveCanonicalizedPublicKeyRing(CanonicalizedPublicKeyRing keyRing,
|
||||
Progressable progress, boolean selfCertsAreTrusted) {
|
||||
|
||||
// start with ok result
|
||||
int result = SaveKeyringResult.SAVED_PUBLIC;
|
||||
@ -318,7 +309,7 @@ public class ProviderHelper {
|
||||
{ // insert subkeys
|
||||
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
|
||||
int rank = 0;
|
||||
for (UncachedPublicKey key : new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) {
|
||||
for (CanonicalizedPublicKey key : keyRing.publicKeyIterator()) {
|
||||
long keyId = key.getKeyId();
|
||||
log(LogLevel.DEBUG, keyId == masterKeyId ? LogType.MSG_IP_MASTER : LogType.MSG_IP_SUBKEY,
|
||||
PgpKeyHelper.convertKeyIdToHex(keyId)
|
||||
@ -401,7 +392,7 @@ public class ProviderHelper {
|
||||
mIndent -= 1;
|
||||
|
||||
// get a list of owned secret keys, for verification filtering
|
||||
LongSparseArray<WrappedPublicKey> trustedKeys = getTrustedMasterKeys();
|
||||
LongSparseArray<CanonicalizedPublicKey> trustedKeys = getTrustedMasterKeys();
|
||||
|
||||
// classify and order user ids. primary are moved to the front, revoked to the back,
|
||||
// otherwise the order in the keyfile is preserved.
|
||||
@ -445,7 +436,7 @@ public class ProviderHelper {
|
||||
|
||||
// verify signatures from known private keys
|
||||
if (trustedKeys.indexOfKey(certId) >= 0) {
|
||||
WrappedPublicKey trustedKey = trustedKeys.get(certId);
|
||||
CanonicalizedPublicKey trustedKey = trustedKeys.get(certId);
|
||||
cert.init(trustedKey);
|
||||
if (cert.verifySignature(masterKey, userId)) {
|
||||
item.trustedCerts.add(cert);
|
||||
@ -559,28 +550,13 @@ public class ProviderHelper {
|
||||
/** Saves an UncachedKeyRing of the secret variant into the db.
|
||||
* This method will fail if no corresponding public keyring is in the database!
|
||||
*/
|
||||
private int internalSaveSecretKeyRing(UncachedKeyRing keyRing) {
|
||||
|
||||
if (!keyRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
|
||||
if (!keyRing.isCanonicalized()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
private int saveCanonicalizedSecretKeyRing(CanonicalizedSecretKeyRing keyRing) {
|
||||
|
||||
long masterKeyId = keyRing.getMasterKeyId();
|
||||
log(LogLevel.START, LogType.MSG_IS, PgpKeyHelper.convertKeyIdToHex(masterKeyId));
|
||||
mIndent += 1;
|
||||
try {
|
||||
|
||||
// Canonicalize this key, to assert a number of assumptions made about it.
|
||||
keyRing = keyRing.canonicalize(mLog, mIndent);
|
||||
if (keyRing == null) {
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
try {
|
||||
|
||||
// IF this is successful, it's a secret key
|
||||
int result = SaveKeyringResult.SAVED_SECRET;
|
||||
@ -615,8 +591,7 @@ public class ProviderHelper {
|
||||
log(LogLevel.INFO, LogType.MSG_IS_IMPORTING_SUBKEYS);
|
||||
mIndent += 1;
|
||||
Set<Long> available = keyRing.getAvailableSubkeys();
|
||||
for (UncachedPublicKey sub :
|
||||
new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) {
|
||||
for (UncachedPublicKey sub : keyRing.publicKeyIterator()) {
|
||||
long id = sub.getKeyId();
|
||||
if (available.contains(id)) {
|
||||
int upd = mContentResolver.update(uri, values, Keys.KEY_ID + " = ?",
|
||||
@ -667,9 +642,16 @@ public class ProviderHelper {
|
||||
log(LogLevel.START, LogType.MSG_IP, PgpKeyHelper.convertKeyIdToHex(masterKeyId));
|
||||
mIndent += 1;
|
||||
|
||||
if (publicRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
CanonicalizedPublicKeyRing canPublicRing;
|
||||
|
||||
// If there is an old keyring, merge it
|
||||
try {
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into the old one
|
||||
publicRing = oldPublicRing.merge(publicRing, mLog, mIndent);
|
||||
@ -680,8 +662,8 @@ public class ProviderHelper {
|
||||
}
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
publicRing = publicRing.canonicalize(mLog, mIndent);
|
||||
if (publicRing == null) {
|
||||
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
|
||||
if (canPublicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
@ -695,39 +677,40 @@ public class ProviderHelper {
|
||||
// Not an issue, just means we are dealing with a new keyring.
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
publicRing = publicRing.canonicalize(mLog, mIndent);
|
||||
if (publicRing == null) {
|
||||
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
|
||||
if (canPublicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If there is a secret key, merge new data (if any) and save the key for later
|
||||
UncachedKeyRing secretRing;
|
||||
CanonicalizedSecretKeyRing canSecretRing;
|
||||
try {
|
||||
secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing();
|
||||
UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into secret one
|
||||
secretRing = secretRing.merge(publicRing, mLog, mIndent);
|
||||
if (secretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
secretRing = secretRing.canonicalize(mLog, mIndent);
|
||||
if (secretRing == null) {
|
||||
// This has always been a secret key ring, this is a safe cast
|
||||
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
|
||||
if (canSecretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
// No secret key available (this is what happens most of the time)
|
||||
secretRing = null;
|
||||
canSecretRing = null;
|
||||
}
|
||||
|
||||
int result = internalSavePublicKeyRing(publicRing, progress, secretRing != null);
|
||||
int result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null);
|
||||
|
||||
// Save the saved keyring (if any)
|
||||
if (secretRing != null) {
|
||||
if (canSecretRing != null) {
|
||||
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
|
||||
int secretResult = internalSaveSecretKeyRing(secretRing);
|
||||
int secretResult = saveCanonicalizedSecretKeyRing(canSecretRing);
|
||||
if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) {
|
||||
result |= SaveKeyringResult.SAVED_SECRET;
|
||||
}
|
||||
@ -751,9 +734,16 @@ public class ProviderHelper {
|
||||
log(LogLevel.START, LogType.MSG_IS, PgpKeyHelper.convertKeyIdToHex(masterKeyId));
|
||||
mIndent += 1;
|
||||
|
||||
if ( ! secretRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
CanonicalizedSecretKeyRing canSecretRing;
|
||||
|
||||
// If there is an old secret key, merge it.
|
||||
try {
|
||||
UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
UncachedKeyRing oldSecretRing = getCanonicalizedSecretKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new secret ring into old one
|
||||
secretRing = secretRing.merge(oldSecretRing, mLog, mIndent);
|
||||
@ -764,8 +754,9 @@ public class ProviderHelper {
|
||||
}
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
secretRing = secretRing.canonicalize(mLog, mIndent);
|
||||
if (secretRing == null) {
|
||||
// This is a safe cast, because we made sure this is a secret ring above
|
||||
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
|
||||
if (canSecretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
@ -780,8 +771,9 @@ public class ProviderHelper {
|
||||
// Not an issue, just means we are dealing with a new keyring
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
secretRing = secretRing.canonicalize(mLog, mIndent);
|
||||
if (secretRing == null) {
|
||||
// This is a safe cast, because we made sure this is a secret ring above
|
||||
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
|
||||
if (canSecretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
@ -790,7 +782,7 @@ public class ProviderHelper {
|
||||
// Merge new data into public keyring as well, if there is any
|
||||
UncachedKeyRing publicRing;
|
||||
try {
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new secret ring into public one
|
||||
publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);
|
||||
@ -798,31 +790,26 @@ public class ProviderHelper {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
// If nothing changed, never mind
|
||||
if (Arrays.hashCode(publicRing.getEncoded())
|
||||
== Arrays.hashCode(oldPublicRing.getEncoded())) {
|
||||
publicRing = null;
|
||||
}
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log(LogLevel.DEBUG, LogType.MSG_IS_PUBRING_GENERATE);
|
||||
publicRing = secretRing.extractPublicKeyRing();
|
||||
}
|
||||
|
||||
if (publicRing != null) {
|
||||
publicRing = publicRing.canonicalize(mLog, mIndent);
|
||||
if (publicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
|
||||
if (canPublicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
int result = internalSavePublicKeyRing(publicRing, progress, true);
|
||||
if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
int result;
|
||||
|
||||
result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true);
|
||||
if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
|
||||
int result = internalSaveSecretKeyRing(secretRing);
|
||||
result = saveCanonicalizedSecretKeyRing(canSecretRing);
|
||||
|
||||
return new SaveKeyringResult(result, mLog);
|
||||
|
||||
} catch (IOException e) {
|
||||
@ -1037,4 +1024,8 @@ public class ProviderHelper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ContentResolver getContentResolver() {
|
||||
return mContentResolver;
|
||||
}
|
||||
}
|
||||
|
@ -457,7 +457,7 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
try {
|
||||
// try to find key, throws NotFoundException if not in db!
|
||||
mProviderHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
|
@ -32,10 +32,11 @@ import android.widget.Button;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.ui.EditKeyActivityOld;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
|
||||
import org.sufficientlysecure.keychain.util.AlgorithmNames;
|
||||
@ -163,11 +164,11 @@ public class AccountSettingsFragment extends Fragment implements
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(getActivity(), EditKeyActivityOld.class);
|
||||
intent.setAction(EditKeyActivityOld.ACTION_CREATE_KEY);
|
||||
intent.putExtra(EditKeyActivityOld.EXTRA_GENERATE_DEFAULT_KEYS, true);
|
||||
// set default user id to account name
|
||||
intent.putExtra(EditKeyActivityOld.EXTRA_USER_IDS, mAccSettings.getAccountName());
|
||||
String[] userId = KeyRing.splitUserId(mAccSettings.getAccountName());
|
||||
|
||||
Intent intent = new Intent(getActivity(), CreateKeyActivity.class);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_NAME, userId[0]);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_EMAIL, userId[1]);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_KEY);
|
||||
}
|
||||
|
||||
|
@ -31,11 +31,15 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.keyimport.FileImportCache;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
@ -44,15 +48,13 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
@ -169,7 +171,7 @@ public class KeychainIntentService extends IntentService
|
||||
// export
|
||||
public static final String RESULT_EXPORT = "exported";
|
||||
|
||||
public static final String RESULT = "result";
|
||||
public static final String RESULT_IMPORT = "result";
|
||||
|
||||
Messenger mMessenger;
|
||||
|
||||
@ -330,39 +332,37 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
/* Operation */
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 50, 100));
|
||||
try {
|
||||
OperationLog log = new OperationLog();
|
||||
UncachedKeyRing ring;
|
||||
if (saveParcel.mMasterKeyId != null) {
|
||||
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
|
||||
WrappedSecretKeyRing secRing =
|
||||
providerHelper.getWrappedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 60, 100));
|
||||
EditKeyResult result;
|
||||
|
||||
ring = keyOperations.modifySecretKeyRing(secRing, saveParcel,
|
||||
passphrase, log, 0);
|
||||
} else {
|
||||
ring = keyOperations.createSecretKeyRing(saveParcel, log, 0);
|
||||
}
|
||||
if (saveParcel.mMasterKeyId != null) {
|
||||
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
|
||||
CanonicalizedSecretKeyRing secRing =
|
||||
providerHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||
|
||||
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 10, 95, 100));
|
||||
result = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase);
|
||||
} else {
|
||||
result = keyOperations.createSecretKeyRing(saveParcel);
|
||||
}
|
||||
|
||||
// cache new passphrase
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(),
|
||||
saveParcel.mNewPassphrase, ring.getPublicKey().getPrimaryUserIdWithFallback());
|
||||
}
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
sendErrorToHandler(e);
|
||||
UncachedKeyRing ring = result.getRing();
|
||||
|
||||
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100));
|
||||
|
||||
// cache new passphrase
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(),
|
||||
saveParcel.mNewPassphrase, ring.getPublicKey().getPrimaryUserIdWithFallback());
|
||||
}
|
||||
|
||||
setProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
/* Output */
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
} else if (ACTION_DELETE_FILE_SECURELY.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
@ -386,13 +386,21 @@ public class KeychainIntentService extends IntentService
|
||||
}
|
||||
} else if (ACTION_IMPORT_KEYRING.equals(action)) {
|
||||
try {
|
||||
List<ParcelableKeyRing> entries = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
||||
List<ParcelableKeyRing> entries;
|
||||
if (data.containsKey(IMPORT_KEY_LIST)) {
|
||||
// get entries from intent
|
||||
entries = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
||||
} else {
|
||||
// get entries from cached file
|
||||
FileImportCache cache = new FileImportCache(this);
|
||||
entries = cache.readCache();
|
||||
}
|
||||
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, this);
|
||||
OperationResults.ImportResult result = pgpImportExport.importKeyRings(entries);
|
||||
ImportKeyResult result = pgpImportExport.importKeyRings(entries);
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putParcelable(RESULT, result);
|
||||
resultData.putParcelable(RESULT_IMPORT, result);
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
@ -466,7 +474,7 @@ public class KeychainIntentService extends IntentService
|
||||
HkpKeyserver server = new HkpKeyserver(keyServer);
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
WrappedPublicKeyRing keyring = providerHelper.getWrappedPublicKeyRing(dataUri);
|
||||
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
||||
|
||||
boolean uploaded = pgpImportExport.uploadKeyRingToServer(server, keyring);
|
||||
@ -515,6 +523,7 @@ public class KeychainIntentService extends IntentService
|
||||
Intent importIntent = new Intent(this, KeychainIntentService.class);
|
||||
importIntent.setAction(ACTION_IMPORT_KEYRING);
|
||||
Bundle importData = new Bundle();
|
||||
// This is not going through binder, nothing to fear of
|
||||
importData.putParcelableArrayList(IMPORT_KEY_LIST, keyRings);
|
||||
importIntent.putExtra(EXTRA_DATA, importData);
|
||||
importIntent.putExtra(EXTRA_MESSENGER, mMessenger);
|
||||
@ -542,9 +551,9 @@ public class KeychainIntentService extends IntentService
|
||||
}
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
WrappedPublicKeyRing publicRing = providerHelper.getWrappedPublicKeyRing(pubKeyId);
|
||||
WrappedSecretKeyRing secretKeyRing = providerHelper.getWrappedSecretKeyRing(masterKeyId);
|
||||
WrappedSecretKey certificationKey = secretKeyRing.getSecretKey();
|
||||
CanonicalizedPublicKeyRing publicRing = providerHelper.getCanonicalizedPublicKeyRing(pubKeyId);
|
||||
CanonicalizedSecretKeyRing secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing(masterKeyId);
|
||||
CanonicalizedSecretKey certificationKey = secretKeyRing.getSecretKey();
|
||||
if(!certificationKey.unlock(signaturePassphrase)) {
|
||||
throw new PgpGeneralException("Error extracting key (bad passphrase?)");
|
||||
}
|
||||
@ -622,6 +631,12 @@ public class KeychainIntentService extends IntentService
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessageToHandler(Integer arg1, OperationResultParcel data) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(OperationResultParcel.EXTRA_RESULT, data);
|
||||
sendMessageToHandler(arg1, null, bundle);
|
||||
}
|
||||
|
||||
private void sendMessageToHandler(Integer arg1, Bundle data) {
|
||||
sendMessageToHandler(arg1, null, data);
|
||||
}
|
||||
|
@ -25,12 +25,11 @@ import android.os.Message;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
public class KeychainIntentServiceHandler extends Handler {
|
||||
|
||||
@ -102,9 +101,9 @@ public class KeychainIntentServiceHandler extends Handler {
|
||||
|
||||
// show error from service
|
||||
if (data.containsKey(DATA_ERROR)) {
|
||||
AppMsg.makeText(mActivity,
|
||||
Notify.showNotify(mActivity,
|
||||
mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.Style.ERROR);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -1,10 +1,20 @@
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.johnpersano.supertoasts.SuperCardToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast;
|
||||
import com.github.johnpersano.supertoasts.util.OnClickWrapper;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@ -24,6 +34,9 @@ import java.util.List;
|
||||
*
|
||||
*/
|
||||
public class OperationResultParcel implements Parcelable {
|
||||
|
||||
public static final String EXTRA_RESULT = "operation_result";
|
||||
|
||||
/** Holds the overall result, the number specifying varying degrees of success. The first bit
|
||||
* is 0 on overall success, 1 on overall failure. All other bits may be used for more specific
|
||||
* conditions. */
|
||||
@ -70,6 +83,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
mType = type;
|
||||
mParameters = parameters;
|
||||
mIndent = indent;
|
||||
Log.v(Constants.TAG, "log: " + this.toString());
|
||||
}
|
||||
|
||||
public LogEntryParcel(Parcel source) {
|
||||
@ -113,6 +127,68 @@ public class OperationResultParcel implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
|
||||
int resultType = getResult();
|
||||
|
||||
String str;
|
||||
int duration, color;
|
||||
|
||||
// Not an overall failure
|
||||
if ((resultType & OperationResultParcel.RESULT_ERROR) == 0) {
|
||||
|
||||
if (getLog().containsWarnings()) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
} else {
|
||||
duration = SuperToast.Duration.LONG;
|
||||
color = Style.GREEN;
|
||||
}
|
||||
|
||||
str = "operation succeeded!";
|
||||
// str = activity.getString(R.string.import_error);
|
||||
|
||||
} else {
|
||||
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
|
||||
str = "operation failed";
|
||||
// str = activity.getString(R.string.import_error);
|
||||
|
||||
}
|
||||
|
||||
boolean button = getLog() != null && !getLog().isEmpty();
|
||||
SuperCardToast toast = new SuperCardToast(activity,
|
||||
button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD,
|
||||
Style.getStyle(color, SuperToast.Animations.POPUP));
|
||||
toast.setText(str);
|
||||
toast.setDuration(duration);
|
||||
toast.setIndeterminate(duration == 0);
|
||||
toast.setSwipeToDismiss(true);
|
||||
// If we have a log and it's non-empty, show a View Log button
|
||||
if (button) {
|
||||
toast.setButtonIcon(R.drawable.ic_action_view_as_list,
|
||||
activity.getResources().getString(R.string.view_log));
|
||||
toast.setButtonTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setOnClickWrapper(new OnClickWrapper("supercardtoast",
|
||||
new SuperToast.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view, Parcelable token) {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResultParcel.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
return toast;
|
||||
|
||||
}
|
||||
|
||||
/** This is an enum of all possible log events.
|
||||
*
|
||||
* Element names should generally be prefixed with MSG_XX_ where XX is an
|
||||
@ -132,6 +208,8 @@ public class OperationResultParcel implements Parcelable {
|
||||
*/
|
||||
public static enum LogType {
|
||||
|
||||
INTERNAL_ERROR (R.string.internal_error),
|
||||
|
||||
// import public
|
||||
MSG_IP(R.string.msg_ip),
|
||||
MSG_IP_APPLY_BATCH (R.string.msg_ip_apply_batch),
|
||||
@ -279,6 +357,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MF_UID_ADD (R.string.msg_mf_uid_add),
|
||||
MSG_MF_UID_PRIMARY (R.string.msg_mf_uid_primary),
|
||||
MSG_MF_UID_REVOKE (R.string.msg_mf_uid_revoke),
|
||||
MSG_MF_UID_ERROR_EMPTY (R.string.msg_mf_uid_error_empty),
|
||||
MSG_MF_UNLOCK_ERROR (R.string.msg_mf_unlock_error),
|
||||
MSG_MF_UNLOCK (R.string.msg_mf_unlock),
|
||||
;
|
||||
@ -329,12 +408,10 @@ public class OperationResultParcel implements Parcelable {
|
||||
|
||||
/// Simple convenience method
|
||||
public void add(LogLevel level, LogType type, int indent, Object... parameters) {
|
||||
Log.d(Constants.TAG, type.toString());
|
||||
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, parameters));
|
||||
}
|
||||
|
||||
public void add(LogLevel level, LogType type, int indent) {
|
||||
Log.d(Constants.TAG, type.toString());
|
||||
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null));
|
||||
}
|
||||
|
||||
|
@ -12,12 +12,13 @@ import com.github.johnpersano.supertoasts.util.OnClickWrapper;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
|
||||
public abstract class OperationResults {
|
||||
|
||||
public static class ImportResult extends OperationResultParcel {
|
||||
public static class ImportKeyResult extends OperationResultParcel {
|
||||
|
||||
public final int mNewKeys, mUpdatedKeys, mBadKeys;
|
||||
|
||||
@ -47,15 +48,15 @@ public abstract class OperationResults {
|
||||
return (mResult & RESULT_FAIL_NOTHING) == RESULT_FAIL_NOTHING;
|
||||
}
|
||||
|
||||
public ImportResult(Parcel source) {
|
||||
public ImportKeyResult(Parcel source) {
|
||||
super(source);
|
||||
mNewKeys = source.readInt();
|
||||
mUpdatedKeys = source.readInt();
|
||||
mBadKeys = source.readInt();
|
||||
}
|
||||
|
||||
public ImportResult(int result, OperationLog log,
|
||||
int newKeys, int updatedKeys, int badKeys) {
|
||||
public ImportKeyResult(int result, OperationLog log,
|
||||
int newKeys, int updatedKeys, int badKeys) {
|
||||
super(result, log);
|
||||
mNewKeys = newKeys;
|
||||
mUpdatedKeys = updatedKeys;
|
||||
@ -70,17 +71,17 @@ public abstract class OperationResults {
|
||||
dest.writeInt(mBadKeys);
|
||||
}
|
||||
|
||||
public static Creator<ImportResult> CREATOR = new Creator<ImportResult>() {
|
||||
public ImportResult createFromParcel(final Parcel source) {
|
||||
return new ImportResult(source);
|
||||
public static Creator<ImportKeyResult> CREATOR = new Creator<ImportKeyResult>() {
|
||||
public ImportKeyResult createFromParcel(final Parcel source) {
|
||||
return new ImportKeyResult(source);
|
||||
}
|
||||
|
||||
public ImportResult[] newArray(final int size) {
|
||||
return new ImportResult[size];
|
||||
public ImportKeyResult[] newArray(final int size) {
|
||||
return new ImportKeyResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
public void displayNotify(final Activity activity) {
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
|
||||
int resultType = getResult();
|
||||
|
||||
@ -88,11 +89,11 @@ public abstract class OperationResults {
|
||||
int duration, color;
|
||||
|
||||
// Not an overall failure
|
||||
if ((resultType & ImportResult.RESULT_ERROR) == 0) {
|
||||
if ((resultType & OperationResultParcel.RESULT_ERROR) == 0) {
|
||||
String withWarnings;
|
||||
|
||||
// Any warnings?
|
||||
if ((resultType & ImportResult.RESULT_WITH_WARNINGS) > 0) {
|
||||
if ((resultType & ImportKeyResult.RESULT_WITH_WARNINGS) > 0) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
withWarnings = activity.getResources().getString(R.string.import_with_warnings);
|
||||
@ -106,7 +107,7 @@ public abstract class OperationResults {
|
||||
if (this.isOkBoth()) {
|
||||
str = activity.getResources().getQuantityString(
|
||||
R.plurals.import_keys_added_and_updated_1, mNewKeys, mNewKeys);
|
||||
str += activity.getResources().getQuantityString(
|
||||
str += " "+ activity.getResources().getQuantityString(
|
||||
R.plurals.import_keys_added_and_updated_2, mUpdatedKeys, mUpdatedKeys, withWarnings);
|
||||
} else if (isOkUpdated()) {
|
||||
str = activity.getResources().getQuantityString(
|
||||
@ -142,7 +143,7 @@ public abstract class OperationResults {
|
||||
// If we have a log and it's non-empty, show a View Log button
|
||||
if (button) {
|
||||
toast.setButtonIcon(R.drawable.ic_action_view_as_list,
|
||||
activity.getResources().getString(R.string.import_view_log));
|
||||
activity.getResources().getString(R.string.view_log));
|
||||
toast.setButtonTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setOnClickWrapper(new OnClickWrapper("supercardtoast",
|
||||
@ -151,18 +152,59 @@ public abstract class OperationResults {
|
||||
public void onClick(View view, Parcelable token) {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportResult.this);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportKeyResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
toast.show();
|
||||
|
||||
return toast;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class EditKeyResult extends OperationResultParcel {
|
||||
|
||||
private transient UncachedKeyRing mRing;
|
||||
public final Long mRingMasterKeyId;
|
||||
|
||||
public EditKeyResult(int result, OperationLog log,
|
||||
UncachedKeyRing ring) {
|
||||
super(result, log);
|
||||
mRing = ring;
|
||||
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : null;
|
||||
}
|
||||
|
||||
public UncachedKeyRing getRing() {
|
||||
return mRing;
|
||||
}
|
||||
|
||||
public EditKeyResult(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<EditKeyResult> CREATOR = new Creator<EditKeyResult>() {
|
||||
public EditKeyResult createFromParcel(final Parcel source) {
|
||||
return new EditKeyResult(source);
|
||||
}
|
||||
|
||||
public EditKeyResult[] newArray(final int size) {
|
||||
return new EditKeyResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class SaveKeyringResult extends OperationResultParcel {
|
||||
|
||||
public SaveKeyringResult(int result, OperationLog log) {
|
||||
|
@ -18,9 +18,9 @@
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -42,7 +42,7 @@ import org.spongycastle.bcpg.S2K;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
@ -71,7 +71,7 @@ public class PassphraseCacheService extends Service {
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String EXTRA_PASSPHRASE = "passphrase";
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
public static final String EXTRA_USERID = "userid";
|
||||
public static final String EXTRA_USER_ID = "user_id";
|
||||
|
||||
private static final int REQUEST_ID = 0;
|
||||
private static final long DEFAULT_TTL = 15;
|
||||
@ -103,7 +103,7 @@ public class PassphraseCacheService extends Service {
|
||||
intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassphraseCacheTtl());
|
||||
intent.putExtra(EXTRA_PASSPHRASE, passphrase);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
intent.putExtra(EXTRA_USERID, primaryUserId);
|
||||
intent.putExtra(EXTRA_USER_ID, primaryUserId);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
@ -185,7 +185,7 @@ public class PassphraseCacheService extends Service {
|
||||
// try to get master key id which is used as an identifier for cached passphrases
|
||||
try {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for masterKeyId " + keyId);
|
||||
WrappedSecretKeyRing key = new ProviderHelper(this).getWrappedSecretKeyRing(
|
||||
CanonicalizedSecretKeyRing key = new ProviderHelper(this).getCanonicalizedSecretKeyRing(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId));
|
||||
// no passphrase needed? just add empty string and return it, then
|
||||
if (!key.hasPassphrase()) {
|
||||
@ -209,18 +209,18 @@ public class PassphraseCacheService extends Service {
|
||||
// get cached passphrase
|
||||
CachedPassphrase cachedPassphrase = mPassphraseCache.get(keyId);
|
||||
if (cachedPassphrase == null) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Passphrase not (yet) cached, returning null");
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Passphrase not (yet) cached, returning null");
|
||||
// not really an error, just means the passphrase is not cached but not empty either
|
||||
return null;
|
||||
}
|
||||
|
||||
// set it again to reset the cache life cycle
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Cache passphrase again when getting it!");
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Cache passphrase again when getting it!");
|
||||
addCachedPassphrase(this, keyId, cachedPassphrase.getPassphrase(), cachedPassphrase.getPrimaryUserID());
|
||||
return cachedPassphrase.getPassphrase();
|
||||
|
||||
} 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!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -237,7 +237,7 @@ public class PassphraseCacheService extends Service {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Received broadcast...");
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Received broadcast...");
|
||||
|
||||
if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
@ -262,10 +262,8 @@ public class PassphraseCacheService extends Service {
|
||||
private static PendingIntent buildIntent(Context context, long keyId) {
|
||||
Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_ID, intent,
|
||||
return PendingIntent.getBroadcast(context, REQUEST_ID, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
return sender;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,11 +282,12 @@ public class PassphraseCacheService extends Service {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
|
||||
String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE);
|
||||
String primaryUserID = intent.getStringExtra(EXTRA_USERID);
|
||||
String primaryUserID = intent.getStringExtra(EXTRA_USER_ID);
|
||||
|
||||
Log.d(Constants.TAG,
|
||||
"PassphraseCacheService Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: "
|
||||
+ keyId + ", ttl: " + ttl + ", usrId: " + primaryUserID);
|
||||
"PassphraseCacheService: Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: "
|
||||
+ keyId + ", ttl: " + ttl + ", usrId: " + primaryUserID
|
||||
);
|
||||
|
||||
// add keyId, passphrase and primary user id to memory
|
||||
mPassphraseCache.put(keyId, new CachedPassphrase(passphrase, primaryUserID));
|
||||
@ -300,8 +299,7 @@ public class PassphraseCacheService extends Service {
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId));
|
||||
}
|
||||
|
||||
updateNotifications();
|
||||
|
||||
updateService();
|
||||
} else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
|
||||
@ -315,21 +313,21 @@ public class PassphraseCacheService extends Service {
|
||||
try {
|
||||
messenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService Sending message failed", e);
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Sending message failed", e);
|
||||
}
|
||||
} else if (ACTION_PASSPHRASE_CACHE_CLEAR.equals(intent.getAction())) {
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
// Stop all ttl alarms
|
||||
for(int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
|
||||
}
|
||||
|
||||
mPassphraseCache.clear();
|
||||
|
||||
updateNotifications();
|
||||
updateService();
|
||||
} else {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService Intent or Intent Action not supported!");
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Intent or Intent Action not supported!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,79 +346,74 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Timeout of keyId " + keyId + ", removed from memory!");
|
||||
|
||||
// stop whole service if no cached passphrases remaining
|
||||
if (mPassphraseCache.size() == 0) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheServic No passphrases remaining in memory, stopping service!");
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
updateNotifications();
|
||||
updateService();
|
||||
}
|
||||
|
||||
private void updateNotifications() {
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
private void updateService() {
|
||||
if (mPassphraseCache.size() > 0) {
|
||||
startForeground(NOTIFICATION_ID, getNotification());
|
||||
} else {
|
||||
// stop whole service if no cached passphrases remaining
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: No passphrases remaining in memory, stopping service!");
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
||||
|
||||
if(mPassphraseCache.size() > 0) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
private Notification getNotification() {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
|
||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(String.format(getString(R.string.passp_cache_notif_n_keys), mPassphraseCache.size()));
|
||||
.setContentText(String.format(getString(R.string.passp_cache_notif_n_keys),
|
||||
mPassphraseCache.size()));
|
||||
|
||||
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
|
||||
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
|
||||
|
||||
inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys));
|
||||
inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys));
|
||||
|
||||
// Moves events into the big view
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
inboxStyle.addLine(mPassphraseCache.valueAt(i).getPrimaryUserID());
|
||||
}
|
||||
// Moves events into the big view
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
inboxStyle.addLine(mPassphraseCache.valueAt(i).getPrimaryUserID());
|
||||
}
|
||||
|
||||
// Moves the big view style object into the notification object.
|
||||
builder.setStyle(inboxStyle);
|
||||
// Moves the big view style object into the notification object.
|
||||
builder.setStyle(inboxStyle);
|
||||
|
||||
// Add purging action
|
||||
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
builder.addAction(
|
||||
// Add purging action
|
||||
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
builder.addAction(
|
||||
R.drawable.abc_ic_clear_normal,
|
||||
getString(R.string.passp_cache_notif_clear),
|
||||
PendingIntent.getService(
|
||||
getApplicationContext(),
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
getApplicationContext(),
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
);
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build());
|
||||
} else { // Fallback, since expandable notifications weren't available back then
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
|
||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys, mPassphraseCache.size())))
|
||||
);
|
||||
} else {
|
||||
// Fallback, since expandable notifications weren't available back then
|
||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys,
|
||||
mPassphraseCache.size())))
|
||||
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
|
||||
|
||||
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
|
||||
builder.setContentIntent(
|
||||
builder.setContentIntent(
|
||||
PendingIntent.getService(
|
||||
getApplicationContext(),
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
getApplicationContext(),
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
);
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
} else {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -463,6 +456,7 @@ public class PassphraseCacheService extends Service {
|
||||
public String getPrimaryUserID() {
|
||||
return primaryUserID;
|
||||
}
|
||||
|
||||
public String getPassphrase() {
|
||||
return passphrase;
|
||||
}
|
||||
@ -470,6 +464,7 @@ public class PassphraseCacheService extends Service {
|
||||
public void setPrimaryUserID(String primaryUserID) {
|
||||
this.primaryUserID = primaryUserID;
|
||||
}
|
||||
|
||||
public void setPassphrase(String passphrase) {
|
||||
this.passphrase = passphrase;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@ -37,12 +38,11 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
@ -52,10 +52,12 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -64,7 +66,8 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private View mSignButton;
|
||||
private View mCertifyButton;
|
||||
private ImageView mActionCertifyImage;
|
||||
private CheckBox mUploadKeyCheckbox;
|
||||
private Spinner mSelectKeyserverSpinner;
|
||||
|
||||
@ -88,10 +91,19 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.sign_key_select_key_fragment);
|
||||
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
|
||||
mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox);
|
||||
mCertifyButton = findViewById(R.id.certify_key_certify_button);
|
||||
mActionCertifyImage = (ImageView) findViewById(R.id.certify_key_action_certify_image);
|
||||
mUserIds = (ListView) findViewById(R.id.view_key_user_ids);
|
||||
|
||||
// make certify image gray, like action icons
|
||||
mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
|
||||
mSelectKeyFragment.setCallback(this);
|
||||
mSelectKeyFragment.setFilterCertify(true);
|
||||
|
||||
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
||||
.getKeyServers()
|
||||
@ -99,7 +111,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mSelectKeyserverSpinner.setAdapter(adapter);
|
||||
|
||||
mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox);
|
||||
if (!mUploadKeyCheckbox.isChecked()) {
|
||||
mSelectKeyserverSpinner.setEnabled(false);
|
||||
} else {
|
||||
@ -118,16 +129,17 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
}
|
||||
});
|
||||
|
||||
mSignButton = findViewById(R.id.sign_key_sign_button);
|
||||
mSignButton.setOnClickListener(new OnClickListener() {
|
||||
mCertifyButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mPubKeyId != 0) {
|
||||
if (mMasterKeyId == 0) {
|
||||
mSelectKeyFragment.setError(getString(R.string.select_key_to_certify));
|
||||
Notify.showNotify(CertifyKeyActivity.this, getString(R.string.select_key_to_certify),
|
||||
Notify.Style.ERROR);
|
||||
} else {
|
||||
initiateSigning();
|
||||
initiateCertifying();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,7 +153,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
}
|
||||
Log.e(Constants.TAG, "uri: " + mDataUri);
|
||||
|
||||
mUserIds = (ListView) findViewById(R.id.view_key_user_ids);
|
||||
|
||||
mUserIdsAdapter = new UserIdsAdapter(this, null, 0, true);
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
@ -218,7 +229,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
/**
|
||||
* handles the UI bits of the signing process on the UI thread
|
||||
*/
|
||||
private void initiateSigning() {
|
||||
private void initiateCertifying() {
|
||||
// get the user's passphrase for this key (if required)
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
|
||||
if (passphrase == null) {
|
||||
@ -227,27 +238,27 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
startSigning();
|
||||
startCertifying();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
// bail out; need to wait until the user has entered the passphrase before trying again
|
||||
return;
|
||||
} else {
|
||||
startSigning();
|
||||
startCertifying();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kicks off the actual signing process on a background thread
|
||||
*/
|
||||
private void startSigning() {
|
||||
|
||||
private void startCertifying() {
|
||||
// Bail out if there is not at least one user id selected
|
||||
ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds();
|
||||
if (userIds.isEmpty()) {
|
||||
AppMsg.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!",
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(CertifyKeyActivity.this, "No identities selected!",
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -267,15 +278,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
// Message is received after signing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) {
|
||||
getString(R.string.progress_certifying), ProgressDialog.STYLE_SPINNER) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
AppMsg.makeText(CertifyKeyActivity.this, R.string.key_certify_success,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
Notify.showNotify(CertifyKeyActivity.this, R.string.key_certify_success,
|
||||
Notify.Style.INFO);
|
||||
|
||||
// check if we need to send the key to the server or not
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
@ -321,14 +332,16 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
// Message is received after uploading is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
getString(R.string.progress_uploading), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
AppMsg.makeText(CertifyKeyActivity.this, R.string.key_send_success,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OperationResultParcel.EXTRA_RESULT, message.getData());
|
||||
Notify.showNotify(CertifyKeyActivity.this, R.string.key_send_success,
|
||||
Notify.Style.INFO);
|
||||
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
|
@ -17,171 +17,69 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Patterns;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class CreateKeyActivity extends ActionBarActivity {
|
||||
|
||||
AutoCompleteTextView nameEdit;
|
||||
AutoCompleteTextView emailEdit;
|
||||
EditText passphraseEdit;
|
||||
Button createButton;
|
||||
public static final String EXTRA_NAME = "name";
|
||||
public static final String EXTRA_EMAIL = "email";
|
||||
|
||||
public static final int FRAG_ACTION_START = 0;
|
||||
public static final int FRAG_ACTION_TO_RIGHT = 1;
|
||||
public static final int FRAG_ACTION_TO_LEFT = 2;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.create_key_activity);
|
||||
|
||||
nameEdit = (AutoCompleteTextView) findViewById(R.id.name);
|
||||
emailEdit = (AutoCompleteTextView) findViewById(R.id.email);
|
||||
passphraseEdit = (EditText) findViewById(R.id.passphrase);
|
||||
createButton = (Button) findViewById(R.id.create_key_button);
|
||||
|
||||
emailEdit.setThreshold(1); // Start working from first character
|
||||
emailEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(this, android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserEmails(this)
|
||||
)
|
||||
);
|
||||
emailEdit.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
String email = editable.toString();
|
||||
if (email.length() > 0) {
|
||||
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
|
||||
if (emailMatcher.matches()) {
|
||||
emailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_ok, 0);
|
||||
} else {
|
||||
emailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_bad, 0);
|
||||
}
|
||||
} else {
|
||||
// remove drawable if email is empty
|
||||
emailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
nameEdit.setThreshold(1); // Start working from first character
|
||||
nameEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(this, android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserNames(this)
|
||||
)
|
||||
);
|
||||
|
||||
createButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createKeyCheck();
|
||||
}
|
||||
});
|
||||
|
||||
// pass extras into fragment
|
||||
CreateKeyInputFragment frag =
|
||||
CreateKeyInputFragment.newInstance(
|
||||
getIntent().getStringExtra(EXTRA_NAME),
|
||||
getIntent().getStringExtra(EXTRA_EMAIL)
|
||||
);
|
||||
loadFragment(null, frag, FRAG_ACTION_START);
|
||||
}
|
||||
|
||||
private void createKeyCheck() {
|
||||
if (isEditTextNotEmpty(this, nameEdit)
|
||||
&& isEditTextNotEmpty(this, emailEdit)
|
||||
&& isEditTextNotEmpty(this, passphraseEdit)) {
|
||||
createKey();
|
||||
}
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
// 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) {
|
||||
CreateKeyActivity.this.finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null));
|
||||
String userId = nameEdit.getText().toString() + " <" + emailEdit.getText().toString() + ">";
|
||||
parcel.mAddUserIds.add(userId);
|
||||
parcel.mNewPassphrase = passphraseEdit.getText().toString();
|
||||
|
||||
// get selected key entries
|
||||
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
|
||||
|
||||
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);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if text of given EditText is not empty. If it is empty an error is
|
||||
* set and the EditText gets the focus.
|
||||
*
|
||||
* @param context
|
||||
* @param editText
|
||||
* @return true if EditText is not empty
|
||||
*/
|
||||
private static boolean isEditTextNotEmpty(Context context, EditText editText) {
|
||||
boolean output = true;
|
||||
if (editText.getText().toString().length() == 0) {
|
||||
editText.setError("empty!");
|
||||
editText.requestFocus();
|
||||
output = false;
|
||||
} else {
|
||||
editText.setError(null);
|
||||
public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) {
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
// we could end up with overlapping fragments.
|
||||
if (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return output;
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
switch (action) {
|
||||
case FRAG_ACTION_START:
|
||||
transaction.setCustomAnimations(0, 0);
|
||||
transaction.replace(R.id.create_key_fragment_container, fragment)
|
||||
.commitAllowingStateLoss();
|
||||
break;
|
||||
case FRAG_ACTION_TO_LEFT:
|
||||
getSupportFragmentManager().popBackStackImmediate();
|
||||
break;
|
||||
case FRAG_ACTION_TO_RIGHT:
|
||||
transaction.setCustomAnimations(R.anim.frag_slide_in_from_right, R.anim.frag_slide_out_to_left,
|
||||
R.anim.frag_slide_in_from_left, R.anim.frag_slide_out_to_right);
|
||||
transaction.addToBackStack(null);
|
||||
transaction.replace(R.id.create_key_fragment_container, fragment)
|
||||
.commitAllowingStateLoss();
|
||||
break;
|
||||
|
||||
}
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,249 @@
|
||||
/*
|
||||
* 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.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
public class CreateKeyFinalFragment extends Fragment {
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
|
||||
TextView mNameEdit;
|
||||
TextView mEmailEdit;
|
||||
CheckBox mUploadCheckbox;
|
||||
View mBackButton;
|
||||
View mCreateButton;
|
||||
|
||||
public static final String ARG_NAME = "name";
|
||||
public static final String ARG_EMAIL = "email";
|
||||
public static final String ARG_PASSPHRASE = "passphrase";
|
||||
|
||||
String mName;
|
||||
String mEmail;
|
||||
String mPassphrase;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static CreateKeyFinalFragment newInstance(String name, String email, String passphrase) {
|
||||
CreateKeyFinalFragment frag = new CreateKeyFinalFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_NAME, name);
|
||||
args.putString(ARG_EMAIL, email);
|
||||
args.putString(ARG_PASSPHRASE, passphrase);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_key_final_fragment, container, false);
|
||||
|
||||
mNameEdit = (TextView) view.findViewById(R.id.name);
|
||||
mEmailEdit = (TextView) view.findViewById(R.id.email);
|
||||
mUploadCheckbox = (CheckBox) view.findViewById(R.id.create_key_upload);
|
||||
mBackButton = view.findViewById(R.id.create_key_back_button);
|
||||
mCreateButton = view.findViewById(R.id.create_key_create_button);
|
||||
|
||||
// get args
|
||||
mName = getArguments().getString(ARG_NAME);
|
||||
mEmail = getArguments().getString(ARG_EMAIL);
|
||||
mPassphrase = getArguments().getString(ARG_PASSPHRASE);
|
||||
|
||||
// set values
|
||||
mNameEdit.setText(mName);
|
||||
mEmailEdit.setText(mEmail);
|
||||
|
||||
mCreateButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createKey();
|
||||
}
|
||||
});
|
||||
|
||||
mBackButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mCreateKeyActivity.loadFragment(null, null, CreateKeyActivity.FRAG_ACTION_TO_LEFT);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_importing),
|
||||
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 OperationResults.EditKeyResult result =
|
||||
returnData.getParcelable(OperationResultParcel.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.getResult() == OperationResultParcel.RESULT_OK) {
|
||||
if (mUploadCheckbox.isChecked()) {
|
||||
// result will be displayed after upload
|
||||
uploadKey(result);
|
||||
} else {
|
||||
// TODO: return result
|
||||
result.createNotify(getActivity());
|
||||
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
}
|
||||
} else {
|
||||
// display result on error without finishing activity
|
||||
result.createNotify(getActivity());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null));
|
||||
String userId = mName + " <" + mEmail + ">";
|
||||
parcel.mAddUserIds.add(userId);
|
||||
parcel.mNewPassphrase = mPassphrase;
|
||||
|
||||
// get selected key entries
|
||||
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
|
||||
|
||||
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);
|
||||
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
private void uploadKey(final OperationResults.EditKeyResult editKeyResult) {
|
||||
// Send all information needed to service to upload key in other thread
|
||||
final Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
|
||||
|
||||
// set data uri as path to keyring
|
||||
Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
|
||||
editKeyResult.mRingMasterKeyId);
|
||||
intent.setData(blobUri);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
// upload to favorite keyserver
|
||||
String keyserver = Preferences.getPreferences(getActivity()).getKeyServers()[0];
|
||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after uploading is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_uploading), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// TODO: not supported by upload?
|
||||
// if (result.getResult() == OperationResultParcel.RESULT_OK) {
|
||||
// TODO: return result
|
||||
editKeyResult.createNotify(getActivity());
|
||||
|
||||
Notify.showNotify(getActivity(), R.string.key_send_success,
|
||||
Notify.Style.INFO);
|
||||
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
// } else {
|
||||
// // display result on error without finishing activity
|
||||
// editKeyResult.createNotify(getActivity());
|
||||
// }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Patterns;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class CreateKeyInputFragment extends Fragment {
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
|
||||
AutoCompleteTextView mNameEdit;
|
||||
AutoCompleteTextView mEmailEdit;
|
||||
EditText mPassphraseEdit;
|
||||
EditText mPassphraseEditAgain;
|
||||
View mCreateButton;
|
||||
|
||||
public static final String ARG_NAME = "name";
|
||||
public static final String ARG_EMAIL = "email";
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static CreateKeyInputFragment newInstance(String name, String email) {
|
||||
CreateKeyInputFragment frag = new CreateKeyInputFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_NAME, name);
|
||||
args.putString(ARG_EMAIL, email);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_key_input_fragment, container, false);
|
||||
|
||||
mNameEdit = (AutoCompleteTextView) view.findViewById(R.id.name);
|
||||
mEmailEdit = (AutoCompleteTextView) view.findViewById(R.id.email);
|
||||
mPassphraseEdit = (EditText) view.findViewById(R.id.passphrase);
|
||||
mPassphraseEditAgain = (EditText) view.findViewById(R.id.passphrase_again);
|
||||
mCreateButton = view.findViewById(R.id.create_key_button);
|
||||
|
||||
// initial values
|
||||
String name = getArguments().getString(ARG_NAME);
|
||||
String email = getArguments().getString(ARG_EMAIL);
|
||||
mNameEdit.setText(name);
|
||||
mEmailEdit.setText(email);
|
||||
|
||||
// focus non-empty edit fields
|
||||
if (name != null && email != null) {
|
||||
mPassphraseEdit.requestFocus();
|
||||
} else if (name != null) {
|
||||
mEmailEdit.requestFocus();
|
||||
}
|
||||
|
||||
mEmailEdit.setThreshold(1); // Start working from first character
|
||||
mEmailEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserEmails(getActivity())
|
||||
)
|
||||
);
|
||||
mEmailEdit.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
String email = editable.toString();
|
||||
if (email.length() > 0) {
|
||||
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
|
||||
if (emailMatcher.matches()) {
|
||||
mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_ok, 0);
|
||||
} else {
|
||||
mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_bad, 0);
|
||||
}
|
||||
} else {
|
||||
// remove drawable if email is empty
|
||||
mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mNameEdit.setThreshold(1); // Start working from first character
|
||||
mNameEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserNames(getActivity())
|
||||
)
|
||||
);
|
||||
|
||||
mCreateButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createKeyCheck();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
private void createKeyCheck() {
|
||||
if (isEditTextNotEmpty(getActivity(), mNameEdit)
|
||||
&& isEditTextNotEmpty(getActivity(), mEmailEdit)
|
||||
&& isEditTextNotEmpty(getActivity(), mPassphraseEdit)
|
||||
&& areEditTextsEqual(getActivity(), mPassphraseEdit, mPassphraseEditAgain)) {
|
||||
|
||||
CreateKeyFinalFragment frag =
|
||||
CreateKeyFinalFragment.newInstance(
|
||||
mNameEdit.getText().toString(),
|
||||
mEmailEdit.getText().toString(),
|
||||
mPassphraseEdit.getText().toString()
|
||||
);
|
||||
|
||||
hideKeyboard();
|
||||
mCreateKeyActivity.loadFragment(null, frag, CreateKeyActivity.FRAG_ACTION_TO_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager inputManager = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
//check if no view has focus:
|
||||
View v = getActivity().getCurrentFocus();
|
||||
if (v == null)
|
||||
return;
|
||||
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if text of given EditText is not empty. If it is empty an error is
|
||||
* set and the EditText gets the focus.
|
||||
*
|
||||
* @param context
|
||||
* @param editText
|
||||
* @return true if EditText is not empty
|
||||
*/
|
||||
private static boolean isEditTextNotEmpty(Context context, EditText editText) {
|
||||
boolean output = true;
|
||||
if (editText.getText().toString().length() == 0) {
|
||||
editText.setError(context.getString(R.string.create_key_empty));
|
||||
editText.requestFocus();
|
||||
output = false;
|
||||
} else {
|
||||
editText.setError(null);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static boolean areEditTextsEqual(Context context, EditText editText1, EditText editText2) {
|
||||
boolean output = true;
|
||||
if (!editText1.getText().toString().equals(editText2.getText().toString())) {
|
||||
editText2.setError(context.getString(R.string.create_key_passphrases_not_equal));
|
||||
editText2.requestFocus();
|
||||
output = false;
|
||||
} else {
|
||||
editText2.setError(null);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
@ -34,8 +34,6 @@ import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
@ -129,7 +127,6 @@ public class DecryptFileFragment extends DecryptFragment {
|
||||
}
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
//AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
@ -137,11 +134,8 @@ public class DecryptFileFragment extends DecryptFragment {
|
||||
if (mInputUri == null && mInputFilename.startsWith("file")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
AppMsg.makeText(
|
||||
getActivity(),
|
||||
getString(R.string.error_message,
|
||||
getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
Notify.showNotify(getActivity(), getString(R.string.error_message,
|
||||
getString(R.string.error_file_not_found)), Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ public class DecryptFragment extends Fragment {
|
||||
mResultText.setText(R.string.decrypt_result_decrypted_and_signature_certified);
|
||||
}
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_green));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_green_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
@ -146,7 +146,7 @@ public class DecryptFragment extends Fragment {
|
||||
mResultText.setText(R.string.decrypt_result_decrypted_and_signature_uncertified);
|
||||
}
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_orange));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_orange_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
@ -160,7 +160,7 @@ public class DecryptFragment extends Fragment {
|
||||
mResultText.setText(R.string.decrypt_result_decrypted_unknown_pub_key);
|
||||
}
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_orange));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_orange_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
mLookupKey.setVisibility(View.VISIBLE);
|
||||
@ -170,7 +170,7 @@ public class DecryptFragment extends Fragment {
|
||||
case OpenPgpSignatureResult.SIGNATURE_ERROR: {
|
||||
mResultText.setText(R.string.decrypt_result_invalid_signature);
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_red));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_red_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
@ -180,7 +180,7 @@ public class DecryptFragment extends Fragment {
|
||||
default: {
|
||||
mResultText.setText(R.string.error);
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_red));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_red_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
@ -192,7 +192,7 @@ public class DecryptFragment extends Fragment {
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
|
||||
// successful decryption-only
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_purple));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_purple_light));
|
||||
mResultText.setText(R.string.decrypt_result_decrypted);
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,6 @@ import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
@ -38,6 +36,7 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
@ -107,12 +106,10 @@ public class DecryptMessageFragment extends DecryptFragment {
|
||||
mCiphertext = matcher.group(1);
|
||||
decryptStart(null);
|
||||
} else {
|
||||
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
Notify.showNotify(getActivity(), R.string.error_invalid_data, Notify.Style.ERROR);
|
||||
}
|
||||
} else {
|
||||
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
Notify.showNotify(getActivity(), R.string.error_invalid_data, Notify.Style.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.edit_key_activity_new);
|
||||
setContentView(R.layout.edit_key_activity);
|
||||
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri == null) {
|
||||
|
@ -1,744 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeyEditor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SectionView;
|
||||
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EditKeyActivityOld extends ActionBarActivity implements EditorListener {
|
||||
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
|
||||
public static final String ACTION_EDIT_KEY = Constants.INTENT_PREFIX + "EDIT_KEY";
|
||||
|
||||
// possible extra keys
|
||||
public static final String EXTRA_USER_IDS = "user_ids";
|
||||
public static final String EXTRA_NO_PASSPHRASE = "no_passphrase";
|
||||
public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys";
|
||||
|
||||
// EDIT
|
||||
private Uri mDataUri;
|
||||
|
||||
private SectionView mUserIdsView;
|
||||
private SectionView mKeysView;
|
||||
|
||||
private String mCurrentPassphrase = null;
|
||||
private String mNewPassphrase = null;
|
||||
private String mSavedNewPassphrase = null;
|
||||
private boolean mIsPassphraseSet;
|
||||
private boolean mNeedsSaving;
|
||||
private boolean mIsBrandNewKeyring = false;
|
||||
|
||||
private Button mChangePassphrase;
|
||||
|
||||
private CheckBox mNoPassphrase;
|
||||
|
||||
Vector<String> mUserIds;
|
||||
Vector<UncachedSecretKey> mKeys;
|
||||
Vector<Integer> mKeysUsages;
|
||||
boolean mMasterCanSign = true;
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
|
||||
public boolean needsSaving() {
|
||||
mNeedsSaving = (mUserIdsView == null) ? false : mUserIdsView.needsSaving();
|
||||
mNeedsSaving |= (mKeysView == null) ? false : mKeysView.needsSaving();
|
||||
mNeedsSaving |= hasPassphraseChanged();
|
||||
mNeedsSaving |= mIsBrandNewKeyring;
|
||||
return mNeedsSaving;
|
||||
}
|
||||
|
||||
|
||||
public void somethingChanged() {
|
||||
ActivityCompat.invalidateOptionsMenu(this);
|
||||
}
|
||||
|
||||
public void onDeleted(Editor e, boolean wasNewItem) {
|
||||
somethingChanged();
|
||||
}
|
||||
|
||||
public void onEdited() {
|
||||
somethingChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
|
||||
R.string.btn_save, R.drawable.ic_action_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Save
|
||||
saveClicked();
|
||||
}
|
||||
}, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Cancel
|
||||
cancelClicked();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
mUserIds = new Vector<String>();
|
||||
mKeys = new Vector<UncachedSecretKey>();
|
||||
mKeysUsages = new Vector<Integer>();
|
||||
|
||||
// Catch Intents opened from other apps
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
if (ACTION_CREATE_KEY.equals(action)) {
|
||||
handleActionCreateKey(intent);
|
||||
} else if (ACTION_EDIT_KEY.equals(action)) {
|
||||
handleActionEditKey(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle intent action to create new key
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionCreateKey(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
mCurrentPassphrase = "";
|
||||
mIsBrandNewKeyring = true;
|
||||
|
||||
if (extras != null) {
|
||||
// if userId is given, prefill the fields
|
||||
if (extras.containsKey(EXTRA_USER_IDS)) {
|
||||
Log.d(Constants.TAG, "UserIds are given!");
|
||||
mUserIds.add(extras.getString(EXTRA_USER_IDS));
|
||||
}
|
||||
|
||||
// if no passphrase is given
|
||||
if (extras.containsKey(EXTRA_NO_PASSPHRASE)) {
|
||||
boolean noPassphrase = extras.getBoolean(EXTRA_NO_PASSPHRASE);
|
||||
if (noPassphrase) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassphrase.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// generate key
|
||||
if (extras.containsKey(EXTRA_GENERATE_DEFAULT_KEYS)) {
|
||||
/*
|
||||
boolean generateDefaultKeys = extras.getBoolean(EXTRA_GENERATE_DEFAULT_KEYS);
|
||||
if (generateDefaultKeys) {
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE,
|
||||
mCurrentPassphrase);
|
||||
|
||||
serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after generating is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this, getResources().getQuantityString(R.plurals.progress_generating, 1),
|
||||
ProgressDialog.STYLE_HORIZONTAL, true,
|
||||
|
||||
new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
// Stop key generation on cancel
|
||||
stopService(serviceIntent);
|
||||
EditKeyActivity.this.setResult(Activity.RESULT_CANCELED);
|
||||
EditKeyActivity.this.finish();
|
||||
}
|
||||
}) {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get new key from data bundle returned from service
|
||||
Bundle data = message.getDataAsStringList();
|
||||
|
||||
ArrayList<UncachedSecretKey> newKeys =
|
||||
PgpConversionHelper.BytesToPGPSecretKeyList(data
|
||||
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
|
||||
|
||||
ArrayList<Integer> keyUsageFlags = data.getIntegerArrayList(
|
||||
KeychainIntentService.RESULT_KEY_USAGES);
|
||||
|
||||
if (newKeys.size() == keyUsageFlags.size()) {
|
||||
for (int i = 0; i < newKeys.size(); ++i) {
|
||||
mKeys.add(newKeys.get(i));
|
||||
mKeysUsages.add(keyUsageFlags.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
buildLayout(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
serviceIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(serviceIntent);
|
||||
}
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
buildLayout(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle intent action to edit existing key
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionEditKey(Intent intent) {
|
||||
mDataUri = intent.getData();
|
||||
if (mDataUri == null) {
|
||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
|
||||
finish();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "uri: " + mDataUri);
|
||||
|
||||
try {
|
||||
Uri secretUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
WrappedSecretKeyRing keyRing = new ProviderHelper(this).getWrappedSecretKeyRing(secretUri);
|
||||
|
||||
mMasterCanSign = keyRing.getSecretKey().canCertify();
|
||||
for (WrappedSecretKey key : keyRing.secretKeyIterator()) {
|
||||
// Turn into uncached instance
|
||||
mKeys.add(key.getUncached());
|
||||
mKeysUsages.add(key.getKeyUsage()); // get usage when view is created
|
||||
}
|
||||
|
||||
boolean isSet = false;
|
||||
for (String userId : keyRing.getSecretKey().getUserIds()) {
|
||||
Log.d(Constants.TAG, "Added userId " + userId);
|
||||
if (!isSet) {
|
||||
isSet = true;
|
||||
String[] parts = KeyRing.splitUserId(userId);
|
||||
if (parts[0] != null) {
|
||||
setTitle(parts[0]);
|
||||
}
|
||||
}
|
||||
mUserIds.add(userId);
|
||||
}
|
||||
|
||||
buildLayout(false);
|
||||
|
||||
mCurrentPassphrase = "";
|
||||
mIsPassphraseSet = keyRing.hasPassphrase();
|
||||
if (!mIsPassphraseSet) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassphrase.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "Keyring not found: " + e.getMessage(), e);
|
||||
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the dialog to set a new passphrase
|
||||
*/
|
||||
private void showSetPassphraseDialog() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
// set new returned passphrase!
|
||||
mNewPassphrase = data
|
||||
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
||||
|
||||
updatePassphraseButtonText();
|
||||
somethingChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
// set title based on isPassphraseSet()
|
||||
int title;
|
||||
if (isPassphraseSet()) {
|
||||
title = R.string.title_change_passphrase;
|
||||
} else {
|
||||
title = R.string.title_set_passphrase;
|
||||
}
|
||||
|
||||
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
|
||||
messenger, null, title);
|
||||
|
||||
setPassphraseDialog.show(getSupportFragmentManager(), "setPassphraseDialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
|
||||
* id and key.
|
||||
*
|
||||
* @param newKeys
|
||||
*/
|
||||
private void buildLayout(boolean newKeys) {
|
||||
setContentView(R.layout.edit_key_activity);
|
||||
|
||||
// find views
|
||||
mChangePassphrase = (Button) findViewById(R.id.edit_key_btn_change_passphrase);
|
||||
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
|
||||
// Build layout based on given userIds and keys
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container);
|
||||
if (mIsPassphraseSet) {
|
||||
mChangePassphrase.setText(getString(R.string.btn_change_passphrase));
|
||||
}
|
||||
mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mUserIdsView.setType(SectionView.TYPE_USER_ID);
|
||||
mUserIdsView.setCanBeEdited(mMasterCanSign);
|
||||
mUserIdsView.setUserIds(mUserIds);
|
||||
mUserIdsView.setEditorListener(this);
|
||||
container.addView(mUserIdsView);
|
||||
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mKeysView.setType(SectionView.TYPE_KEY);
|
||||
mKeysView.setCanBeEdited(mMasterCanSign);
|
||||
mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
|
||||
mKeysView.setEditorListener(this);
|
||||
container.addView(mKeysView);
|
||||
|
||||
updatePassphraseButtonText();
|
||||
|
||||
mChangePassphrase.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
showSetPassphraseDialog();
|
||||
}
|
||||
});
|
||||
|
||||
// disable passphrase when no passphrase checkbox is checked!
|
||||
mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
// remove passphrase
|
||||
mSavedNewPassphrase = mNewPassphrase;
|
||||
mNewPassphrase = "";
|
||||
mChangePassphrase.setVisibility(View.GONE);
|
||||
} else {
|
||||
mNewPassphrase = mSavedNewPassphrase;
|
||||
mChangePassphrase.setVisibility(View.VISIBLE);
|
||||
}
|
||||
somethingChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private long getMasterKeyId() {
|
||||
if (mKeysView.getEditors().getChildCount() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((KeyEditor) mKeysView.getEditors().getChildAt(0)).getValue().getKeyId();
|
||||
}
|
||||
|
||||
public boolean isPassphraseSet() {
|
||||
if (mNoPassphrase.isChecked()) {
|
||||
return true;
|
||||
} else if ((mIsPassphraseSet)
|
||||
|| (mNewPassphrase != null && !mNewPassphrase.equals(""))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasPassphraseChanged() {
|
||||
if (mNoPassphrase != null) {
|
||||
if (mNoPassphrase.isChecked()) {
|
||||
return mIsPassphraseSet;
|
||||
} else {
|
||||
return (mNewPassphrase != null && !mNewPassphrase.equals(""));
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveClicked() {
|
||||
final long masterKeyId = getMasterKeyId();
|
||||
if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu
|
||||
try {
|
||||
if (!isPassphraseSet()) {
|
||||
throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
|
||||
}
|
||||
|
||||
String passphrase;
|
||||
if (mIsPassphraseSet) {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
|
||||
} else {
|
||||
passphrase = "";
|
||||
}
|
||||
if (passphrase == null) {
|
||||
PassphraseDialogFragment.show(this, masterKeyId,
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(
|
||||
EditKeyActivityOld.this, masterKeyId);
|
||||
checkEmptyIDsWanted();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mCurrentPassphrase = passphrase;
|
||||
checkEmptyIDsWanted();
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
} else {
|
||||
AppMsg.makeText(this, R.string.error_change_something_first, AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEmptyIDsWanted() {
|
||||
try {
|
||||
ArrayList<String> userIDs = getUserIds(mUserIdsView);
|
||||
List<Boolean> newIDs = mUserIdsView.getNewIDFlags();
|
||||
ArrayList<String> originalIDs = mUserIdsView.getOriginalIDs();
|
||||
int curID = 0;
|
||||
for (String userID : userIDs) {
|
||||
if (userID.equals("") && (!userID.equals(originalIDs.get(curID)) || newIDs.get(curID))) {
|
||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
|
||||
EditKeyActivityOld.this);
|
||||
|
||||
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(EditKeyActivityOld.this.getString(R.string.ask_empty_id_ok));
|
||||
|
||||
alert.setPositiveButton(EditKeyActivityOld.this.getString(android.R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
finallySaveClicked();
|
||||
}
|
||||
}
|
||||
);
|
||||
alert.setNegativeButton(this.getString(android.R.string.no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
);
|
||||
alert.setCancelable(false);
|
||||
alert.show();
|
||||
return;
|
||||
}
|
||||
curID++;
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
|
||||
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()), AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
finallySaveClicked();
|
||||
}
|
||||
|
||||
private boolean[] toPrimitiveArray(final List<Boolean> booleanList) {
|
||||
final boolean[] primitives = new boolean[booleanList.size()];
|
||||
int index = 0;
|
||||
for (Boolean object : booleanList) {
|
||||
primitives[index++] = object;
|
||||
}
|
||||
return primitives;
|
||||
}
|
||||
|
||||
private void finallySaveClicked() {
|
||||
/*
|
||||
try {
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
OldSaveKeyringParcel saveParams = new OldSaveKeyringParcel();
|
||||
saveParams.userIds = getUserIds(mUserIdsView);
|
||||
saveParams.originalIDs = mUserIdsView.getOriginalIDs();
|
||||
saveParams.deletedIDs = mUserIdsView.getDeletedIDs();
|
||||
saveParams.newIDs = toPrimitiveArray(mUserIdsView.getNewIDFlags());
|
||||
saveParams.primaryIDChanged = mUserIdsView.primaryChanged();
|
||||
saveParams.moddedKeys = toPrimitiveArray(mKeysView.getNeedsSavingArray());
|
||||
saveParams.deletedKeys = mKeysView.getDeletedKeys();
|
||||
saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
|
||||
saveParams.keysUsages = getKeysUsages(mKeysView);
|
||||
saveParams.mNewPassphrase = mNewPassphrase;
|
||||
saveParams.oldPassphrase = mCurrentPassphrase;
|
||||
saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
|
||||
saveParams.keys = getKeys(mKeysView);
|
||||
saveParams.originalPrimaryID = mUserIdsView.getOriginalPrimaryID();
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
|
||||
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after saving is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Intent data = new Intent();
|
||||
|
||||
// return uri pointing to new created key
|
||||
Uri uri = KeyRings.buildGenericKeyRingUri(getMasterKeyId());
|
||||
data.setData(uri);
|
||||
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
|
||||
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
if (needsSaving()) { //ask if we want to save
|
||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
|
||||
EditKeyActivityOld.this);
|
||||
|
||||
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(EditKeyActivityOld.this.getString(R.string.ask_save_changed_key));
|
||||
|
||||
alert.setPositiveButton(EditKeyActivityOld.this.getString(android.R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
saveClicked();
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(this.getString(android.R.string.no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
alert.show();
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user ids from the SectionView
|
||||
*
|
||||
* @param userIdsView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<String> getUserIds(SectionView userIdsView) throws PgpGeneralException {
|
||||
ArrayList<String> userIds = new ArrayList<String>();
|
||||
|
||||
ViewGroup userIdEditors = userIdsView.getEditors();
|
||||
|
||||
boolean gotMainUserId = false;
|
||||
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
|
||||
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
|
||||
String userId;
|
||||
userId = editor.getValue();
|
||||
|
||||
if (editor.isMainUserId()) {
|
||||
userIds.add(0, userId);
|
||||
gotMainUserId = true;
|
||||
} else {
|
||||
userIds.add(userId);
|
||||
}
|
||||
}
|
||||
|
||||
if (userIds.size() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_a_user_id));
|
||||
}
|
||||
|
||||
if (!gotMainUserId) {
|
||||
throw new PgpGeneralException(getString(R.string.error_main_user_id_must_not_be_empty));
|
||||
}
|
||||
|
||||
return userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns keys from the SectionView
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<UncachedSecretKey> getKeys(SectionView keysView) throws PgpGeneralException {
|
||||
ArrayList<UncachedSecretKey> keys = new ArrayList<UncachedSecretKey>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
keys.add(editor.getValue());
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns usage selections of keys from the SectionView
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getKeysUsages(SectionView keysView) throws PgpGeneralException {
|
||||
ArrayList<Integer> keysUsages = new ArrayList<Integer>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
keysUsages.add(editor.getUsage());
|
||||
}
|
||||
|
||||
return keysUsages;
|
||||
}
|
||||
|
||||
private ArrayList<Calendar> getKeysExpiryDates(SectionView keysView) throws PgpGeneralException {
|
||||
ArrayList<Calendar> keysExpiryDates = new ArrayList<Calendar>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
keysExpiryDates.add(editor.getExpiryDate());
|
||||
}
|
||||
|
||||
return keysExpiryDates;
|
||||
}
|
||||
|
||||
private void updatePassphraseButtonText() {
|
||||
mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
|
||||
: getString(R.string.btn_set_passphrase));
|
||||
}
|
||||
|
||||
}
|
@ -42,12 +42,13 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
|
||||
@ -167,8 +168,8 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
try {
|
||||
Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
WrappedSecretKeyRing keyRing =
|
||||
new ProviderHelper(getActivity()).getWrappedSecretKeyRing(secretUri);
|
||||
CanonicalizedSecretKeyRing keyRing =
|
||||
new ProviderHelper(getActivity()).getCanonicalizedSecretKeyRing(secretUri);
|
||||
|
||||
mSaveKeyringParcel = new SaveKeyringParcel(keyRing.getMasterKeyId(),
|
||||
keyRing.getUncachedKeyRing().getFingerprint());
|
||||
@ -466,26 +467,30 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
getActivity().finish();
|
||||
|
||||
// TODO below
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final OperationResults.SaveKeyringResult result =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT);
|
||||
final OperationResults.EditKeyResult result =
|
||||
returnData.getParcelable(EditKeyResult.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if good -> finish, return result to showkey and display there!
|
||||
// if bad -> display here!
|
||||
if (!result.success()) {
|
||||
result.createNotify(getActivity()).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// result.displayNotify(ImportKeysActivity.this);
|
||||
// if good -> finish, return result to showkey and display there!
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EditKeyResult.EXTRA_RESULT, result);
|
||||
getActivity().setResult(EditKeyActivity.RESULT_OK, intent);
|
||||
getActivity().finish();
|
||||
|
||||
// getActivity().finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -31,11 +31,14 @@ import android.widget.Button;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
@ -195,22 +198,23 @@ public class EncryptAsymmetricFragment extends Fragment {
|
||||
mMainUserIdRest.setText("");
|
||||
} else {
|
||||
// See if we can get a user_id from a unified query
|
||||
String[] userId;
|
||||
try {
|
||||
userId = mProviderHelper.getCachedPublicKeyRing(
|
||||
String[] userIdSplit = mProviderHelper.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingUri(mSecretKeyId)).getSplitPrimaryUserIdWithFallback();
|
||||
|
||||
if (userIdSplit[0] != null) {
|
||||
mMainUserId.setText(userIdSplit[0]);
|
||||
} else {
|
||||
mMainUserId.setText(R.string.user_id_no_name);
|
||||
}
|
||||
if (userIdSplit[1] != null) {
|
||||
mMainUserIdRest.setText(userIdSplit[1]);
|
||||
} else {
|
||||
mMainUserIdRest.setText(getString(R.string.label_key_id) + ": "
|
||||
+ PgpKeyHelper.convertKeyIdToHex(mSecretKeyId));
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
userId = null;
|
||||
}
|
||||
if (userId != null && userId[0] != null) {
|
||||
mMainUserId.setText(String.format("%#16x", Long.parseLong(userId[0])));
|
||||
} else {
|
||||
mMainUserId.setText(getResources().getString(R.string.user_id_no_name));
|
||||
}
|
||||
if (userId != null && userId[1] != null) {
|
||||
mMainUserIdRest.setText(userId[1]);
|
||||
} else {
|
||||
mMainUserIdRest.setText("");
|
||||
Notify.showNotify(getActivity(), "Key not found! This is a bug!", Notify.Style.ERROR);
|
||||
}
|
||||
mSign.setChecked(true);
|
||||
}
|
||||
|
@ -37,8 +37,6 @@ import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
@ -51,6 +49,7 @@ import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Choice;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@ -58,7 +57,7 @@ public class EncryptFileFragment extends Fragment {
|
||||
public static final String ARG_FILENAME = "filename";
|
||||
public static final String ARG_ASCII_ARMOR = "ascii_armor";
|
||||
|
||||
private static final int RESULT_CODE_FILE = 0x00007003;
|
||||
private static final int REQUEST_CODE_FILE = 0x00007003;
|
||||
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
|
||||
@ -109,10 +108,10 @@ public class EncryptFileFragment extends Fragment {
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (Constants.KITKAT) {
|
||||
FileHelper.openDocument(EncryptFileFragment.this, mInputUri, "*/*", RESULT_CODE_FILE);
|
||||
FileHelper.openDocument(EncryptFileFragment.this, mInputUri, "*/*", REQUEST_CODE_FILE);
|
||||
} else {
|
||||
FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*",
|
||||
RESULT_CODE_FILE);
|
||||
REQUEST_CODE_FILE);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -218,18 +217,18 @@ public class EncryptFileFragment extends Fragment {
|
||||
}
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInputUri == null && !mInputFilename.startsWith("content")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
AppMsg.makeText(
|
||||
Notify.showNotify(
|
||||
getActivity(),
|
||||
getString(R.string.error_message,
|
||||
getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
getString(R.string.error_file_not_found)), Notify.Style.ERROR
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -240,13 +239,13 @@ public class EncryptFileFragment extends Fragment {
|
||||
boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null
|
||||
&& mEncryptInterface.getPassphrase().length() != 0);
|
||||
if (!gotPassphrase) {
|
||||
AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
Notify.showNotify(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) {
|
||||
AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@ -256,13 +255,13 @@ public class EncryptFileFragment extends Fragment {
|
||||
&& mEncryptInterface.getEncryptionKeys().length > 0);
|
||||
|
||||
if (!gotEncryptionKeys) {
|
||||
AppMsg.makeText(getActivity(), R.string.select_encryption_key, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) {
|
||||
AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.select_encryption_or_signature_key,
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -345,8 +344,8 @@ public class EncryptFileFragment extends Fragment {
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
AppMsg.makeText(getActivity(), R.string.encrypt_sign_successful,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
Notify.showNotify(getActivity(), R.string.encrypt_sign_successful,
|
||||
Notify.Style.INFO);
|
||||
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
@ -390,7 +389,7 @@ public class EncryptFileFragment extends Fragment {
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case RESULT_CODE_FILE: {
|
||||
case REQUEST_CODE_FILE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
if (Constants.KITKAT) {
|
||||
mInputUri = data.getData();
|
||||
|
@ -30,8 +30,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
@ -41,6 +39,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
public class EncryptMessageFragment extends Fragment {
|
||||
public static final String ARG_TEXT = "text";
|
||||
@ -126,13 +125,12 @@ public class EncryptMessageFragment extends Fragment {
|
||||
boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null
|
||||
&& mEncryptInterface.getPassphrase().length() != 0);
|
||||
if (!gotPassphrase) {
|
||||
AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
Notify.showNotify(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) {
|
||||
AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -143,8 +141,8 @@ public class EncryptMessageFragment extends Fragment {
|
||||
&& mEncryptInterface.getEncryptionKeys().length > 0);
|
||||
|
||||
if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) {
|
||||
AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.select_encryption_or_signature_key,
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -226,9 +224,8 @@ public class EncryptMessageFragment extends Fragment {
|
||||
|
||||
if (toClipboard) {
|
||||
ClipboardReflection.copyToClipboard(getActivity(), output);
|
||||
AppMsg.makeText(getActivity(),
|
||||
R.string.encrypt_sign_clipboard_successful, AppMsg.STYLE_INFO)
|
||||
.show();
|
||||
Notify.showNotify(getActivity(),
|
||||
R.string.encrypt_sign_clipboard_successful, Notify.Style.INFO);
|
||||
} else {
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
|
||||
|
@ -22,15 +22,19 @@ import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class FirstTimeActivity extends ActionBarActivity {
|
||||
|
||||
Button mCreateKey;
|
||||
Button mImportKey;
|
||||
Button mSkipSetup;
|
||||
View mCreateKey;
|
||||
View mImportKey;
|
||||
View mSkipSetup;
|
||||
|
||||
public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -40,16 +44,14 @@ public class FirstTimeActivity extends ActionBarActivity {
|
||||
|
||||
setContentView(R.layout.first_time_activity);
|
||||
|
||||
mCreateKey = (Button) findViewById(R.id.first_time_create_key);
|
||||
mImportKey = (Button) findViewById(R.id.first_time_import_key);
|
||||
mSkipSetup = (Button) findViewById(R.id.first_time_cancel);
|
||||
mCreateKey = findViewById(R.id.first_time_create_key);
|
||||
mImportKey = findViewById(R.id.first_time_import_key);
|
||||
mSkipSetup = findViewById(R.id.first_time_cancel);
|
||||
|
||||
mSkipSetup.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(FirstTimeActivity.this, KeyListActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
finishSetup();
|
||||
}
|
||||
});
|
||||
|
||||
@ -58,8 +60,7 @@ public class FirstTimeActivity extends ActionBarActivity {
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(FirstTimeActivity.this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
|
||||
@ -67,11 +68,30 @@ public class FirstTimeActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(FirstTimeActivity.this, CreateKeyActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQUEST_CODE_CREATE_OR_IMPORT_KEY) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
finishSetup();
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "No valid request code!");
|
||||
}
|
||||
}
|
||||
|
||||
private void finishSetup() {
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
prefs.setFirstTime(false);
|
||||
Intent intent = new Intent(FirstTimeActivity.this, KeyListActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
@ -40,17 +40,19 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.keyimport.FileImportCache;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
@ -98,6 +100,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
|
||||
public static final int VIEW_PAGER_HEIGHT = 64; // dp
|
||||
|
||||
private static final int ALL_TABS = -1;
|
||||
private static final int TAB_KEYSERVER = 0;
|
||||
private static final int TAB_QR_CODE = 1;
|
||||
private static final int TAB_FILE = 2;
|
||||
@ -150,7 +153,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
}
|
||||
|
||||
Bundle serverBundle = null;
|
||||
boolean serverOnly = false;
|
||||
int showTabOnly = ALL_TABS;
|
||||
if (ACTION_IMPORT_KEY.equals(action)) {
|
||||
/* Keychain's own Actions */
|
||||
|
||||
@ -214,7 +217,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
serverBundle.putString(ImportKeysServerFragment.ARG_QUERY, query);
|
||||
serverBundle.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true);
|
||||
// display server tab only
|
||||
serverOnly = true;
|
||||
showTabOnly = TAB_KEYSERVER;
|
||||
mSwitchToTab = TAB_KEYSERVER;
|
||||
|
||||
// action: search immediately
|
||||
@ -227,11 +230,18 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)
|
||||
|| ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(action)) {
|
||||
} else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
|
||||
// NOTE: this only displays the appropriate fragment, no actions are taken
|
||||
mSwitchToTab = TAB_FILE;
|
||||
|
||||
// no immediate actions!
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(action)) {
|
||||
// NOTE: this only displays the appropriate fragment, no actions are taken
|
||||
mSwitchToTab = TAB_FILE;
|
||||
// display file tab only
|
||||
showTabOnly = TAB_FILE;
|
||||
|
||||
// no immediate actions!
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_QR_CODE.equals(action)) {
|
||||
@ -259,10 +269,10 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
}
|
||||
|
||||
initTabs(serverBundle, serverOnly);
|
||||
initTabs(serverBundle, showTabOnly);
|
||||
}
|
||||
|
||||
private void initTabs(Bundle serverBundle, boolean serverOnly) {
|
||||
private void initTabs(Bundle serverBundle, int showTabOnly) {
|
||||
mTabsAdapter = new PagerTabStripAdapter(this);
|
||||
mViewPager.setAdapter(mTabsAdapter);
|
||||
mSlidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||
@ -285,15 +295,34 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
}
|
||||
});
|
||||
|
||||
mTabsAdapter.addTab(ImportKeysServerFragment.class,
|
||||
serverBundle, getString(R.string.import_tab_keyserver));
|
||||
if (!serverOnly) {
|
||||
mTabsAdapter.addTab(ImportKeysQrCodeFragment.class,
|
||||
null, getString(R.string.import_tab_qr_code));
|
||||
mTabsAdapter.addTab(ImportKeysFileFragment.class,
|
||||
null, getString(R.string.import_tab_direct));
|
||||
mTabsAdapter.addTab(ImportKeysKeybaseFragment.class,
|
||||
null, getString(R.string.import_tab_keybase));
|
||||
switch (showTabOnly) {
|
||||
case ALL_TABS:
|
||||
// show all tabs
|
||||
mTabsAdapter.addTab(ImportKeysServerFragment.class,
|
||||
serverBundle, getString(R.string.import_tab_keyserver));
|
||||
mTabsAdapter.addTab(ImportKeysQrCodeFragment.class,
|
||||
null, getString(R.string.import_tab_qr_code));
|
||||
mTabsAdapter.addTab(ImportKeysFileFragment.class,
|
||||
null, getString(R.string.import_tab_direct));
|
||||
mTabsAdapter.addTab(ImportKeysKeybaseFragment.class,
|
||||
null, getString(R.string.import_tab_keybase));
|
||||
break;
|
||||
case TAB_KEYSERVER:
|
||||
mTabsAdapter.addTab(ImportKeysServerFragment.class,
|
||||
serverBundle, getString(R.string.import_tab_keyserver));
|
||||
break;
|
||||
case TAB_QR_CODE:
|
||||
mTabsAdapter.addTab(ImportKeysQrCodeFragment.class,
|
||||
null, getString(R.string.import_tab_qr_code));
|
||||
break;
|
||||
case TAB_FILE:
|
||||
mTabsAdapter.addTab(ImportKeysFileFragment.class,
|
||||
null, getString(R.string.import_tab_direct));
|
||||
break;
|
||||
case TAB_KEYBASE:
|
||||
mTabsAdapter.addTab(ImportKeysKeybaseFragment.class,
|
||||
null, getString(R.string.import_tab_keybase));
|
||||
break;
|
||||
}
|
||||
|
||||
// update layout after operations
|
||||
@ -354,9 +383,8 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
ImportKeysServerFragment f = (ImportKeysServerFragment)
|
||||
getActiveFragment(mViewPager, TAB_KEYSERVER);
|
||||
|
||||
// TODO: Currently it simply uses keyserver nr 0
|
||||
String keyserver = Preferences.getPreferences(ImportKeysActivity.this)
|
||||
.getKeyServers()[0];
|
||||
// ask favorite keyserver
|
||||
String keyserver = Preferences.getPreferences(ImportKeysActivity.this).getKeyServers()[0];
|
||||
|
||||
// set fields of ImportKeysServerFragment
|
||||
f.setQueryAndKeyserver(query, keyserver);
|
||||
@ -427,15 +455,15 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final ImportResult result =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT);
|
||||
final ImportKeyResult result =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT_IMPORT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_RESULT, result);
|
||||
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
|
||||
ImportKeysActivity.this.setResult(RESULT_OK, intent);
|
||||
ImportKeysActivity.this.finish();
|
||||
return;
|
||||
@ -451,7 +479,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
result.displayNotify(ImportKeysActivity.this);
|
||||
result.createNotify(ImportKeysActivity.this).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -470,19 +498,29 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
|
||||
// get DATA from selected key entries
|
||||
ArrayList<ParcelableKeyRing> selectedEntries = mListFragment.getSelectedData();
|
||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
// instead of given the entries by Intent extra, cache them into a file
|
||||
// to prevent Java Binder problems on heavy imports
|
||||
// read FileImportCache for more info.
|
||||
try {
|
||||
FileImportCache cache = new FileImportCache(this);
|
||||
cache.writeCache(selectedEntries);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Problem writing cache file", e);
|
||||
Notify.showNotify(this, "Problem writing cache file!", Notify.Style.ERROR);
|
||||
}
|
||||
} else if (ls instanceof ImportKeysListFragment.KeyserverLoaderState) {
|
||||
ImportKeysListFragment.KeyserverLoaderState sls = (ImportKeysListFragment.KeyserverLoaderState) ls;
|
||||
|
||||
|
@ -35,7 +35,6 @@ import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader;
|
||||
@ -288,13 +287,13 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
if (error == null) {
|
||||
// No error
|
||||
mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings();
|
||||
} else if (error instanceof ImportKeysListLoader.FileHasNoContent) {
|
||||
Notify.showNotify(getActivity(), R.string.error_import_file_no_content, Notify.Style.ERROR);
|
||||
} else if (error instanceof ImportKeysListLoader.NonPgpPart) {
|
||||
} else if (error instanceof ImportKeysListLoader.NoValidKeysException) {
|
||||
Notify.showNotify(getActivity(), R.string.error_import_no_valid_keys, Notify.Style.ERROR);
|
||||
} else if (error instanceof ImportKeysListLoader.NonPgpPartException) {
|
||||
Notify.showNotify(getActivity(),
|
||||
((ImportKeysListLoader.NonPgpPart) error).getCount() + " " + getResources().
|
||||
((ImportKeysListLoader.NonPgpPartException) error).getCount() + " " + getResources().
|
||||
getQuantityString(R.plurals.error_import_non_pgp_part,
|
||||
((ImportKeysListLoader.NonPgpPart) error).getCount()),
|
||||
((ImportKeysListLoader.NonPgpPartException) error).getCount()),
|
||||
Notify.Style.OK
|
||||
);
|
||||
} else {
|
||||
@ -308,9 +307,11 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
if (error == null) {
|
||||
// No error
|
||||
} else if (error instanceof Keyserver.QueryTooShortException) {
|
||||
Notify.showNotify(getActivity(), R.string.error_keyserver_insufficient_query, Notify.Style.ERROR);
|
||||
Notify.showNotify(getActivity(), R.string.error_query_too_short, Notify.Style.ERROR);
|
||||
} else if (error instanceof Keyserver.TooManyResponsesException) {
|
||||
Notify.showNotify(getActivity(), R.string.error_keyserver_too_many_responses, Notify.Style.ERROR);
|
||||
Notify.showNotify(getActivity(), R.string.error_too_many_responses, Notify.Style.ERROR);
|
||||
} else if (error instanceof Keyserver.QueryTooShortOrTooManyResponsesException) {
|
||||
Notify.showNotify(getActivity(), R.string.error_too_short_or_too_many_responses, Notify.Style.ERROR);
|
||||
} else if (error instanceof Keyserver.QueryFailedException) {
|
||||
Log.d(Constants.TAG,
|
||||
"Unrecoverable keyserver query error: " + error.getLocalizedMessage());
|
||||
|
@ -17,29 +17,19 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Constants.choice.algorithm;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@ -51,11 +41,10 @@ public class KeyListActivity extends DrawerActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// if this is the first time show first time activity
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.getFirstTime()) {
|
||||
prefs.setFirstTime(false);
|
||||
Intent intent = new Intent(this, FirstTimeActivity.class);
|
||||
startActivity(intent);
|
||||
if (prefs.isFirstTime()) {
|
||||
startActivity(new Intent(this, FirstTimeActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
@ -85,7 +74,7 @@ public class KeyListActivity extends DrawerActivity {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_list_import:
|
||||
case R.id.menu_key_list_add:
|
||||
importKeys();
|
||||
return true;
|
||||
|
||||
@ -93,6 +82,12 @@ public class KeyListActivity extends DrawerActivity {
|
||||
createKey();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_import_existing_key:
|
||||
Intent intentImportExisting = new Intent(this, ImportKeysActivity.class);
|
||||
intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intentImportExisting, 0);
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_export:
|
||||
mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
||||
return true;
|
||||
@ -100,25 +95,27 @@ public class KeyListActivity extends DrawerActivity {
|
||||
case R.id.menu_key_list_debug_read:
|
||||
try {
|
||||
KeychainDatabase.debugRead(this);
|
||||
AppMsg.makeText(this, "Restored from backup", AppMsg.STYLE_CONFIRM).show();
|
||||
Notify.showNotify(this, "Restored Notify.Style backup", Notify.Style.INFO);
|
||||
getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IO Error", e);
|
||||
AppMsg.makeText(this, "IO Error: " + e.getMessage(), AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(this, "IO Notify.Style: " + e.getMessage(), Notify.Style.ERROR);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_write:
|
||||
try {
|
||||
KeychainDatabase.debugWrite(this);
|
||||
AppMsg.makeText(this, "Backup successful", AppMsg.STYLE_CONFIRM).show();
|
||||
} catch (IOException e) {
|
||||
Notify.showNotify(this, "Backup Notify.Style", Notify.Style.INFO);
|
||||
} catch(IOException e) {
|
||||
Log.e(Constants.TAG, "IO Error", e);
|
||||
AppMsg.makeText(this, "IO Error: " + e.getMessage(), AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(this, "IO Notify.Style: " + e.getMessage(), Notify.Style.ERROR);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_first_time:
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
prefs.setFirstTime(true);
|
||||
Intent intent = new Intent(this, FirstTimeActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
@ -139,4 +136,4 @@ public class KeyListActivity extends DrawerActivity {
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@ -46,14 +47,11 @@ import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
@ -62,6 +60,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Highlighter;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@ -86,6 +85,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
private Button mButtonEmptyCreate;
|
||||
private Button mButtonEmptyImport;
|
||||
|
||||
public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012;
|
||||
|
||||
/**
|
||||
* Load custom layout with StickyListView from library
|
||||
@ -104,11 +104,8 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getActivity(), EditKeyActivityOld.class);
|
||||
intent.setAction(EditKeyActivityOld.ACTION_CREATE_KEY);
|
||||
intent.putExtra(EditKeyActivityOld.EXTRA_GENERATE_DEFAULT_KEYS, true);
|
||||
intent.putExtra(EditKeyActivityOld.EXTRA_USER_IDS, ""); // show user id view
|
||||
startActivityForResult(intent, 0);
|
||||
Intent intent = new Intent(getActivity(), CreateKeyActivity.class);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
mButtonEmptyImport = (Button) view.findViewById(R.id.key_list_empty_button_import);
|
||||
@ -117,8 +114,8 @@ public class KeyListFragment extends LoaderFragment
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getActivity(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE);
|
||||
startActivityForResult(intent, 0);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
|
||||
@ -339,8 +336,8 @@ public class KeyListFragment extends LoaderFragment
|
||||
public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) {
|
||||
// Can only work on singular secret keys
|
||||
if(hasSecret && masterKeyIds.length > 1) {
|
||||
AppMsg.makeText(getActivity(), R.string.secret_cannot_multiple,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.secret_cannot_multiple,
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -437,9 +434,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
private class ItemViewHolder {
|
||||
TextView mMainUserId;
|
||||
TextView mMainUserIdRest;
|
||||
FrameLayout mStatusLayout;
|
||||
TextView mRevoked;
|
||||
ImageView mVerified;
|
||||
ImageView mStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -448,9 +443,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
ItemViewHolder holder = new ItemViewHolder();
|
||||
holder.mMainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
holder.mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
holder.mStatusLayout = (FrameLayout) view.findViewById(R.id.status_layout);
|
||||
holder.mRevoked = (TextView) view.findViewById(R.id.revoked);
|
||||
holder.mVerified = (ImageView) view.findViewById(R.id.verified);
|
||||
holder.mStatus = (ImageView) view.findViewById(R.id.status_image);
|
||||
view.setTag(holder);
|
||||
return view;
|
||||
}
|
||||
@ -482,30 +475,40 @@ public class KeyListFragment extends LoaderFragment
|
||||
}
|
||||
}
|
||||
|
||||
{ // set edit button and revoked info, specific by key type
|
||||
{ // set edit button and status, specific by key type
|
||||
|
||||
if (cursor.getInt(KeyListFragment.INDEX_HAS_ANY_SECRET) != 0) {
|
||||
// this is a secret key
|
||||
h.mStatusLayout.setVisibility(View.VISIBLE);
|
||||
h.mRevoked.setVisibility(View.GONE);
|
||||
h.mVerified.setVisibility(View.GONE);
|
||||
} else {
|
||||
// this is a public key - show if it's revoked
|
||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
boolean isExpired = !cursor.isNull(INDEX_EXPIRY)
|
||||
&& new Date(cursor.getLong(INDEX_EXPIRY)*1000).before(new Date());
|
||||
boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
||||
|
||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
boolean isExpired = !cursor.isNull(INDEX_EXPIRY)
|
||||
&& new Date(cursor.getLong(INDEX_EXPIRY)*1000).before(new Date());
|
||||
if(isRevoked || isExpired) {
|
||||
h.mStatusLayout.setVisibility(View.VISIBLE);
|
||||
h.mRevoked.setVisibility(View.VISIBLE);
|
||||
h.mVerified.setVisibility(View.GONE);
|
||||
h.mRevoked.setText(isRevoked ? R.string.revoked : R.string.expired);
|
||||
// Note: order is important!
|
||||
if (isRevoked) {
|
||||
h.mStatus.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_revoked_cutout));
|
||||
h.mStatus.setColorFilter(getResources().getColor(R.color.android_red_dark),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
h.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (isExpired) {
|
||||
h.mStatus.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_expired_cutout));
|
||||
h.mStatus.setColorFilter(getResources().getColor(R.color.android_orange_dark),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
h.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (isVerified) {
|
||||
if (cursor.getInt(KeyListFragment.INDEX_HAS_ANY_SECRET) != 0) {
|
||||
// this is a secret key
|
||||
h.mStatus.setVisibility(View.GONE);
|
||||
} else {
|
||||
boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
||||
h.mStatusLayout.setVisibility(isVerified ? View.VISIBLE : View.GONE);
|
||||
h.mRevoked.setVisibility(View.GONE);
|
||||
h.mVerified.setVisibility(isVerified ? View.VISIBLE : View.GONE);
|
||||
// this is a public key - show if it's verified
|
||||
h.mStatus.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_verified_cutout));
|
||||
h.mStatus.setColorFilter(getResources().getColor(R.color.android_green_dark),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
h.mStatus.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
h.mStatus.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,9 +178,11 @@ public class LogDisplayFragment extends ListFragment implements OnTouchListener
|
||||
if (entry.mParameters != null && entry.mParameters.length > 0
|
||||
&& entry.mParameters[0] instanceof Integer) {
|
||||
ih.mText.setText(getResources().getQuantityString(entry.mType.getMsgId(),
|
||||
(Integer) entry.mParameters[0], entry.mParameters));
|
||||
(Integer) entry.mParameters[0],
|
||||
entry.mParameters));
|
||||
} else {
|
||||
ih.mText.setText(getResources().getString(entry.mType.getMsgId(), entry.mParameters));
|
||||
ih.mText.setText(getResources().getString(entry.mType.getMsgId(),
|
||||
entry.mParameters));
|
||||
}
|
||||
ih.mText.setTextColor(entry.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK);
|
||||
convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0);
|
||||
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||
|
||||
public class QrCodeActivity extends ActionBarActivity {
|
||||
|
||||
private ImageView mFingerprintQrCode;
|
||||
|
||||
private static final int QR_CODE_SIZE = 1000;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done" custom action bar
|
||||
ActionBarHelper.setOneButtonView(getSupportActionBar(),
|
||||
R.string.btn_okay, R.drawable.ic_action_done,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// "Done"
|
||||
finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.qr_code_activity);
|
||||
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mFingerprintQrCode = (ImageView) findViewById(R.id.qr_code_image);
|
||||
|
||||
mFingerprintQrCode.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
try {
|
||||
byte[] blob = (byte[]) providerHelper.getGenericData(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
KeychainContract.KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
if (blob == null) {
|
||||
Log.e(Constants.TAG, "key not found!");
|
||||
Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR);
|
||||
finish();
|
||||
}
|
||||
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
|
||||
String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
mFingerprintQrCode.setImageBitmap(QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE));
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// custom activity transition to get zoom in effect
|
||||
this.overridePendingTransition(R.anim.qr_code_zoom_enter, android.R.anim.fade_out);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// custom activity transition to get zoom out effect
|
||||
this.overridePendingTransition(0, R.anim.qr_code_zoom_exit);
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@ -92,7 +93,7 @@ public class UploadKeyActivity extends ActionBarActivity {
|
||||
intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
|
||||
|
||||
// set data uri as path to keyring
|
||||
Uri blobUri = KeychainContract.KeyRingData.buildPublicKeyRingUri(mDataUri);
|
||||
Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
intent.setData(blobUri);
|
||||
|
||||
// fill values for this action
|
||||
@ -105,7 +106,7 @@ public class UploadKeyActivity extends ActionBarActivity {
|
||||
|
||||
// Message is received after uploading is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
getString(R.string.progress_uploading), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
@ -35,7 +35,7 @@ import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
@ -143,16 +143,16 @@ public class ViewCertActivity extends ActionBarActivity
|
||||
try {
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
|
||||
WrappedPublicKeyRing signeeRing =
|
||||
providerHelper.getWrappedPublicKeyRing(data.getLong(INDEX_MASTER_KEY_ID));
|
||||
WrappedPublicKeyRing signerRing =
|
||||
providerHelper.getWrappedPublicKeyRing(sig.getKeyId());
|
||||
CanonicalizedPublicKeyRing signeeRing =
|
||||
providerHelper.getCanonicalizedPublicKeyRing(data.getLong(INDEX_MASTER_KEY_ID));
|
||||
CanonicalizedPublicKeyRing signerRing =
|
||||
providerHelper.getCanonicalizedPublicKeyRing(sig.getKeyId());
|
||||
|
||||
try {
|
||||
sig.init(signerRing.getPublicKey());
|
||||
if (sig.verifySignature(signeeRing.getPublicKey(), signeeUid)) {
|
||||
mStatus.setText(R.string.cert_verify_ok);
|
||||
mStatus.setTextColor(getResources().getColor(R.color.result_green));
|
||||
mStatus.setTextColor(getResources().getColor(R.color.android_green_light));
|
||||
} else {
|
||||
mStatus.setText(R.string.cert_verify_failed);
|
||||
mStatus.setTextColor(getResources().getColor(R.color.alert));
|
||||
|
@ -19,9 +19,9 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NdefRecord;
|
||||
@ -43,8 +43,9 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@ -54,11 +55,12 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout.TabColorizer;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@ -81,11 +83,11 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
private ViewPager mViewPager;
|
||||
private SlidingTabLayout mSlidingTabLayout;
|
||||
private PagerTabStripAdapter mTabsAdapter;
|
||||
private View mStatusDivider;
|
||||
private View mStatusRevoked;
|
||||
private View mStatusExpired;
|
||||
|
||||
public static final int REQUEST_CODE_LOOKUP_KEY = 0x00007006;
|
||||
private LinearLayout mStatusLayout;
|
||||
private TextView mStatusText;
|
||||
private ImageView mStatusImage;
|
||||
private View mStatusDivider;
|
||||
|
||||
// NFC
|
||||
private NfcAdapter mNfcAdapter;
|
||||
@ -115,9 +117,10 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
|
||||
setContentView(R.layout.view_key_activity);
|
||||
|
||||
mStatusDivider = findViewById(R.id.status_divider);
|
||||
mStatusRevoked = findViewById(R.id.view_key_revoked);
|
||||
mStatusExpired = findViewById(R.id.view_key_expired);
|
||||
mStatusLayout = (LinearLayout) findViewById(R.id.view_key_status_layout);
|
||||
mStatusText = (TextView) findViewById(R.id.view_key_status_text);
|
||||
mStatusImage = (ImageView) findViewById(R.id.view_key_status_image);
|
||||
mStatusDivider = findViewById(R.id.view_key_status_divider);
|
||||
|
||||
mViewPager = (ViewPager) findViewById(R.id.view_key_pager);
|
||||
mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.view_key_sliding_tab_layout);
|
||||
@ -140,20 +143,27 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
|
||||
}
|
||||
|
||||
Uri dataUri = getDataUri();
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
mDataUri = getIntent().getData();
|
||||
if (mDataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be uri of key!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (mDataUri.getHost().equals(ContactsContract.AUTHORITY)) {
|
||||
mDataUri = ContactHelper.dataUriFromContactUri(this, mDataUri);
|
||||
}
|
||||
|
||||
loadData(dataUri);
|
||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
||||
|
||||
initNfc(dataUri);
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
||||
|
||||
initNfc(mDataUri);
|
||||
|
||||
mShowAdvancedTabs = false;
|
||||
|
||||
initTabs(dataUri);
|
||||
initTabs(mDataUri);
|
||||
|
||||
// switch to tab selected by extra
|
||||
mViewPager.setCurrentItem(switchToTab);
|
||||
@ -230,24 +240,6 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
mSlidingTabLayout.setViewPager(mViewPager);
|
||||
}
|
||||
|
||||
private Uri getDataUri() {
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri != null && dataUri.getHost().equals(ContactsContract.AUTHORITY)) {
|
||||
dataUri = ContactHelper.dataUriFromContactUri(this, dataUri);
|
||||
}
|
||||
return dataUri;
|
||||
}
|
||||
|
||||
private void loadData(Uri dataUri) {
|
||||
mDataUri = dataUri;
|
||||
|
||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
||||
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
@ -268,14 +260,6 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
startActivity(homeIntent);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_key_view_update: {
|
||||
updateFromKeyserver(mDataUri, mProviderHelper);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_key_view_export_keyserver: {
|
||||
uploadToKeyserver(mDataUri);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_key_view_export_file: {
|
||||
exportToFile(mDataUri, mExportHelper, mProviderHelper);
|
||||
return true;
|
||||
@ -295,7 +279,7 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
}
|
||||
}
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(this, R.string.error_key_not_found, Notify.Style.ERROR);
|
||||
Log.e(Constants.TAG, "Key not found", e);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
@ -317,26 +301,6 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
);
|
||||
}
|
||||
|
||||
private void uploadToKeyserver(Uri dataUri) throws ProviderHelper.NotFoundException {
|
||||
Intent uploadIntent = new Intent(this, UploadKeyActivity.class);
|
||||
uploadIntent.setData(dataUri);
|
||||
startActivityForResult(uploadIntent, 0);
|
||||
}
|
||||
|
||||
private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper)
|
||||
throws ProviderHelper.NotFoundException {
|
||||
byte[] blob = (byte[]) providerHelper.getGenericData(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
|
||||
|
||||
Intent queryIntent = new Intent(this, ImportKeysActivity.class);
|
||||
queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT);
|
||||
queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint);
|
||||
|
||||
startActivityForResult(queryIntent, REQUEST_CODE_LOOKUP_KEY);
|
||||
}
|
||||
|
||||
private void deleteKey(Uri dataUri, ExportHelper exportHelper) {
|
||||
// Message is received after key is deleted
|
||||
Handler returnHandler = new Handler() {
|
||||
@ -352,22 +316,11 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_LOOKUP_KEY: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
ImportResult result = data.getParcelableExtra(ImportKeysActivity.EXTRA_RESULT);
|
||||
if (result != null) {
|
||||
result.displayNotify(this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -455,8 +408,8 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case NFC_SENT:
|
||||
AppMsg.makeText(ViewKeyActivity.this, R.string.nfc_successful,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
Notify.showNotify(
|
||||
ViewKeyActivity.this, R.string.nfc_successful, Notify.Style.INFO);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -515,22 +468,32 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId);
|
||||
getSupportActionBar().setSubtitle(keyIdStr);
|
||||
|
||||
// If this key is revoked, it cannot be used for anything!
|
||||
if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) {
|
||||
mStatusDivider.setVisibility(View.VISIBLE);
|
||||
mStatusRevoked.setVisibility(View.VISIBLE);
|
||||
mStatusExpired.setVisibility(View.GONE);
|
||||
} else {
|
||||
mStatusRevoked.setVisibility(View.GONE);
|
||||
boolean isRevoked = data.getInt(INDEX_UNIFIED_IS_REVOKED) > 0;
|
||||
boolean isExpired = !data.isNull(INDEX_UNIFIED_EXPIRY)
|
||||
&& new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000).before(new Date());
|
||||
|
||||
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
|
||||
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
|
||||
mStatusDivider.setVisibility(View.VISIBLE);
|
||||
mStatusExpired.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mStatusDivider.setVisibility(View.GONE);
|
||||
mStatusExpired.setVisibility(View.GONE);
|
||||
}
|
||||
// Note: order is important
|
||||
if (isRevoked) {
|
||||
mStatusText.setText(R.string.view_key_revoked);
|
||||
mStatusText.setTextColor(getResources().getColor(R.color.android_red_dark));
|
||||
mStatusImage.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_revoked_cutout));
|
||||
mStatusImage.setColorFilter(getResources().getColor(R.color.android_red_dark),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
mStatusDivider.setVisibility(View.VISIBLE);
|
||||
mStatusLayout.setVisibility(View.VISIBLE);
|
||||
} else if (isExpired) {
|
||||
mStatusText.setText(R.string.view_key_expired);
|
||||
mStatusText.setTextColor(getResources().getColor(R.color.android_orange_dark));
|
||||
mStatusImage.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_expired_cutout));
|
||||
mStatusImage.setColorFilter(getResources().getColor(R.color.android_orange_dark),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
mStatusDivider.setVisibility(View.VISIBLE);
|
||||
mStatusLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mStatusDivider.setVisibility(View.GONE);
|
||||
mStatusLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
@ -27,19 +28,21 @@ import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@ -52,7 +55,9 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
private View mActionEditDivider;
|
||||
private View mActionEncrypt;
|
||||
private View mActionCertify;
|
||||
private View mActionCertifyDivider;
|
||||
private View mActionCertifyText;
|
||||
private ImageView mActionCertifyImage;
|
||||
private View mActionUpdate;
|
||||
|
||||
private ListView mUserIds;
|
||||
|
||||
@ -76,7 +81,12 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider);
|
||||
mActionEncrypt = view.findViewById(R.id.view_key_action_encrypt);
|
||||
mActionCertify = view.findViewById(R.id.view_key_action_certify);
|
||||
mActionCertifyDivider = view.findViewById(R.id.view_key_action_certify_divider);
|
||||
mActionCertifyText = view.findViewById(R.id.view_key_action_certify_text);
|
||||
mActionCertifyImage = (ImageView) view.findViewById(R.id.view_key_action_certify_image);
|
||||
// make certify image gray, like action icons
|
||||
mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
mActionUpdate = view.findViewById(R.id.view_key_action_update);
|
||||
|
||||
return root;
|
||||
}
|
||||
@ -116,6 +126,15 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
editKey(mDataUri);
|
||||
}
|
||||
});
|
||||
mActionUpdate.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
try {
|
||||
updateFromKeyserver(mDataUri, new ProviderHelper(getActivity()));
|
||||
} catch (NotFoundException e) {
|
||||
Notify.showNotify(getActivity(), R.string.error_key_not_found, Notify.Style.ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0);
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
@ -182,6 +201,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) {
|
||||
mActionEdit.setEnabled(false);
|
||||
mActionCertify.setEnabled(false);
|
||||
mActionCertifyText.setEnabled(false);
|
||||
mActionEncrypt.setEnabled(false);
|
||||
} else {
|
||||
mActionEdit.setEnabled(true);
|
||||
@ -189,9 +209,11 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
|
||||
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
|
||||
mActionCertify.setEnabled(false);
|
||||
mActionCertifyText.setEnabled(false);
|
||||
mActionEncrypt.setEnabled(false);
|
||||
} else {
|
||||
mActionCertify.setEnabled(true);
|
||||
mActionCertifyText.setEnabled(true);
|
||||
mActionEncrypt.setEnabled(true);
|
||||
}
|
||||
}
|
||||
@ -225,7 +247,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
private void encrypt(Uri dataUri) {
|
||||
// If there is no encryption key, don't bother.
|
||||
if (!mHasEncrypt) {
|
||||
AppMsg.makeText(getActivity(), R.string.error_no_encrypt_subkey, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.error_no_encrypt_subkey, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@ -243,18 +265,30 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper)
|
||||
throws ProviderHelper.NotFoundException {
|
||||
byte[] blob = (byte[]) providerHelper.getGenericData(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
|
||||
|
||||
Intent queryIntent = new Intent(getActivity(), ImportKeysActivity.class);
|
||||
queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT);
|
||||
queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint);
|
||||
|
||||
startActivityForResult(queryIntent, 0);
|
||||
}
|
||||
|
||||
private void certify(Uri dataUri) {
|
||||
Intent signIntent = new Intent(getActivity(), CertifyKeyActivity.class);
|
||||
signIntent.setData(dataUri);
|
||||
startActivity(signIntent);
|
||||
startActivityForResult(signIntent, 0);
|
||||
}
|
||||
|
||||
private void editKey(Uri dataUri) {
|
||||
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
|
||||
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
|
||||
// editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
|
||||
// startActivityForResult(editIntent, 0);
|
||||
startActivity(editIntent);
|
||||
startActivityForResult(editIntent, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,7 +20,14 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
@ -33,8 +40,6 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
@ -44,9 +49,10 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -65,6 +71,7 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
private View mKeyClipboardButton;
|
||||
private View mNfcHelpButton;
|
||||
private View mNfcPrefsButton;
|
||||
private View mKeyUploadButton;
|
||||
|
||||
ProviderHelper mProviderHelper;
|
||||
|
||||
@ -89,6 +96,7 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
|
||||
mNfcHelpButton = view.findViewById(R.id.view_key_action_nfc_help);
|
||||
mNfcPrefsButton = view.findViewById(R.id.view_key_action_nfc_prefs);
|
||||
mKeyUploadButton = view.findViewById(R.id.view_key_action_upload);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
mNfcPrefsButton.setVisibility(View.VISIBLE);
|
||||
@ -139,6 +147,12 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
showNfcPrefs();
|
||||
}
|
||||
});
|
||||
mKeyUploadButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
uploadToKeyserver();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
@ -152,7 +166,7 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(data);
|
||||
if(!toClipboard){
|
||||
if (!toClipboard) {
|
||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
} else {
|
||||
content = fingerprint;
|
||||
@ -171,13 +185,13 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
} else {
|
||||
message = getResources().getString(R.string.key_copied_to_clipboard);
|
||||
}
|
||||
AppMsg.makeText(getActivity(), message, AppMsg.STYLE_INFO).show();
|
||||
Notify.showNotify(getActivity(), message, Notify.Style.OK);
|
||||
} else {
|
||||
// Android will fail with android.os.TransactionTooLargeException if key is too big
|
||||
// see http://www.lonestarprod.com/?p=34
|
||||
if (content.length() >= 86389) {
|
||||
AppMsg.makeText(getActivity(), R.string.key_too_big_for_sharing,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.key_too_big_for_sharing,
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -195,19 +209,20 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "error processing key!", e);
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.error_key_processing, Notify.Style.ERROR);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "error processing key!", e);
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.error_key_processing, Notify.Style.ERROR);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.error_key_not_found, Notify.Style.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void showQrCodeDialog() {
|
||||
ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(mDataUri);
|
||||
dialog.show(ViewKeyShareFragment.this.getActivity().getSupportFragmentManager(), "shareQrCodeDialog");
|
||||
Intent qrCodeIntent = new Intent(getActivity(), QrCodeActivity.class);
|
||||
qrCodeIntent.setData(mDataUri);
|
||||
startActivity(qrCodeIntent);
|
||||
}
|
||||
|
||||
private void showNfcHelpDialog() {
|
||||
@ -292,10 +307,7 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
|
||||
mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
|
||||
|
||||
String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
mFingerprintQrCode.setImageBitmap(
|
||||
QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE)
|
||||
);
|
||||
loadQrCode(fingerprint);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -311,4 +323,42 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
*/
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load QR Code asynchronously and with a fade in animation
|
||||
*
|
||||
* @param fingerprint
|
||||
*/
|
||||
private void loadQrCode(final String fingerprint) {
|
||||
AsyncTask<Void, Void, Bitmap> loadTask =
|
||||
new AsyncTask<Void, Void, Bitmap>() {
|
||||
protected Bitmap doInBackground(Void... unused) {
|
||||
String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
return QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE);
|
||||
}
|
||||
|
||||
protected void onPostExecute(Bitmap qrCode) {
|
||||
mFingerprintQrCode.setImageBitmap(qrCode);
|
||||
|
||||
// Transition drawable with a transparent drawable and the final bitmap
|
||||
final TransitionDrawable td =
|
||||
new TransitionDrawable(new Drawable[]{
|
||||
new ColorDrawable(Color.TRANSPARENT),
|
||||
new BitmapDrawable(getResources(), qrCode)
|
||||
});
|
||||
|
||||
mFingerprintQrCode.setImageDrawable(td);
|
||||
td.startTransition(200);
|
||||
}
|
||||
};
|
||||
|
||||
loadTask.execute();
|
||||
}
|
||||
|
||||
private void uploadToKeyserver() {
|
||||
Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class);
|
||||
uploadIntent.setData(mDataUri);
|
||||
startActivityForResult(uploadIntent, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,20 +30,21 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class ImportKeysListLoader
|
||||
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
||||
|
||||
public static class FileHasNoContent extends Exception {
|
||||
|
||||
public static class NoValidKeysException extends Exception {
|
||||
}
|
||||
|
||||
public static class NonPgpPart extends Exception {
|
||||
public static class NonPgpPartException extends Exception {
|
||||
private int mCount;
|
||||
|
||||
public NonPgpPart(int count) {
|
||||
public NonPgpPartException(int count) {
|
||||
this.mCount = count;
|
||||
}
|
||||
|
||||
@ -67,7 +68,6 @@ public class ImportKeysListLoader
|
||||
|
||||
@Override
|
||||
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
|
||||
|
||||
// This has already been loaded! nvm any further, just return
|
||||
if (mEntryListWrapper != null) {
|
||||
return mEntryListWrapper;
|
||||
@ -119,9 +119,6 @@ public class ImportKeysListLoader
|
||||
* @return
|
||||
*/
|
||||
private void generateListOfKeyrings(InputData inputData) {
|
||||
|
||||
boolean isEmpty = true;
|
||||
|
||||
PositionAwareInputStream progressIn = new PositionAwareInputStream(
|
||||
inputData.getInputStream());
|
||||
|
||||
@ -130,27 +127,23 @@ public class ImportKeysListLoader
|
||||
// armor blocks
|
||||
BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
|
||||
try {
|
||||
|
||||
// read all available blocks... (asc files can contain many blocks with BEGIN END)
|
||||
while (bufferedInput.available() > 0) {
|
||||
// TODO: deal with non-keyring objects?
|
||||
List<UncachedKeyRing> rings = UncachedKeyRing.fromStream(bufferedInput);
|
||||
for(UncachedKeyRing key : rings) {
|
||||
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), key);
|
||||
mData.add(item);
|
||||
mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(key.getEncoded()));
|
||||
isEmpty = false;
|
||||
}
|
||||
// parse all keyrings
|
||||
Iterator<UncachedKeyRing> it = UncachedKeyRing.fromStream(bufferedInput);
|
||||
while (it.hasNext()) {
|
||||
UncachedKeyRing ring = it.next();
|
||||
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), ring);
|
||||
mData.add(item);
|
||||
mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(ring.getEncoded()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Exception on parsing key file!", e);
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e);
|
||||
|
||||
if (isEmpty) {
|
||||
Log.e(Constants.TAG, "File has no content!", new FileHasNoContent());
|
||||
NoValidKeysException e1 = new NoValidKeysException();
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>
|
||||
(mData, new FileHasNoContent());
|
||||
(mData, e1);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Other Exception on parsing key file!", e);
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -156,7 +158,10 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
|
||||
if (isRevoked) {
|
||||
// set revocation icon (can this even be primary?)
|
||||
vVerified.setImageResource(R.drawable.key_certify_revoke);
|
||||
vVerified.setImageResource(R.drawable.status_signature_revoked_cutout);
|
||||
vVerified.setColorFilter(
|
||||
mContext.getResources().getColor(R.color.bg_gray),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
|
||||
// disable and strike through text for revoked user ids
|
||||
vName.setEnabled(false);
|
||||
@ -170,22 +175,33 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
vAddress.setEnabled(true);
|
||||
vComment.setEnabled(true);
|
||||
|
||||
// verified: has been verified
|
||||
// isPrimary: show small star icon for primary user ids
|
||||
int verified = cursor.getInt(INDEX_VERIFIED);
|
||||
switch (verified) {
|
||||
if (isPrimary) {
|
||||
vName.setTypeface(null, Typeface.BOLD);
|
||||
vAddress.setTypeface(null, Typeface.BOLD);
|
||||
} else {
|
||||
vName.setTypeface(null, Typeface.NORMAL);
|
||||
vAddress.setTypeface(null, Typeface.NORMAL);
|
||||
}
|
||||
|
||||
int isVerified = cursor.getInt(INDEX_VERIFIED);
|
||||
switch (isVerified) {
|
||||
case Certs.VERIFIED_SECRET:
|
||||
vVerified.setImageResource(isPrimary
|
||||
? R.drawable.key_certify_primary_ok_depth0
|
||||
: R.drawable.key_certify_ok_depth0);
|
||||
vVerified.setImageResource(R.drawable.status_signature_verified_cutout);
|
||||
vVerified.setColorFilter(
|
||||
mContext.getResources().getColor(R.color.android_green_dark),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
break;
|
||||
case Certs.VERIFIED_SELF:
|
||||
vVerified.setImageResource(isPrimary
|
||||
? R.drawable.key_certify_primary_ok_self
|
||||
: R.drawable.key_certify_ok_self);
|
||||
vVerified.setImageResource(R.drawable.status_signature_unverified_cutout);
|
||||
vVerified.setColorFilter(
|
||||
mContext.getResources().getColor(R.color.bg_gray),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
break;
|
||||
default:
|
||||
vVerified.setImageResource(R.drawable.key_certify_error);
|
||||
vVerified.setImageResource(R.drawable.status_signature_invalid_cutout);
|
||||
vVerified.setColorFilter(
|
||||
mContext.getResources().getColor(R.color.android_red_dark),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ import android.support.v4.app.FragmentActivity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
@ -44,8 +44,8 @@ import android.widget.Toast;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
@ -62,7 +62,6 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
|
||||
private Messenger mMessenger;
|
||||
private EditText mPassphraseEditText;
|
||||
private boolean mCanKB;
|
||||
|
||||
/**
|
||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||
@ -102,10 +101,10 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
// check if secret key has a passphrase
|
||||
if (!(secretKeyId == Constants.key.symmetric || secretKeyId == Constants.key.none)) {
|
||||
try {
|
||||
if (!new ProviderHelper(context).getWrappedSecretKeyRing(secretKeyId).hasPassphrase()) {
|
||||
if (!new ProviderHelper(context).getCanonicalizedSecretKeyRing(secretKeyId).hasPassphrase()) {
|
||||
throw new PgpGeneralException("No passphrase! No passphrase dialog needed!");
|
||||
}
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
throw new PgpGeneralException("Error: Key not found!", e);
|
||||
}
|
||||
}
|
||||
@ -120,11 +119,6 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@ -138,7 +132,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
|
||||
alert.setTitle(R.string.title_authentication);
|
||||
|
||||
final WrappedSecretKeyRing secretRing;
|
||||
final CanonicalizedSecretKeyRing secretRing;
|
||||
String userId;
|
||||
|
||||
if (secretKeyId == Constants.key.symmetric || secretKeyId == Constants.key.none) {
|
||||
@ -147,7 +141,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
} else {
|
||||
try {
|
||||
ProviderHelper helper = new ProviderHelper(activity);
|
||||
secretRing = helper.getWrappedSecretKeyRing(secretKeyId);
|
||||
secretRing = helper.getCanonicalizedSecretKeyRing(secretKeyId);
|
||||
// yes the inner try/catch block is necessary, otherwise the final variable
|
||||
// above can't be statically verified to have been set in all cases because
|
||||
// the catch clause doesn't return.
|
||||
@ -165,7 +159,6 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
mCanKB = false;
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
@ -190,7 +183,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
// Early breakout if we are dealing with a symmetric key
|
||||
if (secretRing == null) {
|
||||
PassphraseCacheService.addCachedPassphrase(activity, Constants.key.symmetric,
|
||||
passphrase, getString(R.string.passp_cache_notif_pwd));
|
||||
passphrase, getString(R.string.passp_cache_notif_pwd));
|
||||
// also return passphrase back to activity
|
||||
Bundle data = new Bundle();
|
||||
data.putString(MESSAGE_DATA_PASSPHRASE, passphrase);
|
||||
@ -198,9 +191,9 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
return;
|
||||
}
|
||||
|
||||
WrappedSecretKey unlockedSecretKey = null;
|
||||
CanonicalizedSecretKey unlockedSecretKey = null;
|
||||
|
||||
for(WrappedSecretKey clickSecretKey : secretRing.secretKeyIterator()) {
|
||||
for (CanonicalizedSecretKey clickSecretKey : secretRing.secretKeyIterator()) {
|
||||
try {
|
||||
boolean unlocked = clickSecretKey.unlock(passphrase);
|
||||
if (unlocked) {
|
||||
@ -232,9 +225,9 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
|
||||
try {
|
||||
PassphraseCacheService.addCachedPassphrase(activity, masterKeyId, passphrase,
|
||||
secretRing.getPrimaryUserIdWithFallback());
|
||||
} catch(PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "adding of a passhrase failed", e);
|
||||
secretRing.getPrimaryUserIdWithFallback());
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "adding of a passphrase failed", e);
|
||||
}
|
||||
|
||||
if (unlockedSecretKey.getKeyId() != masterKeyId) {
|
||||
@ -258,22 +251,32 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
}
|
||||
});
|
||||
|
||||
mCanKB = true;
|
||||
// Hack to open keyboard.
|
||||
// This is the only method that I found to work across all Android versions
|
||||
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
||||
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
|
||||
// * opening in onActivityCreated does not work on Android 4.4
|
||||
mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
mPassphraseEditText.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
mPassphraseEditText.requestFocus();
|
||||
|
||||
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
|
||||
return alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle arg0) {
|
||||
super.onActivityCreated(arg0);
|
||||
if (mCanKB) {
|
||||
// request focus and open soft keyboard
|
||||
mPassphraseEditText.requestFocus();
|
||||
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
super.onCancel(dialog);
|
||||
@ -282,6 +285,27 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
Log.d(Constants.TAG, "onDismiss");
|
||||
|
||||
// hide keyboard on dismiss
|
||||
hideKeyboard();
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager inputManager = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
//check if no view has focus:
|
||||
View v = getActivity().getCurrentFocus();
|
||||
if (v == null)
|
||||
return;
|
||||
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the "done" button on the soft keyboard with the okay button in the view
|
||||
*/
|
||||
|
@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.dialog;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
@ -32,6 +33,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
@ -164,18 +166,50 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
|
||||
}
|
||||
});
|
||||
|
||||
// Hack to open keyboard.
|
||||
// This is the only method that I found to work across all Android versions
|
||||
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
||||
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
|
||||
// * opening in onActivityCreated does not work on Android 4.4
|
||||
mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
mPassphraseEditText.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
mPassphraseEditText.requestFocus();
|
||||
|
||||
mPassphraseAgainEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||
mPassphraseAgainEditText.setOnEditorActionListener(this);
|
||||
|
||||
return alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle arg0) {
|
||||
super.onActivityCreated(arg0);
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
|
||||
// request focus and open soft keyboard
|
||||
mPassphraseEditText.requestFocus();
|
||||
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
// hide keyboard on dismiss
|
||||
hideKeyboard();
|
||||
}
|
||||
|
||||
mPassphraseAgainEditText.setOnEditorActionListener(this);
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager inputManager = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
//check if no view has focus:
|
||||
View v = getActivity().getCurrentFocus();
|
||||
if (v == null)
|
||||
return;
|
||||
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012-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.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||
|
||||
public class ShareQrCodeDialogFragment extends DialogFragment {
|
||||
private static final String ARG_KEY_URI = "uri";
|
||||
|
||||
private ImageView mImage;
|
||||
private TextView mText;
|
||||
|
||||
private static final int QR_CODE_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*/
|
||||
public static ShareQrCodeDialogFragment newInstance(Uri dataUri) {
|
||||
ShareQrCodeDialogFragment frag = new ShareQrCodeDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_KEY_URI, dataUri);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
Uri dataUri = getArguments().getParcelable(ARG_KEY_URI);
|
||||
|
||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(getActivity());
|
||||
alert.setTitle(R.string.share_qr_code_dialog_title);
|
||||
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.share_qr_code_dialog, null);
|
||||
alert.setView(view);
|
||||
|
||||
mImage = (ImageView) view.findViewById(R.id.share_qr_code_dialog_image);
|
||||
mText = (TextView) view.findViewById(R.id.share_qr_code_dialog_text);
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(getActivity());
|
||||
String content;
|
||||
try {
|
||||
alert.setPositiveButton(R.string.btn_okay, null);
|
||||
|
||||
byte[] blob = (byte[]) providerHelper.getGenericData(
|
||||
KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
if (blob == null) {
|
||||
Log.e(Constants.TAG, "key not found!");
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||
return null;
|
||||
}
|
||||
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
|
||||
mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint);
|
||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
setQrCode(content);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||
return null;
|
||||
}
|
||||
|
||||
return alert.show();
|
||||
}
|
||||
|
||||
private void setQrCode(String data) {
|
||||
mImage.setImageBitmap(QrCodeUtils.getQRCodeBitmap(data, QR_CODE_SIZE));
|
||||
}
|
||||
|
||||
}
|
@ -29,7 +29,7 @@ import com.github.johnpersano.supertoasts.SuperToast;
|
||||
*/
|
||||
public class Notify {
|
||||
|
||||
public static enum Style {OK, WARN, ERROR}
|
||||
public static enum Style {OK, WARN, INFO, ERROR}
|
||||
|
||||
/**
|
||||
* Shows a simple in-layout notification with the CharSequence given as parameter
|
||||
|
@ -46,13 +46,13 @@ public class ProgressScaler implements Progressable {
|
||||
|
||||
public void setProgress(int resourceId, int progress, int max) {
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(resourceId, progress, mMax);
|
||||
mWrapped.setProgress(resourceId, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int progress, int max) {
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(progress, max);
|
||||
mWrapped.setProgress(mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromXDelta="-100%"
|
||||
android:toXDelta="0"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:duration="500" />
|
||||
</set>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromXDelta="100%"
|
||||
android:toXDelta="0"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:duration="500" />
|
||||
</set>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="-100%"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:duration="500" />
|
||||
</set>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="100%"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:duration="500" />
|
||||
</set>
|
16
OpenKeychain/src/main/res/anim/qr_code_zoom_enter.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator">
|
||||
<scale
|
||||
android:fromXScale="0.5"
|
||||
android:toXScale="1.0"
|
||||
android:fromYScale="0.5"
|
||||
android:toYScale="1.0"
|
||||
android:pivotX="50%p"
|
||||
android:pivotY="50%p"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
<alpha
|
||||
android:fromAlpha="0"
|
||||
android:toAlpha="1.0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
17
OpenKeychain/src/main/res/anim/qr_code_zoom_exit.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:zAdjustment="top">
|
||||
<scale
|
||||
android:fromXScale="1.0"
|
||||
android:toXScale="0.5"
|
||||
android:fromYScale="1.0"
|
||||
android:toYScale="0.5"
|
||||
android:pivotX="50%p"
|
||||
android:pivotY="50%p"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
<alpha
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
BIN
OpenKeychain/src/main/res/drawable-hdpi/create_key_robot.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 898 B |
Before Width: | Height: | Size: 900 B |
Before Width: | Height: | Size: 757 B |
Before Width: | Height: | Size: 1.2 KiB |
BIN
OpenKeychain/src/main/res/drawable-hdpi/status_lock_closed.png
Normal file
After Width: | Height: | Size: 675 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/status_lock_error.png
Normal file
After Width: | Height: | Size: 748 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/status_lock_open.png
Normal file
After Width: | Height: | Size: 675 B |
After Width: | Height: | Size: 723 B |
After Width: | Height: | Size: 789 B |
After Width: | Height: | Size: 528 B |
After Width: | Height: | Size: 444 B |
After Width: | Height: | Size: 714 B |
After Width: | Height: | Size: 861 B |
After Width: | Height: | Size: 640 B |
After Width: | Height: | Size: 740 B |
After Width: | Height: | Size: 536 B |
After Width: | Height: | Size: 813 B |
After Width: | Height: | Size: 541 B |
After Width: | Height: | Size: 695 B |
Before Width: | Height: | Size: 652 B |
Before Width: | Height: | Size: 646 B |
Before Width: | Height: | Size: 507 B |
Before Width: | Height: | Size: 663 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/create_key_robot.png
Normal file
After Width: | Height: | Size: 1.7 KiB |