diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
24 files changed, 1033 insertions, 463 deletions
| 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<WrappedSignature> 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 { @@ -214,6 +245,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       */      private DecryptVerifyResult decryptVerify(InputStream in, int indent) throws IOException, PGPException { @@ -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<WrappedSignature> getEmbeddedSignatures() {          ArrayList<WrappedSignature> 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<WrappedSignature> 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<List<byte[]>> extents = new ArrayList<List<byte[]>>(); +                        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<String> 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 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<ParcelableKeyRing> selectedEntries = new ArrayList<>(); +        selectedEntries.add(keyEntry); + +        startImportService(selectedEntries); + +    } +      public void importKeys(String fingerprint) { + +        ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); +        ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); +        selectedEntries.add(keyEntry); + +        startImportService(selectedEntries); + +    } + +    private void startImportService (ArrayList<ParcelableKeyRing> 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); - -        ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); -        ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); -        selectedEntries.add(keyEntry); +        // 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); +        } -        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); -        qrCodeIntent.setData(mDataUri); -        startActivity(qrCodeIntent); -    } -    private void showNfcHelpDialog() { -        ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance(); -        dialog.show(getActivity().getSupportFragmentManager(), "shareNfcDialog"); -    } +        // 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(); +        } -    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) -    private void showNfcPrefs() { -        Intent intentSettings = new Intent( -                Settings.ACTION_NFCSHARING_SETTINGS); -        startActivity(intentSettings); +        qrCodeIntent.setData(mDataUri); +        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 <tbray@textuality.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +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<Cursor> { + +    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<Cursor> 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<Cursor> 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<Cursor> loader) { +        // no-op in this case I think +    } + +    class ResultPage { +        String mHeader; +        final List<CharSequence> mProofs; + +        public ResultPage(String header, List<CharSequence> proofs) { +            mHeader = header; +            mProofs = proofs; +        } +    } + +    // look for evidence from keybase in the background, make tabular version of result +    // +    private class DescribeKey extends AsyncTask<String, Void, ResultPage> { + +        @Override +        protected ResultPage doInBackground(String... args) { +            String fingerprint = args[0]; + +            final ArrayList<CharSequence> proofList = new ArrayList<CharSequence>(); +            final Hashtable<Integer, ArrayList<Proof>> proofs = new Hashtable<Integer, ArrayList<Proof>>(); +            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<Integer, ArrayList<Proof>> table, Integer proofType, Proof proof) throws KeybaseException { +        ArrayList<Proof> list = table.get(proofType); +        if (list == null) { +            list = new ArrayList<Proof>(); +            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<ImportKeysListEntry> {              // destroyLoader view from holder              holder.userIdsList.removeAllViews(); +            // we want conventional gpg UserIDs first, then Keybase ”proofs”              HashMap<String, HashSet<String>> mergedUserIds = entry.getMergedUserIds(); -            for (Map.Entry<String, HashSet<String>> pair : mergedUserIds.entrySet()) { +            ArrayList<Map.Entry<String, HashSet<String>>> sortedIds = new ArrayList<Map.Entry<String, HashSet<String>>>(mergedUserIds.entrySet()); +            java.util.Collections.sort(sortedIds, new java.util.Comparator<Map.Entry<String, HashSet<String>>>() { +                @Override +                public int compare(Map.Entry<String, HashSet<String>> entry1, Map.Entry<String, HashSet<String>> 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<String, HashSet<String>> pair : sortedIds) {                  String cUserId = pair.getKey();                  HashSet<String> 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 <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program.  If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui.dialog; - -import android.app.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 <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program.  If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui.dialog; - -import android.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(); -    } -} | 
