diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index 0288d2937..dd2feb825 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -231,7 +231,7 @@ public class PgpKeyOperationTest { ring.getPublicKey().getCreationTime().after(new Date(new Date().getTime()-1000*120))); Assert.assertNull("key ring should not expire", - ring.getPublicKey().getExpiryTime()); + ring.getPublicKey().getUnsafeExpiryTimeForTesting()); Assert.assertEquals("first (master) key can certify", KeyFlags.CERTIFY_OTHER, (long) subkeys.get(0).getKeyUsage()); @@ -342,9 +342,9 @@ public class PgpKeyOperationTest { Assert.assertNotNull("new key is not null", newKey); Assert.assertNotNull("added key must have an expiry date", - newKey.getExpiryTime()); + newKey.getUnsafeExpiryTimeForTesting()); Assert.assertEquals("added key must have expected expiry date", - expiry, newKey.getExpiryTime().getTime()/1000); + expiry, newKey.getUnsafeExpiryTimeForTesting().getTime()/1000); Assert.assertEquals("added key must have expected flags", flags, (long) newKey.getKeyUsage()); Assert.assertEquals("added key must have expected bitsize", @@ -403,9 +403,9 @@ public class PgpKeyOperationTest { ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID()); Assert.assertNotNull("modified key must have an expiry date", - modified.getPublicKey(keyId).getExpiryTime()); + modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting()); Assert.assertEquals("modified key must have expected expiry date", - expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000); + expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000); Assert.assertEquals("modified key must have same flags as before", ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage()); } @@ -417,9 +417,9 @@ public class PgpKeyOperationTest { modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB); Assert.assertNotNull("modified key must have an expiry date", - modified.getPublicKey(keyId).getExpiryTime()); + modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting()); Assert.assertEquals("modified key must have expected expiry date", - expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000); + expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000); Assert.assertEquals("modified key must have same flags as before", ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage()); } @@ -443,9 +443,9 @@ public class PgpKeyOperationTest { Assert.assertEquals("modified key must have expected flags", flags, (long) modified.getPublicKey(keyId).getKeyUsage()); Assert.assertNotNull("key must retain its expiry", - modified.getPublicKey(keyId).getExpiryTime()); + modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting()); Assert.assertEquals("key expiry must be unchanged", - expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000); + expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000); } { // expiry of 0 should be "no expiry" @@ -463,7 +463,7 @@ public class PgpKeyOperationTest { Assert.assertEquals("signature must have been created by master key", ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID()); - Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getExpiryTime()); + Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting()); } { // a past expiry should fail @@ -517,9 +517,9 @@ public class PgpKeyOperationTest { PacketTags.SIGNATURE, onlyB.get(1).tag); Assert.assertNotNull("modified key must have an expiry date", - modified.getPublicKey().getExpiryTime()); + modified.getPublicKey().getUnsafeExpiryTimeForTesting()); Assert.assertEquals("modified key must have expected expiry date", - expiry, modified.getPublicKey().getExpiryTime().getTime() / 1000); + expiry, modified.getPublicKey().getUnsafeExpiryTimeForTesting().getTime() / 1000); Assert.assertEquals("modified key must have same flags as before", ring.getPublicKey().getKeyUsage(), modified.getPublicKey().getKeyUsage()); } @@ -531,9 +531,9 @@ public class PgpKeyOperationTest { modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB); Assert.assertNotNull("modified key must have an expiry date", - modified.getPublicKey(keyId).getExpiryTime()); + modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting()); Assert.assertEquals("modified key must have expected expiry date", - expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000); + expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime() / 1000); Assert.assertEquals("modified key must have same flags as before", ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage()); } @@ -547,17 +547,29 @@ public class PgpKeyOperationTest { Assert.assertEquals("modified key must have expected flags", flags, (long) modified.getPublicKey(keyId).getKeyUsage()); Assert.assertNotNull("key must retain its expiry", - modified.getPublicKey(keyId).getExpiryTime()); + modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting()); Assert.assertEquals("key expiry must be unchanged", - expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000); + expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000); } { // expiry of 0 should be "no expiry" + + // even if there is a non-expiring user id while all others are revoked, it doesn't count! + // for this purpose we revoke one while they still have expiry times + parcel.reset(); + parcel.mRevokeUserIds.add("aloe"); + modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB); + parcel.reset(); parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, 0L)); modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB); - Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getExpiryTime()); + // for this check, it is relevant that we DON'T use the unsafe one! + Assert.assertNull("key must not expire anymore", + modified.canonicalize(new OperationLog(), 0).getPublicKey().getExpiryTime()); + // make sure the unsafe one behaves incorrectly as expected + Assert.assertNotNull("unsafe expiry must yield wrong result from revoked user id", + modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting()); } { // if we revoke everything, nothing is left to properly sign... @@ -609,7 +621,7 @@ public class PgpKeyOperationTest { ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID()); Assert.assertTrue("subkey must actually be revoked", - modified.getPublicKey().isRevoked()); + modified.getPublicKey().isMaybeRevoked()); } @@ -653,7 +665,7 @@ public class PgpKeyOperationTest { ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID()); Assert.assertTrue("subkey must actually be revoked", - modified.getPublicKey(keyId).isRevoked()); + modified.getPublicKey(keyId).isMaybeRevoked()); } { // re-add second subkey @@ -691,7 +703,7 @@ public class PgpKeyOperationTest { ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID()); Assert.assertFalse("subkey must no longer be revoked", - modified.getPublicKey(keyId).isRevoked()); + modified.getPublicKey(keyId).isMaybeRevoked()); Assert.assertEquals("subkey must have the same usage flags as before", flags, (long) modified.getPublicKey(keyId).getKeyUsage()); diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 29b748410..20e618320 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -451,7 +451,7 @@ android:value=".ui.MainActivity" /> + + + + + + + + - - - - - - - - diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 591408c8b..79065604a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -294,8 +294,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mKeyId = key.getKeyId(); mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(mKeyId); - mRevoked = key.isRevoked(); - mExpired = key.isExpired(); + mRevoked = key.isMaybeRevoked(); + mExpired = key.isMaybeExpired(); mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint()); mBitStrength = key.getBitStrength(); mCurveOid = key.getCurveOid(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index ec26d4bbe..f79900aab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -586,6 +586,15 @@ public abstract class OperationResult implements Parcelable { MSG_DC_TRAIL_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_trail_unknown), MSG_DC_UNLOCKING (LogLevel.INFO, R.string.msg_dc_unlocking), + // verify signed literal data + MSG_VL (LogLevel.INFO, R.string.msg_vl), + MSG_VL_ERROR_MISSING_SIGLIST (LogLevel.ERROR, R.string.msg_vl_error_no_siglist), + MSG_VL_ERROR_MISSING_LITERAL (LogLevel.ERROR, R.string.msg_vl_error_missing_literal), + MSG_VL_ERROR_MISSING_KEY (LogLevel.ERROR, R.string.msg_vl_error_wrong_key), + MSG_VL_CLEAR_SIGNATURE_CHECK (LogLevel.DEBUG, R.string.msg_vl_clear_signature_check), + MSG_VL_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_vl_error_integrity_check), + MSG_VL_OK (LogLevel.OK, R.string.msg_vl_ok), + // signencrypt MSG_SE (LogLevel.START, R.string.msg_se), MSG_SE_INPUT_BYTES (LogLevel.INFO, R.string.msg_se_input_bytes), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index bbf136dac..4adacaf23 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -79,9 +79,8 @@ public abstract class CanonicalizedKeyRing extends KeyRing { public boolean isExpired() { // Is the master key expired? - Date creationDate = getRing().getPublicKey().getCreationTime(); - Date expiryDate = getRing().getPublicKey().getValidSeconds() > 0 - ? new Date(creationDate.getTime() + getRing().getPublicKey().getValidSeconds() * 1000) : null; + Date creationDate = getPublicKey().getCreationTime(); + Date expiryDate = getPublicKey().getExpiryTime(); Date now = new Date(); return creationDate.after(now) || (expiryDate != null && expiryDate.before(now)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index b026d9257..303070333 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -20,8 +20,16 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; /** Wrapper for a PGPPublicKey. * @@ -100,6 +108,72 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { return false; } + public boolean isRevoked() { + return mPublicKey.getSignaturesOfType(isMasterKey() + ? PGPSignature.KEY_REVOCATION + : PGPSignature.SUBKEY_REVOCATION).hasNext(); + } + + public boolean isExpired () { + Date expiry = getExpiryTime(); + return expiry != null && expiry.before(new Date()); + } + + public long getValidSeconds() { + + long seconds; + + // the getValidSeconds method is unreliable for master keys. we need to iterate all + // user ids, then use the most recent certification from a non-revoked user id + if (isMasterKey()) { + Date latestCreation = null; + seconds = 0; + + for (byte[] rawUserId : getUnorderedRawUserIds()) { + Iterator sigs = getSignaturesForRawId(rawUserId); + + // there is always a certification, so this call is safe + WrappedSignature sig = sigs.next(); + + // we know a user id has at most two sigs: one certification, one revocation. + // if the sig is a revocation, or there is another sig (which is a revocation), + // the data in this uid is not relevant + if (sig.isRevocation() || sigs.hasNext()) { + continue; + } + + // this is our revocation, UNLESS there is a newer certificate! + if (latestCreation == null || latestCreation.before(sig.getCreationTime())) { + latestCreation = sig.getCreationTime(); + seconds = sig.getKeyExpirySeconds(); + } + } + } else { + seconds = mPublicKey.getValidSeconds(); + } + + return seconds; + } + + public Date getExpiryTime() { + long seconds = getValidSeconds(); + + if (seconds > Integer.MAX_VALUE) { + Log.e(Constants.TAG, "error, expiry time too large"); + return null; + } + if (seconds == 0) { + // no expiry + return null; + } + Date creationDate = getCreationTime(); + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.SECOND, (int) seconds); + + return calendar.getTime(); + } + /** Same method as superclass, but we make it public. */ public Integer getKeyUsage() { return super.getKeyUsage(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index ed4715681..46defebf7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -104,8 +104,8 @@ public class OpenPgpSignatureResultBuilder { setUserIds(signingRing.getUnorderedUserIds()); // either master key is expired/revoked or this specific subkey is expired/revoked - setKeyExpired(signingRing.isExpired() || signingKey.isExpired()); - setKeyRevoked(signingRing.isRevoked() || signingKey.isRevoked()); + setKeyExpired(signingRing.isExpired() || signingKey.isMaybeExpired()); + setKeyRevoked(signingRing.isRevoked() || signingKey.isMaybeRevoked()); } public OpenPgpSignatureResult build() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 2ee923e42..2ba0b6231 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -22,6 +22,7 @@ import android.content.Context; import android.webkit.MimeTypeMap; import org.openintents.openpgp.OpenPgpMetadata; +import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.bcpg.ArmoredInputStream; import org.spongycastle.openpgp.PGPCompressedData; import org.spongycastle.openpgp.PGPEncryptedData; @@ -46,6 +47,10 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -83,6 +88,8 @@ public class PgpDecryptVerify extends BaseOperation { private boolean mDecryptMetadataOnly; private byte[] mDecryptedSessionKey; private byte[] mDetachedSignature; + private String mRequiredSignerFingerprint; + private boolean mSignedLiteralData; protected PgpDecryptVerify(Builder builder) { super(builder.mContext, builder.mProviderHelper, builder.mProgressable); @@ -97,6 +104,8 @@ public class PgpDecryptVerify extends BaseOperation { this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly; this.mDecryptedSessionKey = builder.mDecryptedSessionKey; this.mDetachedSignature = builder.mDetachedSignature; + this.mSignedLiteralData = builder.mSignedLiteralData; + this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint; } public static class Builder { @@ -114,6 +123,8 @@ public class PgpDecryptVerify extends BaseOperation { private boolean mDecryptMetadataOnly = false; private byte[] mDecryptedSessionKey = null; private byte[] mDetachedSignature = null; + private String mRequiredSignerFingerprint = null; + private boolean mSignedLiteralData = false; public Builder(Context context, ProviderHelper providerHelper, Progressable progressable, @@ -125,6 +136,24 @@ public class PgpDecryptVerify extends BaseOperation { mOutStream = outStream; } + /** + * This is used when verifying signed literals to check that they are signed with + * the required key + */ + public Builder setRequiredSignerFingerprint(String fingerprint) { + mRequiredSignerFingerprint = fingerprint; + return this; + } + + /** + * This is to force a mode where the message is just the signature key id and + * then a literal data packet; used in Keybase.io proofs + */ + public Builder setSignedLiteralData(boolean signedLiteralData) { + mSignedLiteralData = signedLiteralData; + return this; + } + public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) { mAllowSymmetricDecryption = allowSymmetricDecryption; return this; @@ -189,7 +218,9 @@ public class PgpDecryptVerify extends BaseOperation { // it is ascii armored Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine()); - if (aIn.isClearText()) { + if (mSignedLiteralData) { + return verifySignedLiteralData(aIn, 0); + } else if (aIn.isClearText()) { // a cleartext signature, verify it with the other method return verifyCleartextSignature(aIn, 0); } else { @@ -213,6 +244,136 @@ public class PgpDecryptVerify extends BaseOperation { } } + /** + * Verify Keybase.io style signed literal data + */ + private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent) throws IOException, PGPException { + OperationLog log = new OperationLog(); + log.add(LogType.MSG_VL, indent); + + // thinking that the proof-fetching operation is going to take most of the time + updateProgress(R.string.progress_reading_data, 75, 100); + + JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); + Object o = pgpF.nextObject(); + if (o instanceof PGPCompressedData) { + log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1); + + pgpF = new JcaPGPObjectFactory(((PGPCompressedData) o).getDataStream()); + o = pgpF.nextObject(); + updateProgress(R.string.progress_decompressing_data, 80, 100); + } + + // all we want to see is a OnePassSignatureList followed by LiteralData + if (!(o instanceof PGPOnePassSignatureList)) { + log.add(LogType.MSG_VL_ERROR_MISSING_SIGLIST, indent); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) o; + + // go through all signatures (should be just one), make sure we have + // the key and it matches the one we’re looking for + CanonicalizedPublicKeyRing signingRing = null; + CanonicalizedPublicKey signingKey = null; + int signatureIndex = -1; + for (int i = 0; i < sigList.size(); ++i) { + try { + long sigKeyId = sigList.get(i).getKeyID(); + signingRing = mProviderHelper.getCanonicalizedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) + ); + signingKey = signingRing.getPublicKey(sigKeyId); + signatureIndex = i; + } catch (ProviderHelper.NotFoundException e) { + Log.d(Constants.TAG, "key not found, trying next signature..."); + } + } + + // there has to be a key, and it has to be the right one + if (signingKey == null) { + log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent); + Log.d(Constants.TAG, "Failed to find key in signed-literal message"); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint()); + if (!(mRequiredSignerFingerprint.equals(fingerprint))) { + log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent); + Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint + + " got " + fingerprint + "!"); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); + + PGPOnePassSignature signature = sigList.get(signatureIndex); + signatureResultBuilder.initValid(signingRing, signingKey); + + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); + + o = pgpF.nextObject(); + + if (!(o instanceof PGPLiteralData)) { + log.add(LogType.MSG_VL_ERROR_MISSING_LITERAL, indent); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + PGPLiteralData literalData = (PGPLiteralData) o; + + log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1); + updateProgress(R.string.progress_decrypting, 85, 100); + + InputStream dataIn = literalData.getInputStream(); + + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = dataIn.read(buffer)) > 0) { + mOutStream.write(buffer, 0, length); + signature.update(buffer, 0, length); + } + + updateProgress(R.string.progress_verifying_signature, 95, 100); + log.add(LogType.MSG_VL_CLEAR_SIGNATURE_CHECK, indent + 1); + + PGPSignatureList signatureList = (PGPSignatureList) pgpF.nextObject(); + PGPSignature messageSignature = signatureList.get(signatureIndex); + + // these are not cleartext signatures! + // TODO: what about binary signatures? + signatureResultBuilder.setSignatureOnly(false); + + // Verify signature and check binding signatures + boolean validSignature = signature.verify(messageSignature); + if (validSignature) { + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1); + } else { + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1); + } + signatureResultBuilder.setValidSignature(validSignature); + + OpenPgpSignatureResult signatureResult = signatureResultBuilder.build(); + + if (signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED + && signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED) { + log.add(LogType.MSG_VL_ERROR_INTEGRITY_CHECK, indent); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + updateProgress(R.string.progress_done, 100, 100); + + log.add(LogType.MSG_VL_OK, indent); + + // Return a positive result, with metadata and verification info + DecryptVerifyResult result = + new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setSignatureResult(signatureResult); + return result; + } + + /** * Decrypt and/or verifies binary or ascii armored pgp */ @@ -672,6 +833,7 @@ public class PgpDecryptVerify extends BaseOperation { // If no valid signature is present: // Handle missing integrity protection like failed integrity protection! // The MDC packet can be stripped by an attacker! + Log.d(Constants.TAG, "MDC fail"); if (!signatureResultBuilder.isValidSignature()) { log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index aebb52a03..1a251eb79 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -439,8 +439,8 @@ public class PgpKeyOperation { // since this is the master key, this contains at least CERTIFY_OTHER PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER; - long masterKeyExpiry = masterPublicKey.getValidSeconds() == 0L ? 0L : - masterPublicKey.getCreationTime().getTime() / 1000 + masterPublicKey.getValidSeconds(); + Date expiryTime = wsKR.getPublicKey().getExpiryTime(); + long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L; return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 0fe1ccdb6..9276cba10 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -50,7 +50,7 @@ public class UncachedPublicKey { } /** The revocation signature is NOT checked here, so this may be false! */ - public boolean isRevoked() { + public boolean isMaybeRevoked() { return mPublicKey.getSignaturesOfType(isMasterKey() ? PGPSignature.KEY_REVOCATION : PGPSignature.SUBKEY_REVOCATION).hasNext(); @@ -60,25 +60,8 @@ public class UncachedPublicKey { return mPublicKey.getCreationTime(); } - public Date getExpiryTime() { - long seconds = mPublicKey.getValidSeconds(); - if (seconds > Integer.MAX_VALUE) { - Log.e(Constants.TAG, "error, expiry time too large"); - return null; - } - if (seconds == 0) { - // no expiry - return null; - } - Date creationDate = getCreationTime(); - Calendar calendar = GregorianCalendar.getInstance(); - calendar.setTime(creationDate); - calendar.add(Calendar.SECOND, (int) seconds); - - return calendar.getTime(); - } - - public boolean isExpired() { + /** The revocation signature is NOT checked here, so this may be false! */ + public boolean isMaybeExpired() { Date creationDate = mPublicKey.getCreationTime(); Date expiryDate = mPublicKey.getValidSeconds() > 0 ? new Date(creationDate.getTime() + mPublicKey.getValidSeconds() * 1000) : null; @@ -358,4 +341,24 @@ public class UncachedPublicKey { return mCacheUsage; } + // this method relies on UNSAFE assumptions about the keyring, and should ONLY be used for + // TEST CASES!! + Date getUnsafeExpiryTimeForTesting () { + long valid = mPublicKey.getValidSeconds(); + + if (valid > Integer.MAX_VALUE) { + Log.e(Constants.TAG, "error, expiry time too large"); + return null; + } + if (valid == 0) { + // no expiry + return null; + } + Date creationDate = getCreationTime(); + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.SECOND, (int) valid); + + return calendar.getTime(); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index ade075d55..c6fad1a73 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -78,6 +78,10 @@ public class WrappedSignature { return mSig.getCreationTime(); } + public long getKeyExpirySeconds() { + return mSig.getHashedSubPackets().getKeyExpirationTime(); + } + public ArrayList getEmbeddedSignatures() { ArrayList sigs = new ArrayList<>(); if (!mSig.hasSubpackets()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 18efa2b80..d947ae053 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -473,7 +473,7 @@ public class ProviderHelper { item.selfCert = cert; item.isPrimary = cert.isPrimaryUserId(); } else { - item.isRevoked = true; + item.selfRevocation = cert; log(LogType.MSG_IP_UID_REVOKED); } continue; @@ -569,10 +569,11 @@ public class ProviderHelper { // NOTE self-certificates are already verified during canonicalization, // AND we know there is at most one cert plus at most one revocation + // AND the revocation only exists if there is no newer certification if (!cert.isRevocation()) { item.selfCert = cert; } else { - item.isRevoked = true; + item.selfRevocation = cert; log(LogType.MSG_IP_UAT_REVOKED); } continue; @@ -643,16 +644,21 @@ public class ProviderHelper { for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) { UserPacketItem item = uids.get(userIdRank); operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); - if (item.selfCert != null) { - // TODO get rid of "self verified" status? this cannot even happen anymore! - operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert, - selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); + + if (item.selfCert == null) { + throw new AssertionError("User ids MUST be self-certified at this point!!"); } - // don't bother with trusted certs if the uid is revoked, anyways - if (item.isRevoked) { + + if (item.selfRevocation != null) { + operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation, + Certs.VERIFIED_SELF)); + // don't bother with trusted certs if the uid is revoked, anyways continue; } + operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert, + selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); + // iterate over signatures for (int i = 0; i < item.trustedCerts.size() ; i++) { WrappedSignature sig = item.trustedCerts.valueAt(i); @@ -711,15 +717,16 @@ public class ProviderHelper { String userId; byte[] attributeData; boolean isPrimary = false; - boolean isRevoked = false; WrappedSignature selfCert; + WrappedSignature selfRevocation; LongSparseArray trustedCerts = new LongSparseArray<>(); @Override public int compareTo(UserPacketItem o) { // revoked keys always come last! - if (isRevoked != o.isRevoked) { - return isRevoked ? 1 : -1; + //noinspection DoubleNegation + if ( (selfRevocation != null) != (o.selfRevocation != null)) { + return selfRevocation != null ? 1 : -1; } // if one is a user id, but the other isn't, the user id always comes first. // we compare for null values here, so != is the correct operator! @@ -1353,7 +1360,7 @@ public class ProviderHelper { values.put(UserPackets.USER_ID, item.userId); values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData); values.put(UserPackets.IS_PRIMARY, item.isPrimary); - values.put(UserPackets.IS_REVOKED, item.isRevoked); + values.put(UserPackets.IS_REVOKED, item.selfRevocation != null); values.put(UserPackets.RANK, rank); Uri uri = UserPackets.buildUserIdsUri(masterKeyId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 72eec6955..d95701458 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -26,7 +26,13 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import com.textuality.keybase.lib.Proof; +import com.textuality.keybase.lib.prover.Prover; + +import org.json.JSONObject; +import org.spongycastle.openpgp.PGPUtil; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; @@ -44,6 +50,7 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; @@ -62,10 +69,19 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import de.measite.minidns.Client; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Question; +import de.measite.minidns.Record; +import de.measite.minidns.record.Data; +import de.measite.minidns.record.TXT; + /** * This Service contains all important long lasting operations for OpenKeychain. It receives Intents with * data from the activities or other apps, queues these intents, executes them, and stops itself @@ -82,6 +98,8 @@ public class KeychainIntentService extends IntentService implements Progressable public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY"; + public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF"; + public static final String ACTION_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA"; public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING"; @@ -106,6 +124,7 @@ public class KeychainIntentService extends IntentService implements Progressable // encrypt, decrypt, import export public static final String TARGET = "target"; public static final String SOURCE = "source"; + // possible targets: public static final int IO_BYTES = 1; public static final int IO_URI = 2; @@ -120,6 +139,10 @@ public class KeychainIntentService extends IntentService implements Progressable public static final String DECRYPT_PASSPHRASE = "passphrase"; public static final String DECRYPT_NFC_DECRYPTED_SESSION_KEY = "nfc_decrypted_session_key"; + // keybase proof + public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint"; + public static final String KEYBASE_PROOF = "keybase_proof"; + // save keyring public static final String EDIT_KEYRING_PARCEL = "save_parcel"; public static final String EDIT_KEYRING_PASSPHRASE = "passphrase"; @@ -240,7 +263,7 @@ public class KeychainIntentService extends IntentService implements Progressable break; } - case ACTION_DECRYPT_METADATA: + case ACTION_DECRYPT_METADATA: { try { /* Input */ @@ -271,7 +294,106 @@ public class KeychainIntentService extends IntentService implements Progressable } break; - case ACTION_DECRYPT_VERIFY: + } + case ACTION_VERIFY_KEYBASE_PROOF: { + + try { + Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF))); + setProgress(R.string.keybase_message_fetching_data, 0, 100); + + Prover prover = Prover.findProverFor(proof); + + if (prover == null) { + sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName()); + return; + } + + if (!prover.fetchProofData()) { + sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence)); + return; + } + String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT); + if (!prover.checkFingerprint(requiredFingerprint)) { + sendProofError(getString(R.string.keybase_key_mismatch)); + return; + } + + String domain = prover.dnsTxtCheckRequired(); + if (domain != null) { + DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT)); + if (dnsQuery == null) { + sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure)); + return; + } + Record[] records = dnsQuery.getAnswers(); + List> extents = new ArrayList>(); + for (Record r : records) { + Data d = r.getPayload(); + if (d instanceof TXT) { + extents.add(((TXT) d).getExtents()); + } + } + if (!prover.checkDnsTxt(extents)) { + sendProofError(prover.getLog(), null); + return; + } + } + + byte[] messageBytes = prover.getPgpMessage().getBytes(); + if (prover.rawMessageCheckRequired()) { + InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes)); + if (!prover.checkRawMessageBytes(messageByteStream)) { + sendProofError(prover.getLog(), null); + return; + } + } + + // kind of awkward, but this whole class wants to pull bytes out of “data” + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); + data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes); + + InputData inputData = createDecryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); + + PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( + this, new ProviderHelper(this), this, + inputData, outStream + ); + builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint); + + DecryptVerifyResult decryptVerifyResult = builder.build().execute(); + outStream.close(); + + if (!decryptVerifyResult.success()) { + OperationLog log = decryptVerifyResult.getLog(); + OperationResult.LogEntryParcel lastEntry = null; + for (OperationResult.LogEntryParcel entry : log) { + lastEntry = entry; + } + sendProofError(getString(lastEntry.mType.getMsgId())); + return; + } + + if (!prover.validate(outStream.toString())) { + sendProofError(getString(R.string.keybase_message_payload_mismatch)); + return; + } + + Bundle resultData = new Bundle(); + resultData.putString(KeychainIntentServiceHandler.DATA_MESSAGE, "OK"); + + // these help the handler construct a useful human-readable message + resultData.putString(KeychainIntentServiceHandler.KEYBASE_PROOF_URL, prover.getProofUrl()); + resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl()); + resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel()); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); + } catch (Exception e) { + sendErrorToHandler(e); + } + + break; + } + case ACTION_DECRYPT_VERIFY: { try { /* Input */ @@ -313,6 +435,7 @@ public class KeychainIntentService extends IntentService implements Progressable } break; + } case ACTION_DELETE: { // Input @@ -403,7 +526,7 @@ public class KeychainIntentService extends IntentService implements Progressable break; } - case ACTION_SIGN_ENCRYPT: + case ACTION_SIGN_ENCRYPT: { // Input SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL); @@ -417,16 +540,15 @@ public class KeychainIntentService extends IntentService implements Progressable sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); break; - - case ACTION_UPLOAD_KEYRING: - + } + case ACTION_UPLOAD_KEYRING: { try { - /* Input */ + /* Input */ String keyServer = data.getString(UPLOAD_KEY_SERVER); // and dataUri! - /* Operation */ + /* Operation */ HkpKeyserver server = new HkpKeyserver(keyServer); CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri); @@ -443,8 +565,24 @@ public class KeychainIntentService extends IntentService implements Progressable sendErrorToHandler(e); } break; + } } + } + private void sendProofError(List log, String label) { + String msg = null; + label = (label == null) ? "" : label + ": "; + for (String m : log) { + Log.e(Constants.TAG, label + m); + msg = m; + } + sendProofError(label + msg); + } + + private void sendProofError(String msg) { + Bundle bundle = new Bundle(); + bundle.putString(KeychainIntentServiceHandler.DATA_ERROR, msg); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, bundle); } private void sendErrorToHandler(Exception e) { @@ -457,7 +595,6 @@ public class KeychainIntentService extends IntentService implements Progressable } else { message = e.getMessage(); } - Log.d(Constants.TAG, "KeychainIntentService Exception: ", e); Bundle data = new Bundle(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java index 6aae1269f..ceb0a2d2b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java @@ -45,6 +45,11 @@ public class KeychainIntentServiceHandler extends Handler { public static final String DATA_MESSAGE = "message"; public static final String DATA_MESSAGE_ID = "message_id"; + // keybase proof specific + public static final String KEYBASE_PROOF_URL = "keybase_proof_url"; + public static final String KEYBASE_PRESENCE_URL = "keybase_presence_url"; + public static final String KEYBASE_PRESENCE_LABEL = "keybase_presence_label"; + Activity mActivity; ProgressDialogFragment mProgressDialogFragment; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 6638c9944..71f6fd4bf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -17,17 +17,12 @@ package org.sufficientlysecure.keychain.ui; -import android.annotation.TargetApi; import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; -import android.nfc.NdefMessage; -import android.nfc.NfcAdapter; -import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.Messenger; -import android.os.Parcelable; import android.support.v4.app.Fragment; import android.view.View; import android.view.View.OnClickListener; @@ -63,9 +58,6 @@ public class ImportKeysActivity extends BaseActivity { // Actions for internal use only: public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_FILE"; - public static final String ACTION_IMPORT_KEY_FROM_NFC = Constants.INTENT_PREFIX - + "IMPORT_KEY_FROM_NFC"; - public static final String EXTRA_RESULT = "result"; // only used by ACTION_IMPORT_KEY @@ -215,15 +207,6 @@ public class ImportKeysActivity extends BaseActivity { startListFragment(savedInstanceState, null, null, null); break; } - case ACTION_IMPORT_KEY_FROM_NFC: { - // NOTE: this only displays the appropriate fragment, no actions are taken - startFileFragment(savedInstanceState); - // TODO!!!!! - - // no immediate actions! - startListFragment(savedInstanceState, null, null, null); - break; - } default: { startCloudFragment(savedInstanceState, null, false); startListFragment(savedInstanceState, null, null, null); @@ -433,50 +416,4 @@ public class ImportKeysActivity extends BaseActivity { } } - /** - * NFC - */ - @Override - public void onResume() { - super.onResume(); - - // Check to see if the Activity started due to an Android Beam - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { - handleActionNdefDiscovered(getIntent()); - } else { - Log.d(Constants.TAG, "NFC: No NDEF discovered!"); - } - } else { - Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1"); - } - } - - /** - * NFC - */ - @Override - public void onNewIntent(Intent intent) { - // onResume gets called after this to handle the intent - setIntent(intent); - } - - /** - * NFC: Parses the NDEF Message from the intent and prints to the TextView - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - void handleActionNdefDiscovered(Intent intent) { - Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); - // only one message sent during the beam - NdefMessage msg = (NdefMessage) rawMsgs[0]; - // record 0 contains the MIME type, record 1 is the AAR, if present - byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); - - Intent importIntent = new Intent(this, ImportKeysActivity.class); - importIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY); - importIntent.putExtra(ImportKeysActivity.EXTRA_KEY_BYTES, receivedKeyringBytes); - - handleActions(null, importIntent); - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeScanActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java similarity index 79% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeScanActivity.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java index 1a7a028c6..4cb6c69e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeScanActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -17,12 +17,17 @@ package org.sufficientlysecure.keychain.ui; +import android.annotation.TargetApi; import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; +import android.nfc.NdefMessage; +import android.nfc.NfcAdapter; +import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.Messenger; +import android.os.Parcelable; import android.support.v4.app.FragmentActivity; import android.widget.Toast; @@ -48,7 +53,7 @@ import java.util.Locale; /** * Proxy activity (just a transparent content view) to scan QR Codes using the Barcode Scanner app */ -public class QrCodeScanActivity extends FragmentActivity { +public class ImportKeysProxyActivity extends FragmentActivity { public static final String ACTION_QR_CODE_API = OpenKeychainIntents.IMPORT_KEY_FROM_QR_CODE; public static final String ACTION_SCAN_WITH_RESULT = Constants.INTENT_PREFIX + "SCAN_QR_CODE_WITH_RESULT"; @@ -88,6 +93,15 @@ public class QrCodeScanActivity extends FragmentActivity { returnResult = false; new IntentIntegrator(this).initiateScan(); + } else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { + // Check to see if the Activity started due to an Android Beam + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + returnResult = false; + handleActionNdefDiscovered(getIntent()); + } else { + Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1"); + finish(); + } } else { Log.e(Constants.TAG, "No valid scheme or action given!"); finish(); @@ -116,6 +130,7 @@ public class QrCodeScanActivity extends FragmentActivity { returnResult(data); } else { super.onActivityResult(requestCode, resultCode, data); + finish(); } } @@ -146,7 +161,28 @@ public class QrCodeScanActivity extends FragmentActivity { } } + public void importKeys(byte[] keyringData) { + + ParcelableKeyRing keyEntry = new ParcelableKeyRing(keyringData); + ArrayList selectedEntries = new ArrayList<>(); + selectedEntries.add(keyEntry); + + startImportService(selectedEntries); + + } + public void importKeys(String fingerprint) { + + ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); + ArrayList selectedEntries = new ArrayList<>(); + selectedEntries.add(keyEntry); + + startImportService(selectedEntries); + + } + + private void startImportService (ArrayList keyRings) { + // Message is received after importing is done in KeychainIntentService KeychainIntentServiceHandler serviceHandler = new KeychainIntentServiceHandler( this, @@ -180,34 +216,32 @@ public class QrCodeScanActivity extends FragmentActivity { return; } - Intent certifyIntent = new Intent(QrCodeScanActivity.this, CertifyKeyActivity.class); + Intent certifyIntent = new Intent(ImportKeysProxyActivity.this, + CertifyKeyActivity.class); certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result); - certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds()); + certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, + result.getImportedMasterKeyIds()); startActivityForResult(certifyIntent, 0); } } }; - // search config - Preferences prefs = Preferences.getPreferences(this); - Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - - // Send all information needed to service to query keys in other thread - Intent intent = new Intent(this, KeychainIntentService.class); - - intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING); - // fill values for this action Bundle data = new Bundle(); - data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); + // search config + { + Preferences prefs = Preferences.getPreferences(this); + Preferences.CloudSearchPrefs cloudPrefs = + new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); + data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); + } - ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); - ArrayList selectedEntries = new ArrayList<>(); - selectedEntries.add(keyEntry); - - data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries); + data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyRings); + // Send all information needed to service to query keys in other thread + Intent intent = new Intent(this, KeychainIntentService.class); + intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Create a new Messenger for the communication back @@ -219,6 +253,20 @@ public class QrCodeScanActivity extends FragmentActivity { // start service with intent startService(intent); + + } + + /** + * NFC: Parses the NDEF Message from the intent and prints to the TextView + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + void handleActionNdefDiscovered(Intent intent) { + Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); + // only one message sent during the beam + NdefMessage msg = (NdefMessage) rawMsgs[0]; + // record 0 contains the MIME type, record 1 is the AAR, if present + byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); + importKeys(receivedKeyringBytes); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 3da185dd2..99714b4a0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -602,8 +602,8 @@ public class KeyListFragment extends LoaderFragment } private void scanQrCode() { - Intent scanQrCode = new Intent(getActivity(), QrCodeScanActivity.class); - scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT); + Intent scanQrCode = new Intent(getActivity(), ImportKeysProxyActivity.class); + scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_WITH_RESULT); startActivityForResult(scanQrCode, 0); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index e1a8981c4..afb742079 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -184,7 +184,6 @@ public class ViewKeyActivity extends BaseActivity implements } }); - // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); @@ -317,8 +316,8 @@ public class ViewKeyActivity extends BaseActivity implements } private void scanQrCode() { - Intent scanQrCode = new Intent(this, QrCodeScanActivity.class); - scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT); + Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class); + scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_WITH_RESULT); startActivityForResult(scanQrCode, 0); } @@ -447,7 +446,6 @@ public class ViewKeyActivity extends BaseActivity implements startActivityForResult(safeSlingerIntent, 0); } - /** * Load QR Code asynchronously and with a fade in animation * diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index 37f9113bb..894529cc5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -144,6 +144,11 @@ public class ViewKeyAdvActivity extends BaseActivity implements mTabsAdapter.addTab(ViewKeyAdvCertsFragment.class, certsBundle, getString(R.string.key_view_tab_certs)); + Bundle trustBundle = new Bundle(); + trustBundle.putParcelable(ViewKeyTrustFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyTrustFragment.class, + trustBundle, getString(R.string.key_view_tab_keybase)); + // update layout after operations mSlidingTabLayout.setViewPager(mViewPager); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvMainFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvMainFragment.java index c9d20f9f4..fc107d794 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvMainFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvMainFragment.java @@ -37,15 +37,11 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import java.util.Date; @@ -55,24 +51,15 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements public static final String ARG_DATA_URI = "uri"; - private View mActionEdit; - private View mActionEditDivider; - private View mActionEncryptFiles; - private View mActionEncryptText; - private View mActionEncryptTextText; private View mActionCertify; private View mActionCertifyText; private ImageView mActionCertifyImage; - private View mActionUpdate; private ListView mUserIds; private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_USER_IDS = 1; - // conservative attitude - private boolean mHasEncrypt = true; - private UserIdsAdapter mUserIdsAdapter; private Uri mDataUri; @@ -83,18 +70,12 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements View view = inflater.inflate(R.layout.view_key_adv_main_fragment, getContainer()); mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); - mActionEdit = view.findViewById(R.id.view_key_action_edit); - mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider); - mActionEncryptText = view.findViewById(R.id.view_key_action_encrypt_text); - mActionEncryptTextText = view.findViewById(R.id.view_key_action_encrypt_text_text); - mActionEncryptFiles = view.findViewById(R.id.view_key_action_encrypt_files); mActionCertify = view.findViewById(R.id.view_key_action_certify); 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); mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override @@ -139,37 +120,11 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); - mActionEncryptFiles.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - encrypt(mDataUri, false); - } - }); - mActionEncryptText.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - encrypt(mDataUri, true); - } - }); mActionCertify.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { certify(mDataUri); } }); - mActionEdit.setOnClickListener(new View.OnClickListener() { - public void onClick(View view) { - 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); @@ -222,45 +177,23 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements switch (loader.getId()) { case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { - if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) { - // edit button - mActionEdit.setVisibility(View.VISIBLE); - mActionEditDivider.setVisibility(View.VISIBLE); - } else { - // edit button - mActionEdit.setVisibility(View.GONE); - mActionEditDivider.setVisibility(View.GONE); - } // If this key is revoked, it cannot be used for anything! if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) { - mActionEdit.setEnabled(false); mActionCertify.setEnabled(false); mActionCertifyText.setEnabled(false); - mActionEncryptText.setEnabled(false); - mActionEncryptTextText.setEnabled(false); - mActionEncryptFiles.setEnabled(false); } else { - mActionEdit.setEnabled(true); 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); - mActionEncryptText.setEnabled(false); - mActionEncryptTextText.setEnabled(false); - mActionEncryptFiles.setEnabled(false); } else { mActionCertify.setEnabled(true); mActionCertifyText.setEnabled(true); - mActionEncryptText.setEnabled(true); - mActionEncryptTextText.setEnabled(true); - mActionEncryptFiles.setEnabled(true); } } - mHasEncrypt = data.getInt(INDEX_UNIFIED_HAS_ENCRYPT) != 0; - break; } } @@ -286,48 +219,6 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements } } - private void encrypt(Uri dataUri, boolean text) { - // If there is no encryption key, don't bother. - if (!mHasEncrypt) { - Notify.showNotify(getActivity(), R.string.error_no_encrypt_subkey, Notify.Style.ERROR); - return; - } - try { - long keyId = new ProviderHelper(getActivity()) - .getCachedPublicKeyRing(dataUri) - .extractOrGetMasterKeyId(); - long[] encryptionKeyIds = new long[]{keyId}; - Intent intent; - if (text) { - intent = new Intent(getActivity(), EncryptTextActivity.class); - intent.setAction(EncryptTextActivity.ACTION_ENCRYPT_TEXT); - intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); - } else { - intent = new Intent(getActivity(), EncryptFilesActivity.class); - intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); - intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); - } - // used instead of startActivity set actionbar based on callingPackage - startActivityForResult(intent, 0); - } catch (PgpKeyNotFoundException e) { - Log.e(Constants.TAG, "key not found!", e); - } - } - - 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 = KeyFormattingUtils.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) { long keyId = 0; try { @@ -342,10 +233,4 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements startActivityForResult(certifyIntent, 0); } - private void editKey(Uri dataUri) { - Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); - editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); - startActivityForResult(editIntent, 0); - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index 6208cff4e..8d0a2dd1d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -17,7 +17,7 @@ package org.sufficientlysecure.keychain.ui; -import android.annotation.TargetApi; +import android.app.ActivityOptions; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; @@ -26,10 +26,11 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.provider.Settings; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; +import android.support.v7.widget.CardView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -47,7 +48,6 @@ 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.ui.dialog.ShareNfcDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; @@ -62,14 +62,13 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements public static final String ARG_DATA_URI = "uri"; private TextView mFingerprint; - private ImageView mFingerprintQrCode; + private ImageView mQrCode; + private CardView mQrCodeLayout; private View mFingerprintShareButton; private View mFingerprintClipboardButton; private View mKeyShareButton; private View mKeyClipboardButton; private ImageButton mKeySafeSlingerButton; - private View mNfcHelpButton; - private View mNfcPrefsButton; private View mKeyUploadButton; ProviderHelper mProviderHelper; @@ -86,26 +85,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity()); mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint); - mFingerprintQrCode = (ImageView) view.findViewById(R.id.view_key_fingerprint_qr_code_image); + mQrCode = (ImageView) view.findViewById(R.id.view_key_qr_code); + mQrCodeLayout = (CardView) view.findViewById(R.id.view_key_qr_code_layout); mFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share); mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard); mKeyShareButton = view.findViewById(R.id.view_key_action_key_share); mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard); mKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger); - 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); mKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light), PorterDuff.Mode.SRC_IN); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - mNfcPrefsButton.setVisibility(View.VISIBLE); - } else { - mNfcPrefsButton.setVisibility(View.GONE); - } - - mFingerprintQrCode.setOnClickListener(new View.OnClickListener() { + mQrCodeLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showQrCodeDialog(); @@ -142,18 +134,6 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements startSafeSlinger(mDataUri); } }); - mNfcHelpButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showNfcHelpDialog(); - } - }); - mNfcPrefsButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showNfcPrefs(); - } - }); mKeyUploadButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -239,20 +219,18 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements private void showQrCodeDialog() { Intent qrCodeIntent = new Intent(getActivity(), QrCodeViewActivity.class); + + // create the transition animation - the images in the layouts + // of both activities are defined with android:transitionName="qr_code" + Bundle opts = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ActivityOptions options = ActivityOptions + .makeSceneTransitionAnimation(getActivity(), mQrCodeLayout, "qr_code"); + opts = options.toBundle(); + } + qrCodeIntent.setData(mDataUri); - startActivity(qrCodeIntent); - } - - private void showNfcHelpDialog() { - ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance(); - dialog.show(getActivity().getSupportFragmentManager(), "shareNfcDialog"); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - private void showNfcPrefs() { - Intent intentSettings = new Intent( - Settings.ACTION_NFCSHARING_SETTINGS); - startActivity(intentSettings); + ActivityCompat.startActivity(getActivity(), qrCodeIntent, opts); } @Override @@ -363,14 +341,14 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements // scale the image up to our actual size. we do this in code rather // than let the ImageView do this because we don't require filtering. Bitmap scaled = Bitmap.createScaledBitmap(qrCode, - mFingerprintQrCode.getHeight(), mFingerprintQrCode.getHeight(), + mQrCode.getHeight(), mQrCode.getHeight(), false); - mFingerprintQrCode.setImageBitmap(scaled); + mQrCode.setImageBitmap(scaled); // simple fade-in animation AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); anim.setDuration(200); - mFingerprintQrCode.startAnimation(anim); + mQrCode.startAnimation(anim); } } }; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java new file mode 100644 index 000000000..5483d16b8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2014 Tim Bray + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.StyleSpan; +import android.text.style.URLSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; + +import com.textuality.keybase.lib.KeybaseException; +import com.textuality.keybase.lib.Proof; +import com.textuality.keybase.lib.User; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Hashtable; +import java.util.List; + +public class ViewKeyTrustFragment extends LoaderFragment implements + LoaderManager.LoaderCallbacks { + + public static final String ARG_DATA_URI = "uri"; + + private View mStartSearch; + private TextView mTrustReadout; + private TextView mReportHeader; + private TableLayout mProofListing; + private LayoutInflater mInflater; + private View mProofVerifyHeader; + private TextView mProofVerifyDetail; + + private static final int LOADER_ID_DATABASE = 1; + + // for retrieving the key we’re working on + private Uri mDataUri; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.view_key_trust_fragment, getContainer()); + mInflater = inflater; + + mTrustReadout = (TextView) view.findViewById(R.id.view_key_trust_readout); + mStartSearch = view.findViewById(R.id.view_key_trust_search_cloud); + mStartSearch.setEnabled(false); + mReportHeader = (TextView) view.findViewById(R.id.view_key_trust_cloud_narrative); + mProofListing = (TableLayout) view.findViewById(R.id.view_key_proof_list); + mProofVerifyHeader = view.findViewById(R.id.view_key_proof_verify_header); + mProofVerifyDetail = (TextView) view.findViewById(R.id.view_key_proof_verify_detail); + mReportHeader.setVisibility(View.GONE); + mProofListing.setVisibility(View.GONE); + mProofVerifyHeader.setVisibility(View.GONE); + mProofVerifyDetail.setVisibility(View.GONE); + + return root; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + mDataUri = dataUri; + + // retrieve the key from the database + getLoaderManager().initLoader(LOADER_ID_DATABASE, null, this); + } + + static final String[] TRUST_PROJECTION = new String[]{ + KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.IS_REVOKED, KeyRings.EXPIRY, + KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED + }; + static final int INDEX_TRUST_FINGERPRINT = 1; + static final int INDEX_TRUST_IS_REVOKED = 2; + static final int INDEX_TRUST_EXPIRY = 3; + static final int INDEX_UNIFIED_HAS_ANY_SECRET = 4; + static final int INDEX_VERIFIED = 5; + + public Loader onCreateLoader(int id, Bundle args) { + setContentShown(false); + + switch (id) { + case LOADER_ID_DATABASE: { + Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, TRUST_PROJECTION, null, null, null); + } + // decided to just use an AsyncTask for keybase, but maybe later + default: + return null; + } + } + + public void onLoadFinished(Loader loader, Cursor data) { + /* TODO better error handling? May cause problems when a key is deleted, + * because the notification triggers faster than the activity closes. + */ + // Avoid NullPointerExceptions... + if (data.getCount() == 0) { + return; + } + + boolean nothingSpecial = true; + StringBuilder message = new StringBuilder(); + + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + if (data.moveToFirst()) { + + if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) { + message.append(getString(R.string.key_trust_it_is_yours)).append("\n"); + nothingSpecial = false; + } else if (data.getInt(INDEX_VERIFIED) != 0) { + message.append(getString(R.string.key_trust_already_verified)).append("\n"); + nothingSpecial = false; + } + + // If this key is revoked, don’t trust it! + if (data.getInt(INDEX_TRUST_IS_REVOKED) != 0) { + message.append(getString(R.string.key_trust_revoked)). + append(getString(R.string.key_trust_old_keys)); + + nothingSpecial = false; + } else { + Date expiryDate = new Date(data.getLong(INDEX_TRUST_EXPIRY) * 1000); + if (!data.isNull(INDEX_TRUST_EXPIRY) && expiryDate.before(new Date())) { + + // if expired, don’t trust it! + message.append(getString(R.string.key_trust_expired)). + append(getString(R.string.key_trust_old_keys)); + + nothingSpecial = false; + } + } + + if (nothingSpecial) { + message.append(getString(R.string.key_trust_maybe)); + } + + final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT); + final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); + if (fingerprint != null) { + + mStartSearch.setEnabled(true); + mStartSearch.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mStartSearch.setEnabled(false); + new DescribeKey().execute(fingerprint); + } + }); + } + } + + mTrustReadout.setText(message); + setContentShown(true); + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader loader) { + // no-op in this case I think + } + + class ResultPage { + String mHeader; + final List mProofs; + + public ResultPage(String header, List proofs) { + mHeader = header; + mProofs = proofs; + } + } + + // look for evidence from keybase in the background, make tabular version of result + // + private class DescribeKey extends AsyncTask { + + @Override + protected ResultPage doInBackground(String... args) { + String fingerprint = args[0]; + + final ArrayList proofList = new ArrayList(); + final Hashtable> proofs = new Hashtable>(); + try { + User keybaseUser = User.findByFingerprint(fingerprint); + for (Proof proof : keybaseUser.getProofs()) { + Integer proofType = proof.getType(); + appendIfOK(proofs, proofType, proof); + } + + // a one-liner in a modern programming language + for (Integer proofType : proofs.keySet()) { + Proof[] x = {}; + Proof[] proofsFor = proofs.get(proofType).toArray(x); + if (proofsFor.length > 0) { + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append(getProofNarrative(proofType)).append(" "); + + int i = 0; + while (i < proofsFor.length - 1) { + appendProofLinks(ssb, fingerprint, proofsFor[i]); + ssb.append(", "); + i++; + } + appendProofLinks(ssb, fingerprint, proofsFor[i]); + proofList.add(ssb); + } + } + + } catch (KeybaseException ignored) { + } + + return new ResultPage(getString(R.string.key_trust_results_prefix), proofList); + } + + private SpannableStringBuilder appendProofLinks(SpannableStringBuilder ssb, final String fingerprint, final Proof proof) throws KeybaseException { + int startAt = ssb.length(); + String handle = proof.getHandle(); + ssb.append(handle); + ssb.setSpan(new URLSpan(proof.getServiceUrl()), startAt, startAt + handle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (haveProofFor(proof.getType())) { + ssb.append("\u00a0["); + startAt = ssb.length(); + String verify = getString(R.string.keybase_verify); + ssb.append(verify); + ClickableSpan clicker = new ClickableSpan() { + @Override + public void onClick(View view) { + verify(proof, fingerprint); + } + }; + ssb.setSpan(clicker, startAt, startAt + verify.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append("]"); + } + return ssb; + } + + @Override + protected void onPostExecute(ResultPage result) { + super.onPostExecute(result); + if (result.mProofs.isEmpty()) { + result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence); + } + + mStartSearch.setVisibility(View.GONE); + mReportHeader.setVisibility(View.VISIBLE); + mProofListing.setVisibility(View.VISIBLE); + mReportHeader.setText(result.mHeader); + + int rowNumber = 1; + for (CharSequence s : result.mProofs) { + TableRow row = (TableRow) mInflater.inflate(R.layout.view_key_keybase_proof, null); + TextView number = (TextView) row.findViewById(R.id.proof_number); + TextView text = (TextView) row.findViewById(R.id.proof_text); + number.setText(Integer.toString(rowNumber++) + ". "); + text.setText(s); + text.setMovementMethod(LinkMovementMethod.getInstance()); + mProofListing.addView(row); + } + + // mSearchReport.loadDataWithBaseURL("file:///android_res/drawable/", s, "text/html", "UTF-8", null); + } + } + + private String getProofNarrative(int proofType) { + int stringIndex; + switch (proofType) { + case Proof.PROOF_TYPE_TWITTER: stringIndex = R.string.keybase_narrative_twitter; break; + case Proof.PROOF_TYPE_GITHUB: stringIndex = R.string.keybase_narrative_github; break; + case Proof.PROOF_TYPE_DNS: stringIndex = R.string.keybase_narrative_dns; break; + case Proof.PROOF_TYPE_WEB_SITE: stringIndex = R.string.keybase_narrative_web_site; break; + case Proof.PROOF_TYPE_HACKERNEWS: stringIndex = R.string.keybase_narrative_hackernews; break; + case Proof.PROOF_TYPE_COINBASE: stringIndex = R.string.keybase_narrative_coinbase; break; + case Proof.PROOF_TYPE_REDDIT: stringIndex = R.string.keybase_narrative_reddit; break; + default: stringIndex = R.string.keybase_narrative_unknown; + } + return getActivity().getString(stringIndex); + } + + private void appendIfOK(Hashtable> table, Integer proofType, Proof proof) throws KeybaseException { + ArrayList list = table.get(proofType); + if (list == null) { + list = new ArrayList(); + table.put(proofType, list); + } + list.add(proof); + } + + // which proofs do we have working verifiers for? + private boolean haveProofFor(int proofType) { + switch (proofType) { + case Proof.PROOF_TYPE_TWITTER: return true; + case Proof.PROOF_TYPE_GITHUB: return true; + case Proof.PROOF_TYPE_DNS: return true; + case Proof.PROOF_TYPE_WEB_SITE: return true; + case Proof.PROOF_TYPE_HACKERNEWS: return true; + case Proof.PROOF_TYPE_COINBASE: return true; + case Proof.PROOF_TYPE_REDDIT: return true; + default: return false; + } + } + + private void verify(final Proof proof, final String fingerprint) { + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + Bundle data = new Bundle(); + intent.setAction(KeychainIntentService.ACTION_VERIFY_KEYBASE_PROOF); + + data.putString(KeychainIntentService.KEYBASE_PROOF, proof.toString()); + data.putString(KeychainIntentService.KEYBASE_REQUIRED_FINGERPRINT, fingerprint); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + mProofVerifyDetail.setVisibility(View.GONE); + + // Create a new Messenger for the communication back after proof work is done + // + KeychainIntentServiceHandler handler = new KeychainIntentServiceHandler(getActivity(), + getString(R.string.progress_verifying_signature), ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + Bundle returnData = message.getData(); + String msg = returnData.getString(KeychainIntentServiceHandler.DATA_MESSAGE); + SpannableStringBuilder ssb = new SpannableStringBuilder(); + + if ((msg != null) && msg.equals("OK")) { + + //yay + String proofUrl = returnData.getString(KeychainIntentServiceHandler.KEYBASE_PROOF_URL); + String presenceUrl = returnData.getString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_URL); + String presenceLabel = returnData.getString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_LABEL); + + String proofLabel; + switch (proof.getType()) { + case Proof.PROOF_TYPE_TWITTER: + proofLabel = getString(R.string.keybase_twitter_proof); + break; + case Proof.PROOF_TYPE_DNS: + proofLabel = getString(R.string.keybase_dns_proof); + break; + case Proof.PROOF_TYPE_WEB_SITE: + proofLabel = getString(R.string.keybase_web_site_proof); + break; + case Proof.PROOF_TYPE_GITHUB: + proofLabel = getString(R.string.keybase_github_proof); + break; + case Proof.PROOF_TYPE_REDDIT: + proofLabel = getString(R.string.keybase_reddit_proof); + break; + default: + proofLabel = getString(R.string.keybase_a_post); + break; + } + + ssb.append(getString(R.string.keybase_proof_succeeded)); + StyleSpan bold = new StyleSpan(Typeface.BOLD); + ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append("\n\n"); + int length = ssb.length(); + ssb.append(proofLabel); + if (proofUrl != null) { + URLSpan postLink = new URLSpan(proofUrl); + ssb.setSpan(postLink, length, length + proofLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (Proof.PROOF_TYPE_DNS == proof.getType()) { + ssb.append(" ").append(getString(R.string.keybase_for_the_domain)).append(" "); + } else { + ssb.append(" ").append(getString(R.string.keybase_fetched_from)).append(" "); + } + length = ssb.length(); + URLSpan presenceLink = new URLSpan(presenceUrl); + ssb.append(presenceLabel); + ssb.setSpan(presenceLink, length, length + presenceLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (Proof.PROOF_TYPE_REDDIT == proof.getType()) { + ssb.append(", "). + append(getString(R.string.keybase_reddit_attribution)). + append(" “").append(proof.getHandle()).append("”, "); + } + ssb.append(" ").append(getString(R.string.keybase_contained_signature)); + } else { + // verification failed! + msg = returnData.getString(KeychainIntentServiceHandler.DATA_ERROR); + ssb.append(getString(R.string.keybase_proof_failure)); + if (msg == null) { + msg = getString(R.string.keybase_unknown_proof_failure); + } + StyleSpan bold = new StyleSpan(Typeface.BOLD); + ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append("\n\n").append(msg); + } + mProofVerifyHeader.setVisibility(View.VISIBLE); + mProofVerifyDetail.setVisibility(View.VISIBLE); + mProofVerifyDetail.setMovementMethod(LinkMovementMethod.getInstance()); + mProofVerifyDetail.setText(ssb); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(handler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + handler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 598793233..6ba9e26ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -213,8 +213,24 @@ public class ImportKeysAdapter extends ArrayAdapter { // destroyLoader view from holder holder.userIdsList.removeAllViews(); + // we want conventional gpg UserIDs first, then Keybase ”proofs” HashMap> mergedUserIds = entry.getMergedUserIds(); - for (Map.Entry> pair : mergedUserIds.entrySet()) { + ArrayList>> sortedIds = new ArrayList>>(mergedUserIds.entrySet()); + java.util.Collections.sort(sortedIds, new java.util.Comparator>>() { + @Override + public int compare(Map.Entry> entry1, Map.Entry> entry2) { + + // sort keybase UserIds after non-Keybase + boolean e1IsKeybase = entry1.getKey().contains(":"); + boolean e2IsKeybase = entry2.getKey().contains(":"); + if (e1IsKeybase != e2IsKeybase) { + return (e1IsKeybase) ? 1 : -1; + } + return entry1.getKey().compareTo(entry2.getKey()); + } + }); + + for (Map.Entry> pair : sortedIds) { String cUserId = pair.getKey(); HashSet cEmails = pair.getValue(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java deleted file mode 100644 index 19cf27259..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui.dialog; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentActivity; - -import org.sufficientlysecure.keychain.R; - -public class BadImportKeyDialogFragment extends DialogFragment { - private static final String ARG_BAD_IMPORT = "bad_import"; - - /** - * Creates a new instance of this Bad Import Key DialogFragment - * - * @param bad - * @return - */ - public static BadImportKeyDialogFragment newInstance(int bad) { - BadImportKeyDialogFragment frag = new BadImportKeyDialogFragment(); - Bundle args = new Bundle(); - - args.putInt(ARG_BAD_IMPORT, bad); - frag.setArguments(args); - - return frag; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final FragmentActivity activity = getActivity(); - final int badImport = getArguments().getInt(ARG_BAD_IMPORT); - - CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); - alert.setIcon(R.drawable.ic_dialog_alert_holo_light); - alert.setTitle(R.string.warning); - alert.setMessage(activity.getResources() - .getQuantityString(R.plurals.bad_keys_encountered, badImport, badImport)); - alert.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - alert.setCancelable(true); - - return alert.show(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 5b96ea231..802f0c11b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -33,6 +33,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; @@ -100,14 +101,20 @@ public class DeleteKeyDialogFragment extends DialogFragment { ProviderHelper.FIELD_TYPE_INTEGER } ); - String userId = (String) data.get(KeyRings.USER_ID); + String name; + String[] mainUserId = KeyRing.splitUserId((String) data.get(KeyRings.USER_ID)); + if (mainUserId[0] != null) { + name = mainUserId[0]; + } else { + name = getString(R.string.user_id_no_name); + } hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1; // Set message depending on which key it is. mMainMessage.setText(getString( hasSecret ? R.string.secret_key_deletion_confirmation : R.string.public_key_deletetion_confirmation, - userId + name )); } catch (ProviderHelper.NotFoundException e) { dismiss(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java deleted file mode 100644 index 961f92f03..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2013-2014 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui.dialog; - -import android.annotation.TargetApi; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.nfc.NfcAdapter; -import android.os.Build; -import android.os.Bundle; -import android.provider.Settings; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentActivity; - -import org.sufficientlysecure.htmltextview.HtmlTextView; -import org.sufficientlysecure.keychain.R; - -@TargetApi(Build.VERSION_CODES.JELLY_BEAN) -public class ShareNfcDialogFragment extends DialogFragment { - - /** - * Creates new instance of this fragment - */ - public static ShareNfcDialogFragment newInstance() { - ShareNfcDialogFragment frag = new ShareNfcDialogFragment(); - - return frag; - } - - /** - * Creates dialog - */ - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final FragmentActivity activity = getActivity(); - - CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); - - alert.setTitle(R.string.share_nfc_dialog); - alert.setCancelable(true); - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dismiss(); - } - }); - - HtmlTextView textView = new HtmlTextView(getActivity()); - textView.setPadding(8, 8, 8, 8); - alert.setView(textView); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - textView.setText(getString(R.string.error) + ": " - + getString(R.string.error_jelly_bean_needed)); - } else { - // check if NFC Adapter is available - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); - if (nfcAdapter == null) { - textView.setText(getString(R.string.error) + ": " - + getString(R.string.error_nfc_needed)); - } else { - // nfc works... - textView.setHtmlFromRawResource(getActivity(), R.raw.nfc_beam_share, true); - - alert.setNegativeButton(R.string.menu_beam_preferences, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - Intent intentSettings = new Intent( - Settings.ACTION_NFCSHARING_SETTINGS); - startActivity(intentSettings); - } - } - ); - } - } - - return alert.show(); - } -} diff --git a/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml b/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml index d52e72997..d32b1496f 100644 --- a/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml +++ b/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml @@ -6,10 +6,10 @@ + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:paddingLeft="24dp" + android:paddingRight="24dp"> + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:paddingLeft="24dp" + android:paddingRight="24dp"> + android:lines="1" + android:maxLines="1" + android:minLines="1" + android:scrollbars="vertical" + android:layout_gravity="center_vertical" /> + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:paddingLeft="24dp" + android:paddingRight="24dp"> - - - - - - - - - - - - - - - - - - - diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_share_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_adv_share_fragment.xml index cd8f96e6f..7b382dca5 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_share_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_share_fragment.xml @@ -1,4 +1,5 @@ @@ -67,15 +68,26 @@ android:layout_height="1dip" android:background="?android:attr/listDivider" /> - + android:clickable="true" + android:foreground="?android:attr/selectableItemBackground" + card_view:cardBackgroundColor="@android:color/white" + card_view:cardUseCompatPadding="true" + card_view:cardCornerRadius="4dp"> + + + - - - - - - - - - - - - diff --git a/OpenKeychain/src/main/res/layout/view_key_delete_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_delete_fragment.xml index bbc723bfa..1a6454f91 100644 --- a/OpenKeychain/src/main/res/layout/view_key_delete_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_delete_fragment.xml @@ -1,6 +1,10 @@ @@ -8,7 +12,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/mainMessage" - android:layout_margin="4dp" android:textAppearance="?android:textAppearanceMedium" /> \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_keybase_proof.xml b/OpenKeychain/src/main/res/layout/view_key_keybase_proof.xml new file mode 100644 index 000000000..0ffd151c1 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/view_key_keybase_proof.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/OpenKeychain/src/main/res/layout/view_key_trust_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_trust_fragment.xml new file mode 100644 index 000000000..f97401271 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/view_key_trust_fragment.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 700f1d167..10a53f0b7 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -46,6 +46,9 @@ "Identities" + "Should you trust this key?" + Proof verification + "Evidence from the cloud" "Subkeys" "Cloud search" "General" @@ -545,6 +548,7 @@ "Share" "Subkeys" "Certificates" + "Keybase.io" "Revoked" "This identity has been revoked by the key owner. It is no longer valid." "Certified" @@ -554,6 +558,47 @@ "Invalid" "Something is wrong with this identity!" + + "You have already certified this key!" + "This is one of your keys!" + "This key is neither revoked nor expired.\nYou haven’t certified it, but you may choose to trust it." + "This key has been revoked by its owner. You should not trust it." + "This key has expired. You should not trust it." + " It may be OK to use this to decrypt an old message dating from the time when this key was valid." + "No evidence from the cloud on this key’s trustworthiness." + "Start search" + "Keybase.io offers “proofs” which assert that the owner of this key: " + + + "Posts to Twitter as" + "Is known on GitHub as" + "Controls the domain name(s)" + "Can post to the Web site(s)" + "Posts to Reddit as" + "Is known on Coinbase as" + "Posts to Hacker News as" + "Unknown proof type" + "Unfortunately this proof cannot be verified." + "Unrecognized problem with proof checker" + "Problem with proof evidence" + "Key fingerprint doesn’t match that in proof post" + "DNS TXT Record retrieval failed" + "No proof checker found for" + "Decrypted proof post does not match expected value" + "Fetching proof evidence" + "This proof has been verified!" + "A post" + "fetched from" + "for the domain" + "contains a message which could only have been created by the owner of this key." + "A tweet" + "A DNS TXT record" + "A text file" + "A gist" + "A JSON file" + "attributed by Reddit to" + "Verify" + "Change Passphrase" "Add Identity" @@ -978,6 +1023,19 @@ "Encountered trailing data of unknown type" "Unlocking secret key" + + "Starting signature check" + "No signature list in signed literal data" + "Message not signed with right key" + "No payload in signed literal data" + "Filename: %s" + "MIME type: %s" + "Modification time: %s" + "Filesize: %s" + "Verifying signature data" + "Integrity check error!" + "OK" + "Starting sign/encrypt operation" "Processing input from byte array"