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)));
|
ring.getPublicKey().getCreationTime().after(new Date(new Date().getTime()-1000*120)));
|
||||||
|
|
||||||
Assert.assertNull("key ring should not expire",
|
Assert.assertNull("key ring should not expire",
|
||||||
ring.getPublicKey().getExpiryTime());
|
ring.getPublicKey().getUnsafeExpiryTimeForTesting());
|
||||||
|
|
||||||
Assert.assertEquals("first (master) key can certify",
|
Assert.assertEquals("first (master) key can certify",
|
||||||
KeyFlags.CERTIFY_OTHER, (long) subkeys.get(0).getKeyUsage());
|
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("new key is not null", newKey);
|
||||||
Assert.assertNotNull("added key must have an expiry date",
|
Assert.assertNotNull("added key must have an expiry date",
|
||||||
newKey.getExpiryTime());
|
newKey.getUnsafeExpiryTimeForTesting());
|
||||||
Assert.assertEquals("added key must have expected expiry date",
|
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",
|
Assert.assertEquals("added key must have expected flags",
|
||||||
flags, (long) newKey.getKeyUsage());
|
flags, (long) newKey.getKeyUsage());
|
||||||
Assert.assertEquals("added key must have expected bitsize",
|
Assert.assertEquals("added key must have expected bitsize",
|
||||||
@ -403,9 +403,9 @@ public class PgpKeyOperationTest {
|
|||||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||||
|
|
||||||
Assert.assertNotNull("modified key must have an expiry date",
|
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",
|
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",
|
Assert.assertEquals("modified key must have same flags as before",
|
||||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||||
}
|
}
|
||||||
@ -417,9 +417,9 @@ public class PgpKeyOperationTest {
|
|||||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||||
|
|
||||||
Assert.assertNotNull("modified key must have an expiry date",
|
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",
|
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",
|
Assert.assertEquals("modified key must have same flags as before",
|
||||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||||
}
|
}
|
||||||
@ -443,9 +443,9 @@ public class PgpKeyOperationTest {
|
|||||||
Assert.assertEquals("modified key must have expected flags",
|
Assert.assertEquals("modified key must have expected flags",
|
||||||
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
|
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
|
||||||
Assert.assertNotNull("key must retain its expiry",
|
Assert.assertNotNull("key must retain its expiry",
|
||||||
modified.getPublicKey(keyId).getExpiryTime());
|
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
|
||||||
Assert.assertEquals("key expiry must be unchanged",
|
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"
|
{ // expiry of 0 should be "no expiry"
|
||||||
@ -463,7 +463,7 @@ public class PgpKeyOperationTest {
|
|||||||
Assert.assertEquals("signature must have been created by master key",
|
Assert.assertEquals("signature must have been created by master key",
|
||||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
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
|
{ // a past expiry should fail
|
||||||
@ -517,9 +517,9 @@ public class PgpKeyOperationTest {
|
|||||||
PacketTags.SIGNATURE, onlyB.get(1).tag);
|
PacketTags.SIGNATURE, onlyB.get(1).tag);
|
||||||
|
|
||||||
Assert.assertNotNull("modified key must have an expiry date",
|
Assert.assertNotNull("modified key must have an expiry date",
|
||||||
modified.getPublicKey().getExpiryTime());
|
modified.getPublicKey().getUnsafeExpiryTimeForTesting());
|
||||||
Assert.assertEquals("modified key must have expected expiry date",
|
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",
|
Assert.assertEquals("modified key must have same flags as before",
|
||||||
ring.getPublicKey().getKeyUsage(), modified.getPublicKey().getKeyUsage());
|
ring.getPublicKey().getKeyUsage(), modified.getPublicKey().getKeyUsage());
|
||||||
}
|
}
|
||||||
@ -531,9 +531,9 @@ public class PgpKeyOperationTest {
|
|||||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||||
|
|
||||||
Assert.assertNotNull("modified key must have an expiry date",
|
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",
|
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",
|
Assert.assertEquals("modified key must have same flags as before",
|
||||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||||
}
|
}
|
||||||
@ -547,17 +547,29 @@ public class PgpKeyOperationTest {
|
|||||||
Assert.assertEquals("modified key must have expected flags",
|
Assert.assertEquals("modified key must have expected flags",
|
||||||
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
|
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
|
||||||
Assert.assertNotNull("key must retain its expiry",
|
Assert.assertNotNull("key must retain its expiry",
|
||||||
modified.getPublicKey(keyId).getExpiryTime());
|
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
|
||||||
Assert.assertEquals("key expiry must be unchanged",
|
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"
|
{ // 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.reset();
|
||||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, 0L));
|
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, 0L));
|
||||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
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...
|
{ // if we revoke everything, nothing is left to properly sign...
|
||||||
@ -609,7 +621,7 @@ public class PgpKeyOperationTest {
|
|||||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||||
|
|
||||||
Assert.assertTrue("subkey must actually be revoked",
|
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());
|
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||||
|
|
||||||
Assert.assertTrue("subkey must actually be revoked",
|
Assert.assertTrue("subkey must actually be revoked",
|
||||||
modified.getPublicKey(keyId).isRevoked());
|
modified.getPublicKey(keyId).isMaybeRevoked());
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // re-add second subkey
|
{ // re-add second subkey
|
||||||
@ -691,7 +703,7 @@ public class PgpKeyOperationTest {
|
|||||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||||
|
|
||||||
Assert.assertFalse("subkey must no longer be revoked",
|
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",
|
Assert.assertEquals("subkey must have the same usage flags as before",
|
||||||
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
|
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
|
||||||
|
|
||||||
|
@ -451,7 +451,7 @@
|
|||||||
android:value=".ui.MainActivity" />
|
android:value=".ui.MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.QrCodeScanActivity"
|
android:name=".ui.ImportKeysProxyActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
@ -478,6 +478,14 @@
|
|||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</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>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@ -502,14 +510,6 @@
|
|||||||
-->
|
-->
|
||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="text/plain" />
|
||||||
</intent-filter>
|
</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) -->
|
<!-- VIEW with file endings: *.gpg (e.g. to import from OI File Manager) -->
|
||||||
<intent-filter android:label="@string/intent_import_key">
|
<intent-filter android:label="@string/intent_import_key">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
@ -294,8 +294,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
mKeyId = key.getKeyId();
|
mKeyId = key.getKeyId();
|
||||||
mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(mKeyId);
|
mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(mKeyId);
|
||||||
|
|
||||||
mRevoked = key.isRevoked();
|
mRevoked = key.isMaybeRevoked();
|
||||||
mExpired = key.isExpired();
|
mExpired = key.isMaybeExpired();
|
||||||
mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint());
|
mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint());
|
||||||
mBitStrength = key.getBitStrength();
|
mBitStrength = key.getBitStrength();
|
||||||
mCurveOid = key.getCurveOid();
|
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_TRAIL_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_trail_unknown),
|
||||||
MSG_DC_UNLOCKING (LogLevel.INFO, R.string.msg_dc_unlocking),
|
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
|
// signencrypt
|
||||||
MSG_SE (LogLevel.START, R.string.msg_se),
|
MSG_SE (LogLevel.START, R.string.msg_se),
|
||||||
MSG_SE_INPUT_BYTES (LogLevel.INFO, R.string.msg_se_input_bytes),
|
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() {
|
public boolean isExpired() {
|
||||||
// Is the master key expired?
|
// Is the master key expired?
|
||||||
Date creationDate = getRing().getPublicKey().getCreationTime();
|
Date creationDate = getPublicKey().getCreationTime();
|
||||||
Date expiryDate = getRing().getPublicKey().getValidSeconds() > 0
|
Date expiryDate = getPublicKey().getExpiryTime();
|
||||||
? new Date(creationDate.getTime() + getRing().getPublicKey().getValidSeconds() * 1000) : null;
|
|
||||||
|
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
return creationDate.after(now) || (expiryDate != null && expiryDate.before(now));
|
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.bcpg.sig.KeyFlags;
|
||||||
import org.spongycastle.openpgp.PGPPublicKey;
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.spongycastle.openpgp.PGPSignature;
|
||||||
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
|
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
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.
|
/** Wrapper for a PGPPublicKey.
|
||||||
*
|
*
|
||||||
@ -100,6 +108,72 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
|
|||||||
return false;
|
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. */
|
/** Same method as superclass, but we make it public. */
|
||||||
public Integer getKeyUsage() {
|
public Integer getKeyUsage() {
|
||||||
return super.getKeyUsage();
|
return super.getKeyUsage();
|
||||||
|
@ -104,8 +104,8 @@ public class OpenPgpSignatureResultBuilder {
|
|||||||
setUserIds(signingRing.getUnorderedUserIds());
|
setUserIds(signingRing.getUnorderedUserIds());
|
||||||
|
|
||||||
// either master key is expired/revoked or this specific subkey is expired/revoked
|
// either master key is expired/revoked or this specific subkey is expired/revoked
|
||||||
setKeyExpired(signingRing.isExpired() || signingKey.isExpired());
|
setKeyExpired(signingRing.isExpired() || signingKey.isMaybeExpired());
|
||||||
setKeyRevoked(signingRing.isRevoked() || signingKey.isRevoked());
|
setKeyRevoked(signingRing.isRevoked() || signingKey.isMaybeRevoked());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpSignatureResult build() {
|
public OpenPgpSignatureResult build() {
|
||||||
|
@ -22,6 +22,7 @@ import android.content.Context;
|
|||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpMetadata;
|
import org.openintents.openpgp.OpenPgpMetadata;
|
||||||
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||||
import org.spongycastle.bcpg.ArmoredInputStream;
|
import org.spongycastle.bcpg.ArmoredInputStream;
|
||||||
import org.spongycastle.openpgp.PGPCompressedData;
|
import org.spongycastle.openpgp.PGPCompressedData;
|
||||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
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.DecryptVerifyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
@ -83,6 +88,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
private boolean mDecryptMetadataOnly;
|
private boolean mDecryptMetadataOnly;
|
||||||
private byte[] mDecryptedSessionKey;
|
private byte[] mDecryptedSessionKey;
|
||||||
private byte[] mDetachedSignature;
|
private byte[] mDetachedSignature;
|
||||||
|
private String mRequiredSignerFingerprint;
|
||||||
|
private boolean mSignedLiteralData;
|
||||||
|
|
||||||
protected PgpDecryptVerify(Builder builder) {
|
protected PgpDecryptVerify(Builder builder) {
|
||||||
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
|
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
|
||||||
@ -97,6 +104,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
|
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
|
||||||
this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
|
this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
|
||||||
this.mDetachedSignature = builder.mDetachedSignature;
|
this.mDetachedSignature = builder.mDetachedSignature;
|
||||||
|
this.mSignedLiteralData = builder.mSignedLiteralData;
|
||||||
|
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
@ -114,6 +123,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
private boolean mDecryptMetadataOnly = false;
|
private boolean mDecryptMetadataOnly = false;
|
||||||
private byte[] mDecryptedSessionKey = null;
|
private byte[] mDecryptedSessionKey = null;
|
||||||
private byte[] mDetachedSignature = null;
|
private byte[] mDetachedSignature = null;
|
||||||
|
private String mRequiredSignerFingerprint = null;
|
||||||
|
private boolean mSignedLiteralData = false;
|
||||||
|
|
||||||
public Builder(Context context, ProviderHelper providerHelper,
|
public Builder(Context context, ProviderHelper providerHelper,
|
||||||
Progressable progressable,
|
Progressable progressable,
|
||||||
@ -125,6 +136,24 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
mOutStream = outStream;
|
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) {
|
public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
|
||||||
mAllowSymmetricDecryption = allowSymmetricDecryption;
|
mAllowSymmetricDecryption = allowSymmetricDecryption;
|
||||||
return this;
|
return this;
|
||||||
@ -189,7 +218,9 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
// it is ascii armored
|
// it is ascii armored
|
||||||
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
|
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
|
// a cleartext signature, verify it with the other method
|
||||||
return verifyCleartextSignature(aIn, 0);
|
return verifyCleartextSignature(aIn, 0);
|
||||||
} else {
|
} 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
|
* Decrypt and/or verifies binary or ascii armored pgp
|
||||||
*/
|
*/
|
||||||
@ -672,6 +833,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
// If no valid signature is present:
|
// If no valid signature is present:
|
||||||
// Handle missing integrity protection like failed integrity protection!
|
// Handle missing integrity protection like failed integrity protection!
|
||||||
// The MDC packet can be stripped by an attacker!
|
// The MDC packet can be stripped by an attacker!
|
||||||
|
Log.d(Constants.TAG, "MDC fail");
|
||||||
if (!signatureResultBuilder.isValidSignature()) {
|
if (!signatureResultBuilder.isValidSignature()) {
|
||||||
log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent);
|
log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent);
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
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
|
// since this is the master key, this contains at least CERTIFY_OTHER
|
||||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||||
int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER;
|
int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER;
|
||||||
long masterKeyExpiry = masterPublicKey.getValidSeconds() == 0L ? 0L :
|
Date expiryTime = wsKR.getPublicKey().getExpiryTime();
|
||||||
masterPublicKey.getCreationTime().getTime() / 1000 + masterPublicKey.getValidSeconds();
|
long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L;
|
||||||
|
|
||||||
return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log);
|
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! */
|
/** The revocation signature is NOT checked here, so this may be false! */
|
||||||
public boolean isRevoked() {
|
public boolean isMaybeRevoked() {
|
||||||
return mPublicKey.getSignaturesOfType(isMasterKey()
|
return mPublicKey.getSignaturesOfType(isMasterKey()
|
||||||
? PGPSignature.KEY_REVOCATION
|
? PGPSignature.KEY_REVOCATION
|
||||||
: PGPSignature.SUBKEY_REVOCATION).hasNext();
|
: PGPSignature.SUBKEY_REVOCATION).hasNext();
|
||||||
@ -60,25 +60,8 @@ public class UncachedPublicKey {
|
|||||||
return mPublicKey.getCreationTime();
|
return mPublicKey.getCreationTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getExpiryTime() {
|
/** The revocation signature is NOT checked here, so this may be false! */
|
||||||
long seconds = mPublicKey.getValidSeconds();
|
public boolean isMaybeExpired() {
|
||||||
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() {
|
|
||||||
Date creationDate = mPublicKey.getCreationTime();
|
Date creationDate = mPublicKey.getCreationTime();
|
||||||
Date expiryDate = mPublicKey.getValidSeconds() > 0
|
Date expiryDate = mPublicKey.getValidSeconds() > 0
|
||||||
? new Date(creationDate.getTime() + mPublicKey.getValidSeconds() * 1000) : null;
|
? new Date(creationDate.getTime() + mPublicKey.getValidSeconds() * 1000) : null;
|
||||||
@ -358,4 +341,24 @@ public class UncachedPublicKey {
|
|||||||
return mCacheUsage;
|
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();
|
return mSig.getCreationTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getKeyExpirySeconds() {
|
||||||
|
return mSig.getHashedSubPackets().getKeyExpirationTime();
|
||||||
|
}
|
||||||
|
|
||||||
public ArrayList<WrappedSignature> getEmbeddedSignatures() {
|
public ArrayList<WrappedSignature> getEmbeddedSignatures() {
|
||||||
ArrayList<WrappedSignature> sigs = new ArrayList<>();
|
ArrayList<WrappedSignature> sigs = new ArrayList<>();
|
||||||
if (!mSig.hasSubpackets()) {
|
if (!mSig.hasSubpackets()) {
|
||||||
|
@ -473,7 +473,7 @@ public class ProviderHelper {
|
|||||||
item.selfCert = cert;
|
item.selfCert = cert;
|
||||||
item.isPrimary = cert.isPrimaryUserId();
|
item.isPrimary = cert.isPrimaryUserId();
|
||||||
} else {
|
} else {
|
||||||
item.isRevoked = true;
|
item.selfRevocation = cert;
|
||||||
log(LogType.MSG_IP_UID_REVOKED);
|
log(LogType.MSG_IP_UID_REVOKED);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -569,10 +569,11 @@ public class ProviderHelper {
|
|||||||
|
|
||||||
// NOTE self-certificates are already verified during canonicalization,
|
// NOTE self-certificates are already verified during canonicalization,
|
||||||
// AND we know there is at most one cert plus at most one revocation
|
// 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()) {
|
if (!cert.isRevocation()) {
|
||||||
item.selfCert = cert;
|
item.selfCert = cert;
|
||||||
} else {
|
} else {
|
||||||
item.isRevoked = true;
|
item.selfRevocation = cert;
|
||||||
log(LogType.MSG_IP_UAT_REVOKED);
|
log(LogType.MSG_IP_UAT_REVOKED);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -643,16 +644,21 @@ public class ProviderHelper {
|
|||||||
for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) {
|
for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) {
|
||||||
UserPacketItem item = uids.get(userIdRank);
|
UserPacketItem item = uids.get(userIdRank);
|
||||||
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
|
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
|
||||||
if (item.selfCert != null) {
|
|
||||||
// TODO get rid of "self verified" status? this cannot even happen anymore!
|
if (item.selfCert == null) {
|
||||||
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
|
throw new AssertionError("User ids MUST be self-certified at this point!!");
|
||||||
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// don't bother with trusted certs if the uid is revoked, anyways
|
||||||
if (item.isRevoked) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
|
||||||
|
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
|
||||||
|
|
||||||
// iterate over signatures
|
// iterate over signatures
|
||||||
for (int i = 0; i < item.trustedCerts.size() ; i++) {
|
for (int i = 0; i < item.trustedCerts.size() ; i++) {
|
||||||
WrappedSignature sig = item.trustedCerts.valueAt(i);
|
WrappedSignature sig = item.trustedCerts.valueAt(i);
|
||||||
@ -711,15 +717,16 @@ public class ProviderHelper {
|
|||||||
String userId;
|
String userId;
|
||||||
byte[] attributeData;
|
byte[] attributeData;
|
||||||
boolean isPrimary = false;
|
boolean isPrimary = false;
|
||||||
boolean isRevoked = false;
|
|
||||||
WrappedSignature selfCert;
|
WrappedSignature selfCert;
|
||||||
|
WrappedSignature selfRevocation;
|
||||||
LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<>();
|
LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(UserPacketItem o) {
|
public int compareTo(UserPacketItem o) {
|
||||||
// revoked keys always come last!
|
// revoked keys always come last!
|
||||||
if (isRevoked != o.isRevoked) {
|
//noinspection DoubleNegation
|
||||||
return isRevoked ? 1 : -1;
|
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.
|
// 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!
|
// 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.USER_ID, item.userId);
|
||||||
values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData);
|
values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData);
|
||||||
values.put(UserPackets.IS_PRIMARY, item.isPrimary);
|
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);
|
values.put(UserPackets.RANK, rank);
|
||||||
|
|
||||||
Uri uri = UserPackets.buildUserIdsUri(masterKeyId);
|
Uri uri = UserPackets.buildUserIdsUri(masterKeyId);
|
||||||
|
@ -26,7 +26,13 @@ import android.os.Message;
|
|||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.RemoteException;
|
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.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
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.ExportResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
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.PromoteKeyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
@ -62,10 +69,19 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
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
|
* 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
|
* 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_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_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA";
|
||||||
|
|
||||||
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
|
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
|
// encrypt, decrypt, import export
|
||||||
public static final String TARGET = "target";
|
public static final String TARGET = "target";
|
||||||
public static final String SOURCE = "source";
|
public static final String SOURCE = "source";
|
||||||
|
|
||||||
// possible targets:
|
// possible targets:
|
||||||
public static final int IO_BYTES = 1;
|
public static final int IO_BYTES = 1;
|
||||||
public static final int IO_URI = 2;
|
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_PASSPHRASE = "passphrase";
|
||||||
public static final String DECRYPT_NFC_DECRYPTED_SESSION_KEY = "nfc_decrypted_session_key";
|
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
|
// save keyring
|
||||||
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
|
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
|
||||||
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
|
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
|
||||||
@ -240,7 +263,7 @@ public class KeychainIntentService extends IntentService implements Progressable
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ACTION_DECRYPT_METADATA:
|
case ACTION_DECRYPT_METADATA: {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* Input */
|
/* Input */
|
||||||
@ -271,7 +294,106 @@ public class KeychainIntentService extends IntentService implements Progressable
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
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 {
|
try {
|
||||||
/* Input */
|
/* Input */
|
||||||
@ -313,6 +435,7 @@ public class KeychainIntentService extends IntentService implements Progressable
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case ACTION_DELETE: {
|
case ACTION_DELETE: {
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
@ -403,7 +526,7 @@ public class KeychainIntentService extends IntentService implements Progressable
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
case ACTION_SIGN_ENCRYPT:
|
case ACTION_SIGN_ENCRYPT: {
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
|
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
|
||||||
@ -417,9 +540,8 @@ public class KeychainIntentService extends IntentService implements Progressable
|
|||||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case ACTION_UPLOAD_KEYRING:
|
case ACTION_UPLOAD_KEYRING: {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
/* Input */
|
/* Input */
|
||||||
@ -444,7 +566,23 @@ public class KeychainIntentService extends IntentService implements Progressable
|
|||||||
}
|
}
|
||||||
break;
|
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) {
|
private void sendErrorToHandler(Exception e) {
|
||||||
@ -457,7 +595,6 @@ public class KeychainIntentService extends IntentService implements Progressable
|
|||||||
} else {
|
} else {
|
||||||
message = e.getMessage();
|
message = e.getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(Constants.TAG, "KeychainIntentService Exception: ", e);
|
Log.d(Constants.TAG, "KeychainIntentService Exception: ", e);
|
||||||
|
|
||||||
Bundle data = new Bundle();
|
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 = "message";
|
||||||
public static final String DATA_MESSAGE_ID = "message_id";
|
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;
|
Activity mActivity;
|
||||||
ProgressDialogFragment mProgressDialogFragment;
|
ProgressDialogFragment mProgressDialogFragment;
|
||||||
|
|
||||||
|
@ -17,17 +17,12 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.nfc.NdefMessage;
|
|
||||||
import android.nfc.NfcAdapter;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
@ -63,9 +58,6 @@ public class ImportKeysActivity extends BaseActivity {
|
|||||||
// Actions for internal use only:
|
// Actions for internal use only:
|
||||||
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
|
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
|
||||||
+ "IMPORT_KEY_FROM_FILE";
|
+ "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";
|
public static final String EXTRA_RESULT = "result";
|
||||||
|
|
||||||
// only used by ACTION_IMPORT_KEY
|
// only used by ACTION_IMPORT_KEY
|
||||||
@ -215,15 +207,6 @@ public class ImportKeysActivity extends BaseActivity {
|
|||||||
startListFragment(savedInstanceState, null, null, null);
|
startListFragment(savedInstanceState, null, null, null);
|
||||||
break;
|
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: {
|
default: {
|
||||||
startCloudFragment(savedInstanceState, null, false);
|
startCloudFragment(savedInstanceState, null, false);
|
||||||
startListFragment(savedInstanceState, null, null, null);
|
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;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.nfc.NdefMessage;
|
||||||
|
import android.nfc.NfcAdapter;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.widget.Toast;
|
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
|
* 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_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";
|
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;
|
returnResult = false;
|
||||||
new IntentIntegrator(this).initiateScan();
|
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 {
|
} else {
|
||||||
Log.e(Constants.TAG, "No valid scheme or action given!");
|
Log.e(Constants.TAG, "No valid scheme or action given!");
|
||||||
finish();
|
finish();
|
||||||
@ -116,6 +130,7 @@ public class QrCodeScanActivity extends FragmentActivity {
|
|||||||
returnResult(data);
|
returnResult(data);
|
||||||
} else {
|
} else {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
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) {
|
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
|
// Message is received after importing is done in KeychainIntentService
|
||||||
KeychainIntentServiceHandler serviceHandler = new KeychainIntentServiceHandler(
|
KeychainIntentServiceHandler serviceHandler = new KeychainIntentServiceHandler(
|
||||||
this,
|
this,
|
||||||
@ -180,34 +216,32 @@ public class QrCodeScanActivity extends FragmentActivity {
|
|||||||
return;
|
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_RESULT, result);
|
||||||
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds());
|
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS,
|
||||||
|
result.getImportedMasterKeyIds());
|
||||||
startActivityForResult(certifyIntent, 0);
|
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
|
// fill values for this action
|
||||||
Bundle data = new Bundle();
|
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);
|
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||||
|
}
|
||||||
|
|
||||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
|
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyRings);
|
||||||
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
|
|
||||||
selectedEntries.add(keyEntry);
|
|
||||||
|
|
||||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
|
|
||||||
|
|
||||||
|
// 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);
|
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
@ -219,6 +253,20 @@ public class QrCodeScanActivity extends FragmentActivity {
|
|||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
startService(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() {
|
private void scanQrCode() {
|
||||||
Intent scanQrCode = new Intent(getActivity(), QrCodeScanActivity.class);
|
Intent scanQrCode = new Intent(getActivity(), ImportKeysProxyActivity.class);
|
||||||
scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT);
|
scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_WITH_RESULT);
|
||||||
startActivityForResult(scanQrCode, 0);
|
startActivityForResult(scanQrCode, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +184,6 @@ public class ViewKeyActivity extends BaseActivity implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Prepare the loaders. Either re-connect with an existing ones,
|
// Prepare the loaders. Either re-connect with an existing ones,
|
||||||
// or start new ones.
|
// or start new ones.
|
||||||
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
||||||
@ -317,8 +316,8 @@ public class ViewKeyActivity extends BaseActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void scanQrCode() {
|
private void scanQrCode() {
|
||||||
Intent scanQrCode = new Intent(this, QrCodeScanActivity.class);
|
Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class);
|
||||||
scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT);
|
scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_WITH_RESULT);
|
||||||
startActivityForResult(scanQrCode, 0);
|
startActivityForResult(scanQrCode, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,7 +446,6 @@ public class ViewKeyActivity extends BaseActivity implements
|
|||||||
startActivityForResult(safeSlingerIntent, 0);
|
startActivityForResult(safeSlingerIntent, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load QR Code asynchronously and with a fade in animation
|
* Load QR Code asynchronously and with a fade in animation
|
||||||
*
|
*
|
||||||
|
@ -144,6 +144,11 @@ public class ViewKeyAdvActivity extends BaseActivity implements
|
|||||||
mTabsAdapter.addTab(ViewKeyAdvCertsFragment.class,
|
mTabsAdapter.addTab(ViewKeyAdvCertsFragment.class,
|
||||||
certsBundle, getString(R.string.key_view_tab_certs));
|
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
|
// update layout after operations
|
||||||
mSlidingTabLayout.setViewPager(mViewPager);
|
mSlidingTabLayout.setViewPager(mViewPager);
|
||||||
}
|
}
|
||||||
|
@ -37,15 +37,11 @@ import org.sufficientlysecure.keychain.Constants;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
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.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
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.adapter.UserIdsAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
|
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 org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -55,24 +51,15 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
|
|||||||
|
|
||||||
public static final String ARG_DATA_URI = "uri";
|
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 mActionCertify;
|
||||||
private View mActionCertifyText;
|
private View mActionCertifyText;
|
||||||
private ImageView mActionCertifyImage;
|
private ImageView mActionCertifyImage;
|
||||||
private View mActionUpdate;
|
|
||||||
|
|
||||||
private ListView mUserIds;
|
private ListView mUserIds;
|
||||||
|
|
||||||
private static final int LOADER_ID_UNIFIED = 0;
|
private static final int LOADER_ID_UNIFIED = 0;
|
||||||
private static final int LOADER_ID_USER_IDS = 1;
|
private static final int LOADER_ID_USER_IDS = 1;
|
||||||
|
|
||||||
// conservative attitude
|
|
||||||
private boolean mHasEncrypt = true;
|
|
||||||
|
|
||||||
private UserIdsAdapter mUserIdsAdapter;
|
private UserIdsAdapter mUserIdsAdapter;
|
||||||
|
|
||||||
private Uri mDataUri;
|
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());
|
View view = inflater.inflate(R.layout.view_key_adv_main_fragment, getContainer());
|
||||||
|
|
||||||
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
|
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);
|
mActionCertify = view.findViewById(R.id.view_key_action_certify);
|
||||||
mActionCertifyText = view.findViewById(R.id.view_key_action_certify_text);
|
mActionCertifyText = view.findViewById(R.id.view_key_action_certify_text);
|
||||||
mActionCertifyImage = (ImageView) view.findViewById(R.id.view_key_action_certify_image);
|
mActionCertifyImage = (ImageView) view.findViewById(R.id.view_key_action_certify_image);
|
||||||
// make certify image gray, like action icons
|
// make certify image gray, like action icons
|
||||||
mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
||||||
PorterDuff.Mode.SRC_IN);
|
PorterDuff.Mode.SRC_IN);
|
||||||
mActionUpdate = view.findViewById(R.id.view_key_action_update);
|
|
||||||
|
|
||||||
mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -139,37 +120,11 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
|
|||||||
|
|
||||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
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() {
|
mActionCertify.setOnClickListener(new View.OnClickListener() {
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
certify(mDataUri);
|
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);
|
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0);
|
||||||
mUserIds.setAdapter(mUserIdsAdapter);
|
mUserIds.setAdapter(mUserIdsAdapter);
|
||||||
@ -222,45 +177,23 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
|
|||||||
switch (loader.getId()) {
|
switch (loader.getId()) {
|
||||||
case LOADER_ID_UNIFIED: {
|
case LOADER_ID_UNIFIED: {
|
||||||
if (data.moveToFirst()) {
|
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 this key is revoked, it cannot be used for anything!
|
||||||
if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) {
|
if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) {
|
||||||
mActionEdit.setEnabled(false);
|
|
||||||
mActionCertify.setEnabled(false);
|
mActionCertify.setEnabled(false);
|
||||||
mActionCertifyText.setEnabled(false);
|
mActionCertifyText.setEnabled(false);
|
||||||
mActionEncryptText.setEnabled(false);
|
|
||||||
mActionEncryptTextText.setEnabled(false);
|
|
||||||
mActionEncryptFiles.setEnabled(false);
|
|
||||||
} else {
|
} else {
|
||||||
mActionEdit.setEnabled(true);
|
|
||||||
|
|
||||||
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
|
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
|
||||||
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
|
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
|
||||||
mActionCertify.setEnabled(false);
|
mActionCertify.setEnabled(false);
|
||||||
mActionCertifyText.setEnabled(false);
|
mActionCertifyText.setEnabled(false);
|
||||||
mActionEncryptText.setEnabled(false);
|
|
||||||
mActionEncryptTextText.setEnabled(false);
|
|
||||||
mActionEncryptFiles.setEnabled(false);
|
|
||||||
} else {
|
} else {
|
||||||
mActionCertify.setEnabled(true);
|
mActionCertify.setEnabled(true);
|
||||||
mActionCertifyText.setEnabled(true);
|
mActionCertifyText.setEnabled(true);
|
||||||
mActionEncryptText.setEnabled(true);
|
|
||||||
mActionEncryptTextText.setEnabled(true);
|
|
||||||
mActionEncryptFiles.setEnabled(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mHasEncrypt = data.getInt(INDEX_UNIFIED_HAS_ENCRYPT) != 0;
|
|
||||||
|
|
||||||
break;
|
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) {
|
private void certify(Uri dataUri) {
|
||||||
long keyId = 0;
|
long keyId = 0;
|
||||||
try {
|
try {
|
||||||
@ -342,10 +233,4 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
|
|||||||
startActivityForResult(certifyIntent, 0);
|
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;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.app.ActivityOptions;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@ -26,10 +26,11 @@ import android.net.Uri;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v7.widget.CardView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
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.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
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.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
|
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";
|
public static final String ARG_DATA_URI = "uri";
|
||||||
|
|
||||||
private TextView mFingerprint;
|
private TextView mFingerprint;
|
||||||
private ImageView mFingerprintQrCode;
|
private ImageView mQrCode;
|
||||||
|
private CardView mQrCodeLayout;
|
||||||
private View mFingerprintShareButton;
|
private View mFingerprintShareButton;
|
||||||
private View mFingerprintClipboardButton;
|
private View mFingerprintClipboardButton;
|
||||||
private View mKeyShareButton;
|
private View mKeyShareButton;
|
||||||
private View mKeyClipboardButton;
|
private View mKeyClipboardButton;
|
||||||
private ImageButton mKeySafeSlingerButton;
|
private ImageButton mKeySafeSlingerButton;
|
||||||
private View mNfcHelpButton;
|
|
||||||
private View mNfcPrefsButton;
|
|
||||||
private View mKeyUploadButton;
|
private View mKeyUploadButton;
|
||||||
|
|
||||||
ProviderHelper mProviderHelper;
|
ProviderHelper mProviderHelper;
|
||||||
@ -86,26 +85,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
|||||||
mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity());
|
mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity());
|
||||||
|
|
||||||
mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint);
|
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);
|
mFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
|
||||||
mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
|
mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
|
||||||
mKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
|
mKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
|
||||||
mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
|
mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
|
||||||
mKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger);
|
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);
|
mKeyUploadButton = view.findViewById(R.id.view_key_action_upload);
|
||||||
|
|
||||||
mKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
mKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
||||||
PorterDuff.Mode.SRC_IN);
|
PorterDuff.Mode.SRC_IN);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
mQrCodeLayout.setOnClickListener(new View.OnClickListener() {
|
||||||
mNfcPrefsButton.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
mNfcPrefsButton.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
mFingerprintQrCode.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
showQrCodeDialog();
|
showQrCodeDialog();
|
||||||
@ -142,18 +134,6 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
|||||||
startSafeSlinger(mDataUri);
|
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() {
|
mKeyUploadButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
@ -239,20 +219,18 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
|||||||
|
|
||||||
private void showQrCodeDialog() {
|
private void showQrCodeDialog() {
|
||||||
Intent qrCodeIntent = new Intent(getActivity(), QrCodeViewActivity.class);
|
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);
|
qrCodeIntent.setData(mDataUri);
|
||||||
startActivity(qrCodeIntent);
|
ActivityCompat.startActivity(getActivity(), qrCodeIntent, opts);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
// 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.
|
// than let the ImageView do this because we don't require filtering.
|
||||||
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
|
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
|
||||||
mFingerprintQrCode.getHeight(), mFingerprintQrCode.getHeight(),
|
mQrCode.getHeight(), mQrCode.getHeight(),
|
||||||
false);
|
false);
|
||||||
mFingerprintQrCode.setImageBitmap(scaled);
|
mQrCode.setImageBitmap(scaled);
|
||||||
|
|
||||||
// simple fade-in animation
|
// simple fade-in animation
|
||||||
AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
|
AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
|
||||||
anim.setDuration(200);
|
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
|
// destroyLoader view from holder
|
||||||
holder.userIdsList.removeAllViews();
|
holder.userIdsList.removeAllViews();
|
||||||
|
|
||||||
|
// we want conventional gpg UserIDs first, then Keybase ”proofs”
|
||||||
HashMap<String, HashSet<String>> mergedUserIds = entry.getMergedUserIds();
|
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();
|
String cUserId = pair.getKey();
|
||||||
HashSet<String> cEmails = pair.getValue();
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||||
@ -100,14 +101,20 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
|||||||
ProviderHelper.FIELD_TYPE_INTEGER
|
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;
|
hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1;
|
||||||
|
|
||||||
// Set message depending on which key it is.
|
// Set message depending on which key it is.
|
||||||
mMainMessage.setText(getString(
|
mMainMessage.setText(getString(
|
||||||
hasSecret ? R.string.secret_key_deletion_confirmation
|
hasSecret ? R.string.secret_key_deletion_confirmation
|
||||||
: R.string.public_key_deletetion_confirmation,
|
: R.string.public_key_deletetion_confirmation,
|
||||||
userId
|
name
|
||||||
));
|
));
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
dismiss();
|
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
|
<TableLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="16dp"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="16dp"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="24dp"
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="24dp"
|
||||||
android:stretchColumns="1">
|
android:stretchColumns="1">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingLeft="8dp"
|
android:paddingTop="16dp"
|
||||||
android:paddingRight="8dp"
|
android:paddingBottom="16dp"
|
||||||
android:paddingTop="8dp"
|
android:paddingLeft="24dp"
|
||||||
android:paddingBottom="8dp">
|
android:paddingRight="24dp">
|
||||||
|
|
||||||
<org.sufficientlysecure.keychain.ui.widget.AutoCorrectAutoCompleteTextView
|
<org.sufficientlysecure.keychain.ui.widget.AutoCorrectAutoCompleteTextView
|
||||||
android:id="@+id/add_user_id_address"
|
android:id="@+id/add_user_id_address"
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingRight="24dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/edit_subkey_expiry_no_expiry"
|
android:id="@+id/edit_subkey_expiry_no_expiry"
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:checked="true"
|
android:checked="true"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingLeft="5dip"
|
android:paddingTop="16dp"
|
||||||
android:paddingRight="5dip">
|
android:paddingBottom="16dp"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingRight="24dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/message"
|
android:id="@+id/message"
|
||||||
@ -25,10 +27,11 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:gravity="top|left"
|
android:gravity="top|left"
|
||||||
android:inputType="textMultiLine|textUri"
|
android:inputType="textMultiLine|textUri"
|
||||||
android:lines="4"
|
android:lines="1"
|
||||||
android:maxLines="10"
|
android:maxLines="1"
|
||||||
android:minLines="2"
|
android:minLines="1"
|
||||||
android:scrollbars="vertical" />
|
android:scrollbars="vertical"
|
||||||
|
android:layout_gravity="center_vertical" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/btn_browse"
|
android:id="@+id/btn_browse"
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingLeft="16dp"
|
android:paddingTop="16dp"
|
||||||
android:paddingRight="16dp">
|
android:paddingBottom="16dp"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingRight="24dp">
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/passphrase_no_passphrase"
|
android:id="@+id/passphrase_no_passphrase"
|
||||||
|
@ -71,94 +71,6 @@
|
|||||||
android:layout_height="1dip"
|
android:layout_height="1dip"
|
||||||
android:background="?android:attr/listDivider" />
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<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_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@ -67,15 +68,26 @@
|
|||||||
android:layout_height="1dip"
|
android:layout_height="1dip"
|
||||||
android:background="?android:attr/listDivider" />
|
android:background="?android:attr/listDivider" />
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/view_key_fingerprint_qr_code_image"
|
<android.support.v7.widget.CardView
|
||||||
android:paddingTop="8dp"
|
android:id="@+id/view_key_qr_code_layout"
|
||||||
android:paddingBottom="8dp"
|
android:transitionName="qr_code"
|
||||||
|
android:visibility="visible"
|
||||||
android:layout_width="200dp"
|
android:layout_width="200dp"
|
||||||
android:layout_height="200dp"
|
android:layout_height="200dp"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_weight="1"
|
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
|
<TextView
|
||||||
style="@style/SectionHeader"
|
style="@style/SectionHeader"
|
||||||
@ -162,51 +174,6 @@
|
|||||||
android:drawablePadding="8dp"
|
android:drawablePadding="8dp"
|
||||||
android:gravity="center_vertical" />
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingRight="24dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@ -8,7 +12,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/mainMessage"
|
android:id="@+id/mainMessage"
|
||||||
android:layout_margin="4dp"
|
|
||||||
android:textAppearance="?android:textAppearanceMedium" />
|
android:textAppearance="?android:textAppearanceMedium" />
|
||||||
|
|
||||||
</LinearLayout>
|
</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 -->
|
<!-- section -->
|
||||||
<string name="section_user_ids">"Identities"</string>
|
<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_keys">"Subkeys"</string>
|
||||||
<string name="section_cloud_search">"Cloud search"</string>
|
<string name="section_cloud_search">"Cloud search"</string>
|
||||||
<string name="section_general">"General"</string>
|
<string name="section_general">"General"</string>
|
||||||
@ -545,6 +548,7 @@
|
|||||||
<string name="key_view_tab_share">"Share"</string>
|
<string name="key_view_tab_share">"Share"</string>
|
||||||
<string name="key_view_tab_keys">"Subkeys"</string>
|
<string name="key_view_tab_keys">"Subkeys"</string>
|
||||||
<string name="key_view_tab_certs">"Certificates"</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_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_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>
|
<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_title">"Invalid"</string>
|
||||||
<string name="user_id_info_invalid_text">"Something is wrong with this identity!"</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 -->
|
<!-- Edit key -->
|
||||||
<string name="edit_key_action_change_passphrase">"Change Passphrase"</string>
|
<string name="edit_key_action_change_passphrase">"Change Passphrase"</string>
|
||||||
<string name="edit_key_action_add_identity">"Add Identity"</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_trail_unknown">"Encountered trailing data of unknown type"</string>
|
||||||
<string name="msg_dc_unlocking">"Unlocking secret key"</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 -->
|
<!-- Messages for SignEncrypt operation -->
|
||||||
<string name="msg_se">"Starting sign/encrypt operation"</string>
|
<string name="msg_se">"Starting sign/encrypt operation"</string>
|
||||||
<string name="msg_se_input_bytes">"Processing input from byte array"</string>
|
<string name="msg_se_input_bytes">"Processing input from byte array"</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user