mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-27 19:22:14 -05:00
Merge branch 'development' of https://github.com/open-keychain/open-keychain into development
This commit is contained in:
commit
960a163185
@ -231,7 +231,7 @@ public class PgpKeyOperationTest {
|
||||
ring.getPublicKey().getCreationTime().after(new Date(new Date().getTime()-1000*120)));
|
||||
|
||||
Assert.assertNull("key ring should not expire",
|
||||
ring.getPublicKey().getExpiryTime());
|
||||
ring.getPublicKey().getUnsafeExpiryTimeForTesting());
|
||||
|
||||
Assert.assertEquals("first (master) key can certify",
|
||||
KeyFlags.CERTIFY_OTHER, (long) subkeys.get(0).getKeyUsage());
|
||||
@ -342,9 +342,9 @@ public class PgpKeyOperationTest {
|
||||
|
||||
Assert.assertNotNull("new key is not null", newKey);
|
||||
Assert.assertNotNull("added key must have an expiry date",
|
||||
newKey.getExpiryTime());
|
||||
newKey.getUnsafeExpiryTimeForTesting());
|
||||
Assert.assertEquals("added key must have expected expiry date",
|
||||
expiry, newKey.getExpiryTime().getTime()/1000);
|
||||
expiry, newKey.getUnsafeExpiryTimeForTesting().getTime()/1000);
|
||||
Assert.assertEquals("added key must have expected flags",
|
||||
flags, (long) newKey.getKeyUsage());
|
||||
Assert.assertEquals("added key must have expected bitsize",
|
||||
@ -403,9 +403,9 @@ public class PgpKeyOperationTest {
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertNotNull("modified key must have an expiry date",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
|
||||
Assert.assertEquals("modified key must have expected expiry date",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000);
|
||||
Assert.assertEquals("modified key must have same flags as before",
|
||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||
}
|
||||
@ -417,9 +417,9 @@ public class PgpKeyOperationTest {
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertNotNull("modified key must have an expiry date",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
|
||||
Assert.assertEquals("modified key must have expected expiry date",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000);
|
||||
Assert.assertEquals("modified key must have same flags as before",
|
||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||
}
|
||||
@ -443,9 +443,9 @@ public class PgpKeyOperationTest {
|
||||
Assert.assertEquals("modified key must have expected flags",
|
||||
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
|
||||
Assert.assertNotNull("key must retain its expiry",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
|
||||
Assert.assertEquals("key expiry must be unchanged",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000);
|
||||
}
|
||||
|
||||
{ // expiry of 0 should be "no expiry"
|
||||
@ -463,7 +463,7 @@ public class PgpKeyOperationTest {
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getExpiryTime());
|
||||
Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
|
||||
}
|
||||
|
||||
{ // a past expiry should fail
|
||||
@ -517,9 +517,9 @@ public class PgpKeyOperationTest {
|
||||
PacketTags.SIGNATURE, onlyB.get(1).tag);
|
||||
|
||||
Assert.assertNotNull("modified key must have an expiry date",
|
||||
modified.getPublicKey().getExpiryTime());
|
||||
modified.getPublicKey().getUnsafeExpiryTimeForTesting());
|
||||
Assert.assertEquals("modified key must have expected expiry date",
|
||||
expiry, modified.getPublicKey().getExpiryTime().getTime() / 1000);
|
||||
expiry, modified.getPublicKey().getUnsafeExpiryTimeForTesting().getTime() / 1000);
|
||||
Assert.assertEquals("modified key must have same flags as before",
|
||||
ring.getPublicKey().getKeyUsage(), modified.getPublicKey().getKeyUsage());
|
||||
}
|
||||
@ -531,9 +531,9 @@ public class PgpKeyOperationTest {
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertNotNull("modified key must have an expiry date",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
|
||||
Assert.assertEquals("modified key must have expected expiry date",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime() / 1000);
|
||||
Assert.assertEquals("modified key must have same flags as before",
|
||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||
}
|
||||
@ -547,17 +547,29 @@ public class PgpKeyOperationTest {
|
||||
Assert.assertEquals("modified key must have expected flags",
|
||||
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
|
||||
Assert.assertNotNull("key must retain its expiry",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
|
||||
Assert.assertEquals("key expiry must be unchanged",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000);
|
||||
}
|
||||
|
||||
{ // expiry of 0 should be "no expiry"
|
||||
|
||||
// even if there is a non-expiring user id while all others are revoked, it doesn't count!
|
||||
// for this purpose we revoke one while they still have expiry times
|
||||
parcel.reset();
|
||||
parcel.mRevokeUserIds.add("aloe");
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, 0L));
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getExpiryTime());
|
||||
// for this check, it is relevant that we DON'T use the unsafe one!
|
||||
Assert.assertNull("key must not expire anymore",
|
||||
modified.canonicalize(new OperationLog(), 0).getPublicKey().getExpiryTime());
|
||||
// make sure the unsafe one behaves incorrectly as expected
|
||||
Assert.assertNotNull("unsafe expiry must yield wrong result from revoked user id",
|
||||
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
|
||||
}
|
||||
|
||||
{ // if we revoke everything, nothing is left to properly sign...
|
||||
@ -609,7 +621,7 @@ public class PgpKeyOperationTest {
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertTrue("subkey must actually be revoked",
|
||||
modified.getPublicKey().isRevoked());
|
||||
modified.getPublicKey().isMaybeRevoked());
|
||||
|
||||
}
|
||||
|
||||
@ -653,7 +665,7 @@ public class PgpKeyOperationTest {
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertTrue("subkey must actually be revoked",
|
||||
modified.getPublicKey(keyId).isRevoked());
|
||||
modified.getPublicKey(keyId).isMaybeRevoked());
|
||||
}
|
||||
|
||||
{ // re-add second subkey
|
||||
@ -691,7 +703,7 @@ public class PgpKeyOperationTest {
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertFalse("subkey must no longer be revoked",
|
||||
modified.getPublicKey(keyId).isRevoked());
|
||||
modified.getPublicKey(keyId).isMaybeRevoked());
|
||||
Assert.assertEquals("subkey must have the same usage flags as before",
|
||||
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
|
||||
|
||||
|
@ -451,7 +451,7 @@
|
||||
android:value=".ui.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.QrCodeScanActivity"
|
||||
android:name=".ui.ImportKeysProxyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
@ -478,6 +478,14 @@
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<!-- NFC: Handle NFC tags detected from outside our application -->
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<!-- MIME type as defined in http://tools.ietf.org/html/rfc3156 -->
|
||||
<data android:mimeType="application/pgp-keys" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@ -502,14 +510,6 @@
|
||||
-->
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<!-- NFC: Handle NFC tags detected from outside our application -->
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<!-- MIME type as defined in http://tools.ietf.org/html/rfc3156 -->
|
||||
<data android:mimeType="application/pgp-keys" />
|
||||
</intent-filter>
|
||||
<!-- VIEW with file endings: *.gpg (e.g. to import from OI File Manager) -->
|
||||
<intent-filter android:label="@string/intent_import_key">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
@ -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();
|
||||
|
@ -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),
|
||||
|
@ -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));
|
||||
|
@ -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();
|
||||
|
@ -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() {
|
||||
|
@ -22,6 +22,7 @@ import android.content.Context;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.spongycastle.bcpg.ArmoredInputStream;
|
||||
import org.spongycastle.openpgp.PGPCompressedData;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
@ -46,6 +47,10 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
@ -83,6 +88,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
private boolean mDecryptMetadataOnly;
|
||||
private byte[] mDecryptedSessionKey;
|
||||
private byte[] mDetachedSignature;
|
||||
private String mRequiredSignerFingerprint;
|
||||
private boolean mSignedLiteralData;
|
||||
|
||||
protected PgpDecryptVerify(Builder builder) {
|
||||
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
|
||||
@ -97,6 +104,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
|
||||
this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
|
||||
this.mDetachedSignature = builder.mDetachedSignature;
|
||||
this.mSignedLiteralData = builder.mSignedLiteralData;
|
||||
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@ -114,6 +123,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
private boolean mDecryptMetadataOnly = false;
|
||||
private byte[] mDecryptedSessionKey = null;
|
||||
private byte[] mDetachedSignature = null;
|
||||
private String mRequiredSignerFingerprint = null;
|
||||
private boolean mSignedLiteralData = false;
|
||||
|
||||
public Builder(Context context, ProviderHelper providerHelper,
|
||||
Progressable progressable,
|
||||
@ -125,6 +136,24 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
mOutStream = outStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used when verifying signed literals to check that they are signed with
|
||||
* the required key
|
||||
*/
|
||||
public Builder setRequiredSignerFingerprint(String fingerprint) {
|
||||
mRequiredSignerFingerprint = fingerprint;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is to force a mode where the message is just the signature key id and
|
||||
* then a literal data packet; used in Keybase.io proofs
|
||||
*/
|
||||
public Builder setSignedLiteralData(boolean signedLiteralData) {
|
||||
mSignedLiteralData = signedLiteralData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
|
||||
mAllowSymmetricDecryption = allowSymmetricDecryption;
|
||||
return this;
|
||||
@ -189,7 +218,9 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
// it is ascii armored
|
||||
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
|
||||
|
||||
if (aIn.isClearText()) {
|
||||
if (mSignedLiteralData) {
|
||||
return verifySignedLiteralData(aIn, 0);
|
||||
} else if (aIn.isClearText()) {
|
||||
// a cleartext signature, verify it with the other method
|
||||
return verifyCleartextSignature(aIn, 0);
|
||||
} else {
|
||||
@ -213,6 +244,136 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify Keybase.io style signed literal data
|
||||
*/
|
||||
private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent) throws IOException, PGPException {
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_VL, indent);
|
||||
|
||||
// thinking that the proof-fetching operation is going to take most of the time
|
||||
updateProgress(R.string.progress_reading_data, 75, 100);
|
||||
|
||||
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
|
||||
Object o = pgpF.nextObject();
|
||||
if (o instanceof PGPCompressedData) {
|
||||
log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1);
|
||||
|
||||
pgpF = new JcaPGPObjectFactory(((PGPCompressedData) o).getDataStream());
|
||||
o = pgpF.nextObject();
|
||||
updateProgress(R.string.progress_decompressing_data, 80, 100);
|
||||
}
|
||||
|
||||
// all we want to see is a OnePassSignatureList followed by LiteralData
|
||||
if (!(o instanceof PGPOnePassSignatureList)) {
|
||||
log.add(LogType.MSG_VL_ERROR_MISSING_SIGLIST, indent);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) o;
|
||||
|
||||
// go through all signatures (should be just one), make sure we have
|
||||
// the key and it matches the one we’re looking for
|
||||
CanonicalizedPublicKeyRing signingRing = null;
|
||||
CanonicalizedPublicKey signingKey = null;
|
||||
int signatureIndex = -1;
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
signatureIndex = i;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found, trying next signature...");
|
||||
}
|
||||
}
|
||||
|
||||
// there has to be a key, and it has to be the right one
|
||||
if (signingKey == null) {
|
||||
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
|
||||
Log.d(Constants.TAG, "Failed to find key in signed-literal message");
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
|
||||
if (!(mRequiredSignerFingerprint.equals(fingerprint))) {
|
||||
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
|
||||
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint +
|
||||
" got " + fingerprint + "!");
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
|
||||
PGPOnePassSignature signature = sigList.get(signatureIndex);
|
||||
signatureResultBuilder.initValid(signingRing, signingKey);
|
||||
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
|
||||
|
||||
o = pgpF.nextObject();
|
||||
|
||||
if (!(o instanceof PGPLiteralData)) {
|
||||
log.add(LogType.MSG_VL_ERROR_MISSING_LITERAL, indent);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
PGPLiteralData literalData = (PGPLiteralData) o;
|
||||
|
||||
log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1);
|
||||
updateProgress(R.string.progress_decrypting, 85, 100);
|
||||
|
||||
InputStream dataIn = literalData.getInputStream();
|
||||
|
||||
int length;
|
||||
byte[] buffer = new byte[1 << 16];
|
||||
while ((length = dataIn.read(buffer)) > 0) {
|
||||
mOutStream.write(buffer, 0, length);
|
||||
signature.update(buffer, 0, length);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_verifying_signature, 95, 100);
|
||||
log.add(LogType.MSG_VL_CLEAR_SIGNATURE_CHECK, indent + 1);
|
||||
|
||||
PGPSignatureList signatureList = (PGPSignatureList) pgpF.nextObject();
|
||||
PGPSignature messageSignature = signatureList.get(signatureIndex);
|
||||
|
||||
// these are not cleartext signatures!
|
||||
// TODO: what about binary signatures?
|
||||
signatureResultBuilder.setSignatureOnly(false);
|
||||
|
||||
// Verify signature and check binding signatures
|
||||
boolean validSignature = signature.verify(messageSignature);
|
||||
if (validSignature) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
||||
}
|
||||
signatureResultBuilder.setValidSignature(validSignature);
|
||||
|
||||
OpenPgpSignatureResult signatureResult = signatureResultBuilder.build();
|
||||
|
||||
if (signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED
|
||||
&& signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED) {
|
||||
log.add(LogType.MSG_VL_ERROR_INTEGRITY_CHECK, indent);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
log.add(LogType.MSG_VL_OK, indent);
|
||||
|
||||
// Return a positive result, with metadata and verification info
|
||||
DecryptVerifyResult result =
|
||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||
result.setSignatureResult(signatureResult);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decrypt and/or verifies binary or ascii armored pgp
|
||||
*/
|
||||
@ -672,6 +833,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
// If no valid signature is present:
|
||||
// Handle missing integrity protection like failed integrity protection!
|
||||
// The MDC packet can be stripped by an attacker!
|
||||
Log.d(Constants.TAG, "MDC fail");
|
||||
if (!signatureResultBuilder.isValidSignature()) {
|
||||
log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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!!");
|
||||
}
|
||||
|
||||
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
|
||||
if (item.isRevoked) {
|
||||
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);
|
||||
|
@ -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,9 +540,8 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_UPLOAD_KEYRING:
|
||||
|
||||
}
|
||||
case ACTION_UPLOAD_KEYRING: {
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
@ -444,7 +566,23 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
}
|
||||
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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
// search config
|
||||
{
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
Preferences.CloudSearchPrefs cloudPrefs =
|
||||
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||
}
|
||||
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
|
||||
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
|
||||
selectedEntries.add(keyEntry);
|
||||
|
||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
|
||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyRings);
|
||||
|
||||
// Send all information needed to service to query keys in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
@ -219,6 +253,20 @@ public class QrCodeScanActivity extends FragmentActivity {
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC: Parses the NDEF Message from the intent and prints to the TextView
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
void handleActionNdefDiscovered(Intent intent) {
|
||||
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
|
||||
// only one message sent during the beam
|
||||
NdefMessage msg = (NdefMessage) rawMsgs[0];
|
||||
// record 0 contains the MIME type, record 1 is the AAR, if present
|
||||
byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload();
|
||||
importKeys(receivedKeyringBytes);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
@ -26,10 +26,11 @@ import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -47,7 +48,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
|
||||
@ -62,14 +62,13 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
|
||||
private TextView mFingerprint;
|
||||
private ImageView mFingerprintQrCode;
|
||||
private ImageView mQrCode;
|
||||
private CardView mQrCodeLayout;
|
||||
private View mFingerprintShareButton;
|
||||
private View mFingerprintClipboardButton;
|
||||
private View mKeyShareButton;
|
||||
private View mKeyClipboardButton;
|
||||
private ImageButton mKeySafeSlingerButton;
|
||||
private View mNfcHelpButton;
|
||||
private View mNfcPrefsButton;
|
||||
private View mKeyUploadButton;
|
||||
|
||||
ProviderHelper mProviderHelper;
|
||||
@ -86,26 +85,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
||||
mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity());
|
||||
|
||||
mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint);
|
||||
mFingerprintQrCode = (ImageView) view.findViewById(R.id.view_key_fingerprint_qr_code_image);
|
||||
mQrCode = (ImageView) view.findViewById(R.id.view_key_qr_code);
|
||||
mQrCodeLayout = (CardView) view.findViewById(R.id.view_key_qr_code_layout);
|
||||
mFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
|
||||
mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
|
||||
mKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
|
||||
mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
|
||||
mKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger);
|
||||
mNfcHelpButton = view.findViewById(R.id.view_key_action_nfc_help);
|
||||
mNfcPrefsButton = view.findViewById(R.id.view_key_action_nfc_prefs);
|
||||
mKeyUploadButton = view.findViewById(R.id.view_key_action_upload);
|
||||
|
||||
mKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
mNfcPrefsButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mNfcPrefsButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mFingerprintQrCode.setOnClickListener(new View.OnClickListener() {
|
||||
mQrCodeLayout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
showQrCodeDialog();
|
||||
@ -142,18 +134,6 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
||||
startSafeSlinger(mDataUri);
|
||||
}
|
||||
});
|
||||
mNfcHelpButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
showNfcHelpDialog();
|
||||
}
|
||||
});
|
||||
mNfcPrefsButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
showNfcPrefs();
|
||||
}
|
||||
});
|
||||
mKeyUploadButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -239,20 +219,18 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
||||
|
||||
private void showQrCodeDialog() {
|
||||
Intent qrCodeIntent = new Intent(getActivity(), QrCodeViewActivity.class);
|
||||
|
||||
// create the transition animation - the images in the layouts
|
||||
// of both activities are defined with android:transitionName="qr_code"
|
||||
Bundle opts = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
ActivityOptions options = ActivityOptions
|
||||
.makeSceneTransitionAnimation(getActivity(), mQrCodeLayout, "qr_code");
|
||||
opts = options.toBundle();
|
||||
}
|
||||
|
||||
qrCodeIntent.setData(mDataUri);
|
||||
startActivity(qrCodeIntent);
|
||||
}
|
||||
|
||||
private void showNfcHelpDialog() {
|
||||
ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance();
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "shareNfcDialog");
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
private void showNfcPrefs() {
|
||||
Intent intentSettings = new Intent(
|
||||
Settings.ACTION_NFCSHARING_SETTINGS);
|
||||
startActivity(intentSettings);
|
||||
ActivityCompat.startActivity(getActivity(), qrCodeIntent, opts);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -363,14 +341,14 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
||||
// scale the image up to our actual size. we do this in code rather
|
||||
// than let the ImageView do this because we don't require filtering.
|
||||
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
|
||||
mFingerprintQrCode.getHeight(), mFingerprintQrCode.getHeight(),
|
||||
mQrCode.getHeight(), mQrCode.getHeight(),
|
||||
false);
|
||||
mFingerprintQrCode.setImageBitmap(scaled);
|
||||
mQrCode.setImageBitmap(scaled);
|
||||
|
||||
// simple fade-in animation
|
||||
AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
|
||||
anim.setDuration(200);
|
||||
mFingerprintQrCode.startAnimation(anim);
|
||||
mQrCode.startAnimation(anim);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -6,10 +6,10 @@
|
||||
<TableLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:stretchColumns="1">
|
||||
|
||||
<TextView
|
||||
|
@ -3,10 +3,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp">
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.AutoCorrectAutoCompleteTextView
|
||||
android:id="@+id/add_user_id_address"
|
||||
|
@ -1,14 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/edit_subkey_expiry_no_expiry"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:checked="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -1,10 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="5dip"
|
||||
android:paddingRight="5dip">
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message"
|
||||
@ -25,10 +27,11 @@
|
||||
android:layout_weight="1"
|
||||
android:gravity="top|left"
|
||||
android:inputType="textMultiLine|textUri"
|
||||
android:lines="4"
|
||||
android:maxLines="10"
|
||||
android:minLines="2"
|
||||
android:scrollbars="vertical" />
|
||||
android:lines="1"
|
||||
android:maxLines="1"
|
||||
android:minLines="1"
|
||||
android:scrollbars="vertical"
|
||||
android:layout_gravity="center_vertical" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_browse"
|
||||
|
@ -3,8 +3,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/passphrase_no_passphrase"
|
||||
|
@ -71,94 +71,6 @@
|
||||
android:layout_height="1dip"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_key_action_edit"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:clickable="true"
|
||||
style="@style/SelectableItem"
|
||||
android:text="@string/key_view_action_edit"
|
||||
android:layout_weight="1"
|
||||
android:drawableRight="@drawable/ic_action_edit"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<View
|
||||
android:id="@+id/view_key_action_edit_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dip"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/view_key_action_encrypt_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||
android:clickable="true"
|
||||
style="@style/SelectableItem"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_key_action_encrypt_text_text"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/key_view_action_encrypt"
|
||||
android:layout_weight="1"
|
||||
android:drawableRight="@drawable/ic_action_secure"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dip"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="right"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_key_action_encrypt_files"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/key_view_action_encrypt_files"
|
||||
android:drawableRight="@drawable/ic_action_secure"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
style="@style/SelectableItem" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dip"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_key_action_update"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:clickable="true"
|
||||
style="@style/SelectableItem"
|
||||
android:text="@string/key_view_action_update"
|
||||
android:layout_weight="1"
|
||||
android:drawableRight="@drawable/ic_action_download"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -67,15 +68,26 @@
|
||||
android:layout_height="1dip"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/view_key_fingerprint_qr_code_image"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:id="@+id/view_key_qr_code_layout"
|
||||
android:transitionName="qr_code"
|
||||
android:visibility="visible"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_weight="1"
|
||||
style="@style/SelectableItem" />
|
||||
android:clickable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
card_view:cardBackgroundColor="@android:color/white"
|
||||
card_view:cardUseCompatPadding="true"
|
||||
card_view:cardCornerRadius="4dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/view_key_qr_code"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
<TextView
|
||||
style="@style/SectionHeader"
|
||||
@ -162,51 +174,6 @@
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dip"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/view_key_action_nfc_help"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||
android:clickable="true"
|
||||
style="@style/SelectableItem"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/key_view_action_share_nfc"
|
||||
android:layout_weight="1"
|
||||
android:drawableRight="@drawable/ic_action_help"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dip"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="right"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/view_key_action_nfc_prefs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_action_settings"
|
||||
android:layout_gravity="center_vertical"
|
||||
style="@style/SelectableItem" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
@ -1,6 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -8,7 +12,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/mainMessage"
|
||||
android:layout_margin="4dp"
|
||||
android:textAppearance="?android:textAppearanceMedium" />
|
||||
|
||||
</LinearLayout>
|
19
OpenKeychain/src/main/res/layout/view_key_keybase_proof.xml
Normal file
19
OpenKeychain/src/main/res/layout/view_key_keybase_proof.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:id="@+id/proof_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="6dip"
|
||||
android:text="1."
|
||||
style="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/proof_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="6dip"
|
||||
android:text="Posts to twitter as Timbray"
|
||||
style="?android:attr/textAppearanceMedium" />
|
||||
</TableRow>
|
105
OpenKeychain/src/main/res/layout/view_key_trust_fragment.xml
Normal file
105
OpenKeychain/src/main/res/layout/view_key_trust_fragment.xml
Normal file
@ -0,0 +1,105 @@
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331-->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:descendantFocusability="beforeDescendants"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<TextView
|
||||
style="@style/SectionHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/section_should_you_trust"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_key_trust_readout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_weight="1"
|
||||
style="?android:attr/textAppearanceMedium"/>
|
||||
|
||||
<TextView
|
||||
style="@style/SectionHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/section_cloud_evidence"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/view_key_trust_search_cloud"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||
android:clickable="true"
|
||||
android:paddingRight="4dp"
|
||||
style="@style/SelectableItem"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:paddingLeft="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/key_trust_start_cloud_search"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_action_search_cloud"
|
||||
android:layout_gravity="center_vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_key_trust_cloud_narrative"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginBottom="14dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_weight="1"
|
||||
style="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TableLayout
|
||||
android:id="@+id/view_key_proof_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_key_proof_verify_header"
|
||||
style="@style/SectionHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/section_proof_details"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_key_proof_verify_detail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_weight="1"
|
||||
style="?android:attr/textAppearanceMedium"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -46,6 +46,9 @@
|
||||
|
||||
<!-- section -->
|
||||
<string name="section_user_ids">"Identities"</string>
|
||||
<string name="section_should_you_trust">"Should you trust this key?"</string>
|
||||
<string name="section_proof_details">Proof verification</string>
|
||||
<string name="section_cloud_evidence">"Evidence from the cloud"</string>
|
||||
<string name="section_keys">"Subkeys"</string>
|
||||
<string name="section_cloud_search">"Cloud search"</string>
|
||||
<string name="section_general">"General"</string>
|
||||
@ -545,6 +548,7 @@
|
||||
<string name="key_view_tab_share">"Share"</string>
|
||||
<string name="key_view_tab_keys">"Subkeys"</string>
|
||||
<string name="key_view_tab_certs">"Certificates"</string>
|
||||
<string name="key_view_tab_keybase">"Keybase.io"</string>
|
||||
<string name="user_id_info_revoked_title">"Revoked"</string>
|
||||
<string name="user_id_info_revoked_text">"This identity has been revoked by the key owner. It is no longer valid."</string>
|
||||
<string name="user_id_info_certified_title">"Certified"</string>
|
||||
@ -554,6 +558,47 @@
|
||||
<string name="user_id_info_invalid_title">"Invalid"</string>
|
||||
<string name="user_id_info_invalid_text">"Something is wrong with this identity!"</string>
|
||||
|
||||
<!-- Key trust -->
|
||||
<string name="key_trust_already_verified">"You have already certified this key!"</string>
|
||||
<string name="key_trust_it_is_yours">"This is one of your keys!"</string>
|
||||
<string name="key_trust_maybe">"This key is neither revoked nor expired.\nYou haven’t certified it, but you may choose to trust it."</string>
|
||||
<string name="key_trust_revoked">"This key has been revoked by its owner. You should not trust it."</string>
|
||||
<string name="key_trust_expired">"This key has expired. You should not trust it."</string>
|
||||
<string name="key_trust_old_keys">" It may be OK to use this to decrypt an old message dating from the time when this key was valid."</string>
|
||||
<string name="key_trust_no_cloud_evidence">"No evidence from the cloud on this key’s trustworthiness."</string>
|
||||
<string name="key_trust_start_cloud_search">"Start search"</string>
|
||||
<string name="key_trust_results_prefix">"Keybase.io offers “proofs” which assert that the owner of this key: "</string>
|
||||
|
||||
<!-- keybase proof stuff -->
|
||||
<string name="keybase_narrative_twitter">"Posts to Twitter as"</string>
|
||||
<string name="keybase_narrative_github">"Is known on GitHub as"</string>
|
||||
<string name="keybase_narrative_dns">"Controls the domain name(s)"</string>
|
||||
<string name="keybase_narrative_web_site">"Can post to the Web site(s)"</string>
|
||||
<string name="keybase_narrative_reddit">"Posts to Reddit as"</string>
|
||||
<string name="keybase_narrative_coinbase">"Is known on Coinbase as"</string>
|
||||
<string name="keybase_narrative_hackernews">"Posts to Hacker News as"</string>
|
||||
<string name="keybase_narrative_unknown">"Unknown proof type"</string>
|
||||
<string name="keybase_proof_failure">"Unfortunately this proof cannot be verified."</string>
|
||||
<string name="keybase_unknown_proof_failure">"Unrecognized problem with proof checker"</string>
|
||||
<string name="keybase_problem_fetching_evidence">"Problem with proof evidence"</string>
|
||||
<string name="keybase_key_mismatch">"Key fingerprint doesn’t match that in proof post"</string>
|
||||
<string name="keybase_dns_query_failure">"DNS TXT Record retrieval failed"</string>
|
||||
<string name="keybase_no_prover_found">"No proof checker found for"</string>
|
||||
<string name="keybase_message_payload_mismatch">"Decrypted proof post does not match expected value"</string>
|
||||
<string name="keybase_message_fetching_data">"Fetching proof evidence"</string>
|
||||
<string name="keybase_proof_succeeded">"This proof has been verified!"</string>
|
||||
<string name="keybase_a_post">"A post"</string>
|
||||
<string name="keybase_fetched_from">"fetched from"</string>
|
||||
<string name="keybase_for_the_domain">"for the domain"</string>
|
||||
<string name="keybase_contained_signature">"contains a message which could only have been created by the owner of this key."</string>
|
||||
<string name="keybase_twitter_proof">"A tweet"</string>
|
||||
<string name="keybase_dns_proof">"A DNS TXT record"</string>
|
||||
<string name="keybase_web_site_proof">"A text file"</string>
|
||||
<string name="keybase_github_proof">"A gist"</string>
|
||||
<string name="keybase_reddit_proof">"A JSON file"</string>
|
||||
<string name="keybase_reddit_attribution">"attributed by Reddit to"</string>
|
||||
<string name="keybase_verify">"Verify"</string>
|
||||
|
||||
<!-- Edit key -->
|
||||
<string name="edit_key_action_change_passphrase">"Change Passphrase"</string>
|
||||
<string name="edit_key_action_add_identity">"Add Identity"</string>
|
||||
@ -978,6 +1023,19 @@
|
||||
<string name="msg_dc_trail_unknown">"Encountered trailing data of unknown type"</string>
|
||||
<string name="msg_dc_unlocking">"Unlocking secret key"</string>
|
||||
|
||||
<!-- Messages for VerifySignedLiteralData operation -->
|
||||
<string name="msg_vl">"Starting signature check"</string>
|
||||
<string name="msg_vl_error_no_siglist">"No signature list in signed literal data"</string>
|
||||
<string name="msg_vl_error_wrong_key">"Message not signed with right key"</string>
|
||||
<string name="msg_vl_error_missing_literal">"No payload in signed literal data"</string>
|
||||
<string name="msg_vl_clear_meta_file">"Filename: %s"</string>
|
||||
<string name="msg_vl_clear_meta_mime">"MIME type: %s"</string>
|
||||
<string name="msg_vl_clear_meta_time">"Modification time: %s"</string>
|
||||
<string name="msg_vl_clear_meta_size">"Filesize: %s"</string>
|
||||
<string name="msg_vl_clear_signature_check">"Verifying signature data"</string>
|
||||
<string name="msg_vl_error_integrity_check">"Integrity check error!"</string>
|
||||
<string name="msg_vl_ok">"OK"</string>
|
||||
|
||||
<!-- Messages for SignEncrypt operation -->
|
||||
<string name="msg_se">"Starting sign/encrypt operation"</string>
|
||||
<string name="msg_se_input_bytes">"Processing input from byte array"</string>
|
||||
|
Loading…
Reference in New Issue
Block a user