Merge branch 'development' of https://github.com/open-keychain/open-keychain into development

This commit is contained in:
Adithya Abraham Philip 2015-02-28 01:30:41 +05:30
commit 960a163185
37 changed files with 1304 additions and 652 deletions

View File

@ -231,7 +231,7 @@ public class PgpKeyOperationTest {
ring.getPublicKey().getCreationTime().after(new Date(new Date().getTime()-1000*120)));
Assert.assertNull("key ring should not expire",
ring.getPublicKey().getExpiryTime());
ring.getPublicKey().getUnsafeExpiryTimeForTesting());
Assert.assertEquals("first (master) key can certify",
KeyFlags.CERTIFY_OTHER, (long) subkeys.get(0).getKeyUsage());
@ -342,9 +342,9 @@ public class PgpKeyOperationTest {
Assert.assertNotNull("new key is not null", newKey);
Assert.assertNotNull("added key must have an expiry date",
newKey.getExpiryTime());
newKey.getUnsafeExpiryTimeForTesting());
Assert.assertEquals("added key must have expected expiry date",
expiry, newKey.getExpiryTime().getTime()/1000);
expiry, newKey.getUnsafeExpiryTimeForTesting().getTime()/1000);
Assert.assertEquals("added key must have expected flags",
flags, (long) newKey.getKeyUsage());
Assert.assertEquals("added key must have expected bitsize",
@ -403,9 +403,9 @@ public class PgpKeyOperationTest {
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
Assert.assertNotNull("modified key must have an expiry date",
modified.getPublicKey(keyId).getExpiryTime());
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
Assert.assertEquals("modified key must have expected expiry date",
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000);
Assert.assertEquals("modified key must have same flags as before",
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
}
@ -417,9 +417,9 @@ public class PgpKeyOperationTest {
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
Assert.assertNotNull("modified key must have an expiry date",
modified.getPublicKey(keyId).getExpiryTime());
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
Assert.assertEquals("modified key must have expected expiry date",
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000);
Assert.assertEquals("modified key must have same flags as before",
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
}
@ -443,9 +443,9 @@ public class PgpKeyOperationTest {
Assert.assertEquals("modified key must have expected flags",
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
Assert.assertNotNull("key must retain its expiry",
modified.getPublicKey(keyId).getExpiryTime());
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
Assert.assertEquals("key expiry must be unchanged",
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000);
}
{ // expiry of 0 should be "no expiry"
@ -463,7 +463,7 @@ public class PgpKeyOperationTest {
Assert.assertEquals("signature must have been created by master key",
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getExpiryTime());
Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
}
{ // a past expiry should fail
@ -517,9 +517,9 @@ public class PgpKeyOperationTest {
PacketTags.SIGNATURE, onlyB.get(1).tag);
Assert.assertNotNull("modified key must have an expiry date",
modified.getPublicKey().getExpiryTime());
modified.getPublicKey().getUnsafeExpiryTimeForTesting());
Assert.assertEquals("modified key must have expected expiry date",
expiry, modified.getPublicKey().getExpiryTime().getTime() / 1000);
expiry, modified.getPublicKey().getUnsafeExpiryTimeForTesting().getTime() / 1000);
Assert.assertEquals("modified key must have same flags as before",
ring.getPublicKey().getKeyUsage(), modified.getPublicKey().getKeyUsage());
}
@ -531,9 +531,9 @@ public class PgpKeyOperationTest {
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
Assert.assertNotNull("modified key must have an expiry date",
modified.getPublicKey(keyId).getExpiryTime());
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
Assert.assertEquals("modified key must have expected expiry date",
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime() / 1000);
Assert.assertEquals("modified key must have same flags as before",
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
}
@ -547,17 +547,29 @@ public class PgpKeyOperationTest {
Assert.assertEquals("modified key must have expected flags",
flags, (long) modified.getPublicKey(keyId).getKeyUsage());
Assert.assertNotNull("key must retain its expiry",
modified.getPublicKey(keyId).getExpiryTime());
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
Assert.assertEquals("key expiry must be unchanged",
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
expiry, modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting().getTime()/1000);
}
{ // expiry of 0 should be "no expiry"
// even if there is a non-expiring user id while all others are revoked, it doesn't count!
// for this purpose we revoke one while they still have expiry times
parcel.reset();
parcel.mRevokeUserIds.add("aloe");
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
parcel.reset();
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, 0L));
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
Assert.assertNull("key must not expire anymore", modified.getPublicKey(keyId).getExpiryTime());
// for this check, it is relevant that we DON'T use the unsafe one!
Assert.assertNull("key must not expire anymore",
modified.canonicalize(new OperationLog(), 0).getPublicKey().getExpiryTime());
// make sure the unsafe one behaves incorrectly as expected
Assert.assertNotNull("unsafe expiry must yield wrong result from revoked user id",
modified.getPublicKey(keyId).getUnsafeExpiryTimeForTesting());
}
{ // if we revoke everything, nothing is left to properly sign...
@ -609,7 +621,7 @@ public class PgpKeyOperationTest {
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
Assert.assertTrue("subkey must actually be revoked",
modified.getPublicKey().isRevoked());
modified.getPublicKey().isMaybeRevoked());
}
@ -653,7 +665,7 @@ public class PgpKeyOperationTest {
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
Assert.assertTrue("subkey must actually be revoked",
modified.getPublicKey(keyId).isRevoked());
modified.getPublicKey(keyId).isMaybeRevoked());
}
{ // re-add second subkey
@ -691,7 +703,7 @@ public class PgpKeyOperationTest {
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
Assert.assertFalse("subkey must no longer be revoked",
modified.getPublicKey(keyId).isRevoked());
modified.getPublicKey(keyId).isMaybeRevoked());
Assert.assertEquals("subkey must have the same usage flags as before",
flags, (long) modified.getPublicKey(keyId).getKeyUsage());

View File

@ -451,7 +451,7 @@
android:value=".ui.MainActivity" />
</activity>
<activity
android:name=".ui.QrCodeScanActivity"
android:name=".ui.ImportKeysProxyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoDisplay"
@ -478,6 +478,14 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- NFC: Handle NFC tags detected from outside our application -->
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<!-- MIME type as defined in http://tools.ietf.org/html/rfc3156 -->
<data android:mimeType="application/pgp-keys" />
</intent-filter>
</activity>
<activity
@ -502,14 +510,6 @@
-->
<data android:mimeType="text/plain" />
</intent-filter>
<!-- NFC: Handle NFC tags detected from outside our application -->
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<!-- MIME type as defined in http://tools.ietf.org/html/rfc3156 -->
<data android:mimeType="application/pgp-keys" />
</intent-filter>
<!-- VIEW with file endings: *.gpg (e.g. to import from OI File Manager) -->
<intent-filter android:label="@string/intent_import_key">
<action android:name="android.intent.action.VIEW" />

View File

@ -294,8 +294,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mKeyId = key.getKeyId();
mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(mKeyId);
mRevoked = key.isRevoked();
mExpired = key.isExpired();
mRevoked = key.isMaybeRevoked();
mExpired = key.isMaybeExpired();
mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint());
mBitStrength = key.getBitStrength();
mCurveOid = key.getCurveOid();

View File

@ -586,6 +586,15 @@ public abstract class OperationResult implements Parcelable {
MSG_DC_TRAIL_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_trail_unknown),
MSG_DC_UNLOCKING (LogLevel.INFO, R.string.msg_dc_unlocking),
// verify signed literal data
MSG_VL (LogLevel.INFO, R.string.msg_vl),
MSG_VL_ERROR_MISSING_SIGLIST (LogLevel.ERROR, R.string.msg_vl_error_no_siglist),
MSG_VL_ERROR_MISSING_LITERAL (LogLevel.ERROR, R.string.msg_vl_error_missing_literal),
MSG_VL_ERROR_MISSING_KEY (LogLevel.ERROR, R.string.msg_vl_error_wrong_key),
MSG_VL_CLEAR_SIGNATURE_CHECK (LogLevel.DEBUG, R.string.msg_vl_clear_signature_check),
MSG_VL_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_vl_error_integrity_check),
MSG_VL_OK (LogLevel.OK, R.string.msg_vl_ok),
// signencrypt
MSG_SE (LogLevel.START, R.string.msg_se),
MSG_SE_INPUT_BYTES (LogLevel.INFO, R.string.msg_se_input_bytes),

View File

@ -79,9 +79,8 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
public boolean isExpired() {
// Is the master key expired?
Date creationDate = getRing().getPublicKey().getCreationTime();
Date expiryDate = getRing().getPublicKey().getValidSeconds() > 0
? new Date(creationDate.getTime() + getRing().getPublicKey().getValidSeconds() * 1000) : null;
Date creationDate = getPublicKey().getCreationTime();
Date expiryDate = getPublicKey().getExpiryTime();
Date now = new Date();
return creationDate.after(now) || (expiryDate != null && expiryDate.before(now));

View File

@ -20,8 +20,16 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
/** Wrapper for a PGPPublicKey.
*
@ -100,6 +108,72 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
return false;
}
public boolean isRevoked() {
return mPublicKey.getSignaturesOfType(isMasterKey()
? PGPSignature.KEY_REVOCATION
: PGPSignature.SUBKEY_REVOCATION).hasNext();
}
public boolean isExpired () {
Date expiry = getExpiryTime();
return expiry != null && expiry.before(new Date());
}
public long getValidSeconds() {
long seconds;
// the getValidSeconds method is unreliable for master keys. we need to iterate all
// user ids, then use the most recent certification from a non-revoked user id
if (isMasterKey()) {
Date latestCreation = null;
seconds = 0;
for (byte[] rawUserId : getUnorderedRawUserIds()) {
Iterator<WrappedSignature> sigs = getSignaturesForRawId(rawUserId);
// there is always a certification, so this call is safe
WrappedSignature sig = sigs.next();
// we know a user id has at most two sigs: one certification, one revocation.
// if the sig is a revocation, or there is another sig (which is a revocation),
// the data in this uid is not relevant
if (sig.isRevocation() || sigs.hasNext()) {
continue;
}
// this is our revocation, UNLESS there is a newer certificate!
if (latestCreation == null || latestCreation.before(sig.getCreationTime())) {
latestCreation = sig.getCreationTime();
seconds = sig.getKeyExpirySeconds();
}
}
} else {
seconds = mPublicKey.getValidSeconds();
}
return seconds;
}
public Date getExpiryTime() {
long seconds = getValidSeconds();
if (seconds > Integer.MAX_VALUE) {
Log.e(Constants.TAG, "error, expiry time too large");
return null;
}
if (seconds == 0) {
// no expiry
return null;
}
Date creationDate = getCreationTime();
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(creationDate);
calendar.add(Calendar.SECOND, (int) seconds);
return calendar.getTime();
}
/** Same method as superclass, but we make it public. */
public Integer getKeyUsage() {
return super.getKeyUsage();

View File

@ -104,8 +104,8 @@ public class OpenPgpSignatureResultBuilder {
setUserIds(signingRing.getUnorderedUserIds());
// either master key is expired/revoked or this specific subkey is expired/revoked
setKeyExpired(signingRing.isExpired() || signingKey.isExpired());
setKeyRevoked(signingRing.isRevoked() || signingKey.isRevoked());
setKeyExpired(signingRing.isExpired() || signingKey.isMaybeExpired());
setKeyRevoked(signingRing.isRevoked() || signingKey.isMaybeRevoked());
}
public OpenPgpSignatureResult build() {

View File

@ -22,6 +22,7 @@ import android.content.Context;
import android.webkit.MimeTypeMap;
import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.openpgp.PGPCompressedData;
import org.spongycastle.openpgp.PGPEncryptedData;
@ -46,6 +47,10 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@ -83,6 +88,8 @@ public class PgpDecryptVerify extends BaseOperation {
private boolean mDecryptMetadataOnly;
private byte[] mDecryptedSessionKey;
private byte[] mDetachedSignature;
private String mRequiredSignerFingerprint;
private boolean mSignedLiteralData;
protected PgpDecryptVerify(Builder builder) {
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
@ -97,6 +104,8 @@ public class PgpDecryptVerify extends BaseOperation {
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
this.mDetachedSignature = builder.mDetachedSignature;
this.mSignedLiteralData = builder.mSignedLiteralData;
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
}
public static class Builder {
@ -114,6 +123,8 @@ public class PgpDecryptVerify extends BaseOperation {
private boolean mDecryptMetadataOnly = false;
private byte[] mDecryptedSessionKey = null;
private byte[] mDetachedSignature = null;
private String mRequiredSignerFingerprint = null;
private boolean mSignedLiteralData = false;
public Builder(Context context, ProviderHelper providerHelper,
Progressable progressable,
@ -125,6 +136,24 @@ public class PgpDecryptVerify extends BaseOperation {
mOutStream = outStream;
}
/**
* This is used when verifying signed literals to check that they are signed with
* the required key
*/
public Builder setRequiredSignerFingerprint(String fingerprint) {
mRequiredSignerFingerprint = fingerprint;
return this;
}
/**
* This is to force a mode where the message is just the signature key id and
* then a literal data packet; used in Keybase.io proofs
*/
public Builder setSignedLiteralData(boolean signedLiteralData) {
mSignedLiteralData = signedLiteralData;
return this;
}
public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
mAllowSymmetricDecryption = allowSymmetricDecryption;
return this;
@ -189,7 +218,9 @@ public class PgpDecryptVerify extends BaseOperation {
// it is ascii armored
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
if (aIn.isClearText()) {
if (mSignedLiteralData) {
return verifySignedLiteralData(aIn, 0);
} else if (aIn.isClearText()) {
// a cleartext signature, verify it with the other method
return verifyCleartextSignature(aIn, 0);
} else {
@ -213,6 +244,136 @@ public class PgpDecryptVerify extends BaseOperation {
}
}
/**
* Verify Keybase.io style signed literal data
*/
private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent) throws IOException, PGPException {
OperationLog log = new OperationLog();
log.add(LogType.MSG_VL, indent);
// thinking that the proof-fetching operation is going to take most of the time
updateProgress(R.string.progress_reading_data, 75, 100);
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
Object o = pgpF.nextObject();
if (o instanceof PGPCompressedData) {
log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1);
pgpF = new JcaPGPObjectFactory(((PGPCompressedData) o).getDataStream());
o = pgpF.nextObject();
updateProgress(R.string.progress_decompressing_data, 80, 100);
}
// all we want to see is a OnePassSignatureList followed by LiteralData
if (!(o instanceof PGPOnePassSignatureList)) {
log.add(LogType.MSG_VL_ERROR_MISSING_SIGLIST, indent);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) o;
// go through all signatures (should be just one), make sure we have
// the key and it matches the one were looking for
CanonicalizedPublicKeyRing signingRing = null;
CanonicalizedPublicKey signingKey = null;
int signatureIndex = -1;
for (int i = 0; i < sigList.size(); ++i) {
try {
long sigKeyId = sigList.get(i).getKeyID();
signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
);
signingKey = signingRing.getPublicKey(sigKeyId);
signatureIndex = i;
} catch (ProviderHelper.NotFoundException e) {
Log.d(Constants.TAG, "key not found, trying next signature...");
}
}
// there has to be a key, and it has to be the right one
if (signingKey == null) {
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
Log.d(Constants.TAG, "Failed to find key in signed-literal message");
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
if (!(mRequiredSignerFingerprint.equals(fingerprint))) {
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint +
" got " + fingerprint + "!");
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
PGPOnePassSignature signature = sigList.get(signatureIndex);
signatureResultBuilder.initValid(signingRing, signingKey);
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
o = pgpF.nextObject();
if (!(o instanceof PGPLiteralData)) {
log.add(LogType.MSG_VL_ERROR_MISSING_LITERAL, indent);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
PGPLiteralData literalData = (PGPLiteralData) o;
log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1);
updateProgress(R.string.progress_decrypting, 85, 100);
InputStream dataIn = literalData.getInputStream();
int length;
byte[] buffer = new byte[1 << 16];
while ((length = dataIn.read(buffer)) > 0) {
mOutStream.write(buffer, 0, length);
signature.update(buffer, 0, length);
}
updateProgress(R.string.progress_verifying_signature, 95, 100);
log.add(LogType.MSG_VL_CLEAR_SIGNATURE_CHECK, indent + 1);
PGPSignatureList signatureList = (PGPSignatureList) pgpF.nextObject();
PGPSignature messageSignature = signatureList.get(signatureIndex);
// these are not cleartext signatures!
// TODO: what about binary signatures?
signatureResultBuilder.setSignatureOnly(false);
// Verify signature and check binding signatures
boolean validSignature = signature.verify(messageSignature);
if (validSignature) {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
} else {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
}
signatureResultBuilder.setValidSignature(validSignature);
OpenPgpSignatureResult signatureResult = signatureResultBuilder.build();
if (signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED
&& signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED) {
log.add(LogType.MSG_VL_ERROR_INTEGRITY_CHECK, indent);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
updateProgress(R.string.progress_done, 100, 100);
log.add(LogType.MSG_VL_OK, indent);
// Return a positive result, with metadata and verification info
DecryptVerifyResult result =
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setSignatureResult(signatureResult);
return result;
}
/**
* Decrypt and/or verifies binary or ascii armored pgp
*/
@ -672,6 +833,7 @@ public class PgpDecryptVerify extends BaseOperation {
// If no valid signature is present:
// Handle missing integrity protection like failed integrity protection!
// The MDC packet can be stripped by an attacker!
Log.d(Constants.TAG, "MDC fail");
if (!signatureResultBuilder.isValidSignature()) {
log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);

View File

@ -439,8 +439,8 @@ public class PgpKeyOperation {
// since this is the master key, this contains at least CERTIFY_OTHER
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER;
long masterKeyExpiry = masterPublicKey.getValidSeconds() == 0L ? 0L :
masterPublicKey.getCreationTime().getTime() / 1000 + masterPublicKey.getValidSeconds();
Date expiryTime = wsKR.getPublicKey().getExpiryTime();
long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L;
return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log);

View File

@ -50,7 +50,7 @@ public class UncachedPublicKey {
}
/** The revocation signature is NOT checked here, so this may be false! */
public boolean isRevoked() {
public boolean isMaybeRevoked() {
return mPublicKey.getSignaturesOfType(isMasterKey()
? PGPSignature.KEY_REVOCATION
: PGPSignature.SUBKEY_REVOCATION).hasNext();
@ -60,25 +60,8 @@ public class UncachedPublicKey {
return mPublicKey.getCreationTime();
}
public Date getExpiryTime() {
long seconds = mPublicKey.getValidSeconds();
if (seconds > Integer.MAX_VALUE) {
Log.e(Constants.TAG, "error, expiry time too large");
return null;
}
if (seconds == 0) {
// no expiry
return null;
}
Date creationDate = getCreationTime();
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(creationDate);
calendar.add(Calendar.SECOND, (int) seconds);
return calendar.getTime();
}
public boolean isExpired() {
/** The revocation signature is NOT checked here, so this may be false! */
public boolean isMaybeExpired() {
Date creationDate = mPublicKey.getCreationTime();
Date expiryDate = mPublicKey.getValidSeconds() > 0
? new Date(creationDate.getTime() + mPublicKey.getValidSeconds() * 1000) : null;
@ -358,4 +341,24 @@ public class UncachedPublicKey {
return mCacheUsage;
}
// this method relies on UNSAFE assumptions about the keyring, and should ONLY be used for
// TEST CASES!!
Date getUnsafeExpiryTimeForTesting () {
long valid = mPublicKey.getValidSeconds();
if (valid > Integer.MAX_VALUE) {
Log.e(Constants.TAG, "error, expiry time too large");
return null;
}
if (valid == 0) {
// no expiry
return null;
}
Date creationDate = getCreationTime();
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(creationDate);
calendar.add(Calendar.SECOND, (int) valid);
return calendar.getTime();
}
}

View File

@ -78,6 +78,10 @@ public class WrappedSignature {
return mSig.getCreationTime();
}
public long getKeyExpirySeconds() {
return mSig.getHashedSubPackets().getKeyExpirationTime();
}
public ArrayList<WrappedSignature> getEmbeddedSignatures() {
ArrayList<WrappedSignature> sigs = new ArrayList<>();
if (!mSig.hasSubpackets()) {

View File

@ -473,7 +473,7 @@ public class ProviderHelper {
item.selfCert = cert;
item.isPrimary = cert.isPrimaryUserId();
} else {
item.isRevoked = true;
item.selfRevocation = cert;
log(LogType.MSG_IP_UID_REVOKED);
}
continue;
@ -569,10 +569,11 @@ public class ProviderHelper {
// NOTE self-certificates are already verified during canonicalization,
// AND we know there is at most one cert plus at most one revocation
// AND the revocation only exists if there is no newer certification
if (!cert.isRevocation()) {
item.selfCert = cert;
} else {
item.isRevoked = true;
item.selfRevocation = cert;
log(LogType.MSG_IP_UAT_REVOKED);
}
continue;
@ -643,16 +644,21 @@ public class ProviderHelper {
for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) {
UserPacketItem item = uids.get(userIdRank);
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
if (item.selfCert != null) {
// TODO get rid of "self verified" status? this cannot even happen anymore!
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
if (item.selfCert == null) {
throw new AssertionError("User ids MUST be self-certified at this point!!");
}
// don't bother with trusted certs if the uid is revoked, anyways
if (item.isRevoked) {
if (item.selfRevocation != null) {
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation,
Certs.VERIFIED_SELF));
// don't bother with trusted certs if the uid is revoked, anyways
continue;
}
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
// iterate over signatures
for (int i = 0; i < item.trustedCerts.size() ; i++) {
WrappedSignature sig = item.trustedCerts.valueAt(i);
@ -711,15 +717,16 @@ public class ProviderHelper {
String userId;
byte[] attributeData;
boolean isPrimary = false;
boolean isRevoked = false;
WrappedSignature selfCert;
WrappedSignature selfRevocation;
LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<>();
@Override
public int compareTo(UserPacketItem o) {
// revoked keys always come last!
if (isRevoked != o.isRevoked) {
return isRevoked ? 1 : -1;
//noinspection DoubleNegation
if ( (selfRevocation != null) != (o.selfRevocation != null)) {
return selfRevocation != null ? 1 : -1;
}
// if one is a user id, but the other isn't, the user id always comes first.
// we compare for null values here, so != is the correct operator!
@ -1353,7 +1360,7 @@ public class ProviderHelper {
values.put(UserPackets.USER_ID, item.userId);
values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData);
values.put(UserPackets.IS_PRIMARY, item.isPrimary);
values.put(UserPackets.IS_REVOKED, item.isRevoked);
values.put(UserPackets.IS_REVOKED, item.selfRevocation != null);
values.put(UserPackets.RANK, rank);
Uri uri = UserPackets.buildUserIdsUri(masterKeyId);

View File

@ -26,7 +26,13 @@ import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import com.textuality.keybase.lib.Proof;
import com.textuality.keybase.lib.prover.Prover;
import org.json.JSONObject;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
@ -44,6 +50,7 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
@ -62,10 +69,19 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import de.measite.minidns.Client;
import de.measite.minidns.DNSMessage;
import de.measite.minidns.Question;
import de.measite.minidns.Record;
import de.measite.minidns.record.Data;
import de.measite.minidns.record.TXT;
/**
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
* data from the activities or other apps, queues these intents, executes them, and stops itself
@ -82,6 +98,8 @@ public class KeychainIntentService extends IntentService implements Progressable
public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY";
public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF";
public static final String ACTION_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA";
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
@ -106,6 +124,7 @@ public class KeychainIntentService extends IntentService implements Progressable
// encrypt, decrypt, import export
public static final String TARGET = "target";
public static final String SOURCE = "source";
// possible targets:
public static final int IO_BYTES = 1;
public static final int IO_URI = 2;
@ -120,6 +139,10 @@ public class KeychainIntentService extends IntentService implements Progressable
public static final String DECRYPT_PASSPHRASE = "passphrase";
public static final String DECRYPT_NFC_DECRYPTED_SESSION_KEY = "nfc_decrypted_session_key";
// keybase proof
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
public static final String KEYBASE_PROOF = "keybase_proof";
// save keyring
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
@ -240,7 +263,7 @@ public class KeychainIntentService extends IntentService implements Progressable
break;
}
case ACTION_DECRYPT_METADATA:
case ACTION_DECRYPT_METADATA: {
try {
/* Input */
@ -271,7 +294,106 @@ public class KeychainIntentService extends IntentService implements Progressable
}
break;
case ACTION_DECRYPT_VERIFY:
}
case ACTION_VERIFY_KEYBASE_PROOF: {
try {
Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF)));
setProgress(R.string.keybase_message_fetching_data, 0, 100);
Prover prover = Prover.findProverFor(proof);
if (prover == null) {
sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName());
return;
}
if (!prover.fetchProofData()) {
sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence));
return;
}
String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT);
if (!prover.checkFingerprint(requiredFingerprint)) {
sendProofError(getString(R.string.keybase_key_mismatch));
return;
}
String domain = prover.dnsTxtCheckRequired();
if (domain != null) {
DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
if (dnsQuery == null) {
sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure));
return;
}
Record[] records = dnsQuery.getAnswers();
List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
for (Record r : records) {
Data d = r.getPayload();
if (d instanceof TXT) {
extents.add(((TXT) d).getExtents());
}
}
if (!prover.checkDnsTxt(extents)) {
sendProofError(prover.getLog(), null);
return;
}
}
byte[] messageBytes = prover.getPgpMessage().getBytes();
if (prover.rawMessageCheckRequired()) {
InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes));
if (!prover.checkRawMessageBytes(messageByteStream)) {
sendProofError(prover.getLog(), null);
return;
}
}
// kind of awkward, but this whole class wants to pull bytes out of data
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES);
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes);
InputData inputData = createDecryptInputData(data);
OutputStream outStream = createCryptOutputStream(data);
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
this, new ProviderHelper(this), this,
inputData, outStream
);
builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint);
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
outStream.close();
if (!decryptVerifyResult.success()) {
OperationLog log = decryptVerifyResult.getLog();
OperationResult.LogEntryParcel lastEntry = null;
for (OperationResult.LogEntryParcel entry : log) {
lastEntry = entry;
}
sendProofError(getString(lastEntry.mType.getMsgId()));
return;
}
if (!prover.validate(outStream.toString())) {
sendProofError(getString(R.string.keybase_message_payload_mismatch));
return;
}
Bundle resultData = new Bundle();
resultData.putString(KeychainIntentServiceHandler.DATA_MESSAGE, "OK");
// these help the handler construct a useful human-readable message
resultData.putString(KeychainIntentServiceHandler.KEYBASE_PROOF_URL, prover.getProofUrl());
resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());
resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel());
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
}
case ACTION_DECRYPT_VERIFY: {
try {
/* Input */
@ -313,6 +435,7 @@ public class KeychainIntentService extends IntentService implements Progressable
}
break;
}
case ACTION_DELETE: {
// Input
@ -403,7 +526,7 @@ public class KeychainIntentService extends IntentService implements Progressable
break;
}
case ACTION_SIGN_ENCRYPT:
case ACTION_SIGN_ENCRYPT: {
// Input
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
@ -417,16 +540,15 @@ public class KeychainIntentService extends IntentService implements Progressable
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
break;
case ACTION_UPLOAD_KEYRING:
}
case ACTION_UPLOAD_KEYRING: {
try {
/* Input */
/* Input */
String keyServer = data.getString(UPLOAD_KEY_SERVER);
// and dataUri!
/* Operation */
/* Operation */
HkpKeyserver server = new HkpKeyserver(keyServer);
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
@ -443,8 +565,24 @@ public class KeychainIntentService extends IntentService implements Progressable
sendErrorToHandler(e);
}
break;
}
}
}
private void sendProofError(List<String> log, String label) {
String msg = null;
label = (label == null) ? "" : label + ": ";
for (String m : log) {
Log.e(Constants.TAG, label + m);
msg = m;
}
sendProofError(label + msg);
}
private void sendProofError(String msg) {
Bundle bundle = new Bundle();
bundle.putString(KeychainIntentServiceHandler.DATA_ERROR, msg);
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, bundle);
}
private void sendErrorToHandler(Exception e) {
@ -457,7 +595,6 @@ public class KeychainIntentService extends IntentService implements Progressable
} else {
message = e.getMessage();
}
Log.d(Constants.TAG, "KeychainIntentService Exception: ", e);
Bundle data = new Bundle();

View File

@ -45,6 +45,11 @@ public class KeychainIntentServiceHandler extends Handler {
public static final String DATA_MESSAGE = "message";
public static final String DATA_MESSAGE_ID = "message_id";
// keybase proof specific
public static final String KEYBASE_PROOF_URL = "keybase_proof_url";
public static final String KEYBASE_PRESENCE_URL = "keybase_presence_url";
public static final String KEYBASE_PRESENCE_LABEL = "keybase_presence_label";
Activity mActivity;
ProgressDialogFragment mProgressDialogFragment;

View File

@ -17,17 +17,12 @@
package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.view.View;
import android.view.View.OnClickListener;
@ -63,9 +58,6 @@ public class ImportKeysActivity extends BaseActivity {
// Actions for internal use only:
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_FILE";
public static final String ACTION_IMPORT_KEY_FROM_NFC = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_NFC";
public static final String EXTRA_RESULT = "result";
// only used by ACTION_IMPORT_KEY
@ -215,15 +207,6 @@ public class ImportKeysActivity extends BaseActivity {
startListFragment(savedInstanceState, null, null, null);
break;
}
case ACTION_IMPORT_KEY_FROM_NFC: {
// NOTE: this only displays the appropriate fragment, no actions are taken
startFileFragment(savedInstanceState);
// TODO!!!!!
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
break;
}
default: {
startCloudFragment(savedInstanceState, null, false);
startListFragment(savedInstanceState, null, null, null);
@ -433,50 +416,4 @@ public class ImportKeysActivity extends BaseActivity {
}
}
/**
* NFC
*/
@Override
public void onResume() {
super.onResume();
// Check to see if the Activity started due to an Android Beam
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
handleActionNdefDiscovered(getIntent());
} else {
Log.d(Constants.TAG, "NFC: No NDEF discovered!");
}
} else {
Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1");
}
}
/**
* NFC
*/
@Override
public void onNewIntent(Intent intent) {
// onResume gets called after this to handle the intent
setIntent(intent);
}
/**
* NFC: Parses the NDEF Message from the intent and prints to the TextView
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
void handleActionNdefDiscovered(Intent intent) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
// only one message sent during the beam
NdefMessage msg = (NdefMessage) rawMsgs[0];
// record 0 contains the MIME type, record 1 is the AAR, if present
byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload();
Intent importIntent = new Intent(this, ImportKeysActivity.class);
importIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY);
importIntent.putExtra(ImportKeysActivity.EXTRA_KEY_BYTES, receivedKeyringBytes);
handleActions(null, importIntent);
}
}

View File

@ -17,12 +17,17 @@
package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.support.v4.app.FragmentActivity;
import android.widget.Toast;
@ -48,7 +53,7 @@ import java.util.Locale;
/**
* Proxy activity (just a transparent content view) to scan QR Codes using the Barcode Scanner app
*/
public class QrCodeScanActivity extends FragmentActivity {
public class ImportKeysProxyActivity extends FragmentActivity {
public static final String ACTION_QR_CODE_API = OpenKeychainIntents.IMPORT_KEY_FROM_QR_CODE;
public static final String ACTION_SCAN_WITH_RESULT = Constants.INTENT_PREFIX + "SCAN_QR_CODE_WITH_RESULT";
@ -88,6 +93,15 @@ public class QrCodeScanActivity extends FragmentActivity {
returnResult = false;
new IntentIntegrator(this).initiateScan();
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
// Check to see if the Activity started due to an Android Beam
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
returnResult = false;
handleActionNdefDiscovered(getIntent());
} else {
Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1");
finish();
}
} else {
Log.e(Constants.TAG, "No valid scheme or action given!");
finish();
@ -116,6 +130,7 @@ public class QrCodeScanActivity extends FragmentActivity {
returnResult(data);
} else {
super.onActivityResult(requestCode, resultCode, data);
finish();
}
}
@ -146,7 +161,28 @@ public class QrCodeScanActivity extends FragmentActivity {
}
}
public void importKeys(byte[] keyringData) {
ParcelableKeyRing keyEntry = new ParcelableKeyRing(keyringData);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry);
startImportService(selectedEntries);
}
public void importKeys(String fingerprint) {
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry);
startImportService(selectedEntries);
}
private void startImportService (ArrayList<ParcelableKeyRing> keyRings) {
// Message is received after importing is done in KeychainIntentService
KeychainIntentServiceHandler serviceHandler = new KeychainIntentServiceHandler(
this,
@ -180,34 +216,32 @@ public class QrCodeScanActivity extends FragmentActivity {
return;
}
Intent certifyIntent = new Intent(QrCodeScanActivity.this, CertifyKeyActivity.class);
Intent certifyIntent = new Intent(ImportKeysProxyActivity.this,
CertifyKeyActivity.class);
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result);
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds());
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS,
result.getImportedMasterKeyIds());
startActivityForResult(certifyIntent, 0);
}
}
};
// search config
Preferences prefs = Preferences.getPreferences(this);
Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
// fill values for this action
Bundle data = new Bundle();
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
// search config
{
Preferences prefs = Preferences.getPreferences(this);
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
}
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry);
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyRings);
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
@ -219,6 +253,20 @@ public class QrCodeScanActivity extends FragmentActivity {
// start service with intent
startService(intent);
}
/**
* NFC: Parses the NDEF Message from the intent and prints to the TextView
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
void handleActionNdefDiscovered(Intent intent) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
// only one message sent during the beam
NdefMessage msg = (NdefMessage) rawMsgs[0];
// record 0 contains the MIME type, record 1 is the AAR, if present
byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload();
importKeys(receivedKeyringBytes);
}
}

View File

@ -602,8 +602,8 @@ public class KeyListFragment extends LoaderFragment
}
private void scanQrCode() {
Intent scanQrCode = new Intent(getActivity(), QrCodeScanActivity.class);
scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT);
Intent scanQrCode = new Intent(getActivity(), ImportKeysProxyActivity.class);
scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_WITH_RESULT);
startActivityForResult(scanQrCode, 0);
}

View File

@ -184,7 +184,6 @@ public class ViewKeyActivity extends BaseActivity implements
}
});
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
@ -317,8 +316,8 @@ public class ViewKeyActivity extends BaseActivity implements
}
private void scanQrCode() {
Intent scanQrCode = new Intent(this, QrCodeScanActivity.class);
scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT);
Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class);
scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_WITH_RESULT);
startActivityForResult(scanQrCode, 0);
}
@ -447,7 +446,6 @@ public class ViewKeyActivity extends BaseActivity implements
startActivityForResult(safeSlingerIntent, 0);
}
/**
* Load QR Code asynchronously and with a fade in animation
*

View File

@ -144,6 +144,11 @@ public class ViewKeyAdvActivity extends BaseActivity implements
mTabsAdapter.addTab(ViewKeyAdvCertsFragment.class,
certsBundle, getString(R.string.key_view_tab_certs));
Bundle trustBundle = new Bundle();
trustBundle.putParcelable(ViewKeyTrustFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyTrustFragment.class,
trustBundle, getString(R.string.key_view_tab_keybase));
// update layout after operations
mSlidingTabLayout.setViewPager(mViewPager);
}

View File

@ -37,15 +37,11 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Date;
@ -55,24 +51,15 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
public static final String ARG_DATA_URI = "uri";
private View mActionEdit;
private View mActionEditDivider;
private View mActionEncryptFiles;
private View mActionEncryptText;
private View mActionEncryptTextText;
private View mActionCertify;
private View mActionCertifyText;
private ImageView mActionCertifyImage;
private View mActionUpdate;
private ListView mUserIds;
private static final int LOADER_ID_UNIFIED = 0;
private static final int LOADER_ID_USER_IDS = 1;
// conservative attitude
private boolean mHasEncrypt = true;
private UserIdsAdapter mUserIdsAdapter;
private Uri mDataUri;
@ -83,18 +70,12 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
View view = inflater.inflate(R.layout.view_key_adv_main_fragment, getContainer());
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
mActionEdit = view.findViewById(R.id.view_key_action_edit);
mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider);
mActionEncryptText = view.findViewById(R.id.view_key_action_encrypt_text);
mActionEncryptTextText = view.findViewById(R.id.view_key_action_encrypt_text_text);
mActionEncryptFiles = view.findViewById(R.id.view_key_action_encrypt_files);
mActionCertify = view.findViewById(R.id.view_key_action_certify);
mActionCertifyText = view.findViewById(R.id.view_key_action_certify_text);
mActionCertifyImage = (ImageView) view.findViewById(R.id.view_key_action_certify_image);
// make certify image gray, like action icons
mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
PorterDuff.Mode.SRC_IN);
mActionUpdate = view.findViewById(R.id.view_key_action_update);
mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
@ -139,37 +120,11 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
mActionEncryptFiles.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encrypt(mDataUri, false);
}
});
mActionEncryptText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encrypt(mDataUri, true);
}
});
mActionCertify.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
certify(mDataUri);
}
});
mActionEdit.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
editKey(mDataUri);
}
});
mActionUpdate.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
try {
updateFromKeyserver(mDataUri, new ProviderHelper(getActivity()));
} catch (NotFoundException e) {
Notify.showNotify(getActivity(), R.string.error_key_not_found, Notify.Style.ERROR);
}
}
});
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0);
mUserIds.setAdapter(mUserIdsAdapter);
@ -222,45 +177,23 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
switch (loader.getId()) {
case LOADER_ID_UNIFIED: {
if (data.moveToFirst()) {
if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) {
// edit button
mActionEdit.setVisibility(View.VISIBLE);
mActionEditDivider.setVisibility(View.VISIBLE);
} else {
// edit button
mActionEdit.setVisibility(View.GONE);
mActionEditDivider.setVisibility(View.GONE);
}
// If this key is revoked, it cannot be used for anything!
if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) {
mActionEdit.setEnabled(false);
mActionCertify.setEnabled(false);
mActionCertifyText.setEnabled(false);
mActionEncryptText.setEnabled(false);
mActionEncryptTextText.setEnabled(false);
mActionEncryptFiles.setEnabled(false);
} else {
mActionEdit.setEnabled(true);
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
mActionCertify.setEnabled(false);
mActionCertifyText.setEnabled(false);
mActionEncryptText.setEnabled(false);
mActionEncryptTextText.setEnabled(false);
mActionEncryptFiles.setEnabled(false);
} else {
mActionCertify.setEnabled(true);
mActionCertifyText.setEnabled(true);
mActionEncryptText.setEnabled(true);
mActionEncryptTextText.setEnabled(true);
mActionEncryptFiles.setEnabled(true);
}
}
mHasEncrypt = data.getInt(INDEX_UNIFIED_HAS_ENCRYPT) != 0;
break;
}
}
@ -286,48 +219,6 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
}
}
private void encrypt(Uri dataUri, boolean text) {
// If there is no encryption key, don't bother.
if (!mHasEncrypt) {
Notify.showNotify(getActivity(), R.string.error_no_encrypt_subkey, Notify.Style.ERROR);
return;
}
try {
long keyId = new ProviderHelper(getActivity())
.getCachedPublicKeyRing(dataUri)
.extractOrGetMasterKeyId();
long[] encryptionKeyIds = new long[]{keyId};
Intent intent;
if (text) {
intent = new Intent(getActivity(), EncryptTextActivity.class);
intent.setAction(EncryptTextActivity.ACTION_ENCRYPT_TEXT);
intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
} else {
intent = new Intent(getActivity(), EncryptFilesActivity.class);
intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA);
intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
}
// used instead of startActivity set actionbar based on callingPackage
startActivityForResult(intent, 0);
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
}
}
private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper)
throws ProviderHelper.NotFoundException {
byte[] blob = (byte[]) providerHelper.getGenericData(
KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
Intent queryIntent = new Intent(getActivity(), ImportKeysActivity.class);
queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT);
queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint);
startActivityForResult(queryIntent, 0);
}
private void certify(Uri dataUri) {
long keyId = 0;
try {
@ -342,10 +233,4 @@ public class ViewKeyAdvMainFragment extends LoaderFragment implements
startActivityForResult(certifyIntent, 0);
}
private void editKey(Uri dataUri) {
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
startActivityForResult(editIntent, 0);
}
}

View File

@ -17,7 +17,7 @@
package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
@ -26,10 +26,11 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.widget.CardView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -47,7 +48,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
@ -62,14 +62,13 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
public static final String ARG_DATA_URI = "uri";
private TextView mFingerprint;
private ImageView mFingerprintQrCode;
private ImageView mQrCode;
private CardView mQrCodeLayout;
private View mFingerprintShareButton;
private View mFingerprintClipboardButton;
private View mKeyShareButton;
private View mKeyClipboardButton;
private ImageButton mKeySafeSlingerButton;
private View mNfcHelpButton;
private View mNfcPrefsButton;
private View mKeyUploadButton;
ProviderHelper mProviderHelper;
@ -86,26 +85,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity());
mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint);
mFingerprintQrCode = (ImageView) view.findViewById(R.id.view_key_fingerprint_qr_code_image);
mQrCode = (ImageView) view.findViewById(R.id.view_key_qr_code);
mQrCodeLayout = (CardView) view.findViewById(R.id.view_key_qr_code_layout);
mFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
mKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
mKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger);
mNfcHelpButton = view.findViewById(R.id.view_key_action_nfc_help);
mNfcPrefsButton = view.findViewById(R.id.view_key_action_nfc_prefs);
mKeyUploadButton = view.findViewById(R.id.view_key_action_upload);
mKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
PorterDuff.Mode.SRC_IN);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mNfcPrefsButton.setVisibility(View.VISIBLE);
} else {
mNfcPrefsButton.setVisibility(View.GONE);
}
mFingerprintQrCode.setOnClickListener(new View.OnClickListener() {
mQrCodeLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showQrCodeDialog();
@ -142,18 +134,6 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
startSafeSlinger(mDataUri);
}
});
mNfcHelpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showNfcHelpDialog();
}
});
mNfcPrefsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showNfcPrefs();
}
});
mKeyUploadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -239,20 +219,18 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
private void showQrCodeDialog() {
Intent qrCodeIntent = new Intent(getActivity(), QrCodeViewActivity.class);
// create the transition animation - the images in the layouts
// of both activities are defined with android:transitionName="qr_code"
Bundle opts = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ActivityOptions options = ActivityOptions
.makeSceneTransitionAnimation(getActivity(), mQrCodeLayout, "qr_code");
opts = options.toBundle();
}
qrCodeIntent.setData(mDataUri);
startActivity(qrCodeIntent);
}
private void showNfcHelpDialog() {
ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance();
dialog.show(getActivity().getSupportFragmentManager(), "shareNfcDialog");
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void showNfcPrefs() {
Intent intentSettings = new Intent(
Settings.ACTION_NFCSHARING_SETTINGS);
startActivity(intentSettings);
ActivityCompat.startActivity(getActivity(), qrCodeIntent, opts);
}
@Override
@ -363,14 +341,14 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
// scale the image up to our actual size. we do this in code rather
// than let the ImageView do this because we don't require filtering.
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
mFingerprintQrCode.getHeight(), mFingerprintQrCode.getHeight(),
mQrCode.getHeight(), mQrCode.getHeight(),
false);
mFingerprintQrCode.setImageBitmap(scaled);
mQrCode.setImageBitmap(scaled);
// simple fade-in animation
AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
anim.setDuration(200);
mFingerprintQrCode.startAnimation(anim);
mQrCode.startAnimation(anim);
}
}
};

View File

@ -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 were 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, dont 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, dont 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);
}
}

View File

@ -213,8 +213,24 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
// destroyLoader view from holder
holder.userIdsList.removeAllViews();
// we want conventional gpg UserIDs first, then Keybase proofs
HashMap<String, HashSet<String>> mergedUserIds = entry.getMergedUserIds();
for (Map.Entry<String, HashSet<String>> pair : mergedUserIds.entrySet()) {
ArrayList<Map.Entry<String, HashSet<String>>> sortedIds = new ArrayList<Map.Entry<String, HashSet<String>>>(mergedUserIds.entrySet());
java.util.Collections.sort(sortedIds, new java.util.Comparator<Map.Entry<String, HashSet<String>>>() {
@Override
public int compare(Map.Entry<String, HashSet<String>> entry1, Map.Entry<String, HashSet<String>> entry2) {
// sort keybase UserIds after non-Keybase
boolean e1IsKeybase = entry1.getKey().contains(":");
boolean e2IsKeybase = entry2.getKey().contains(":");
if (e1IsKeybase != e2IsKeybase) {
return (e1IsKeybase) ? 1 : -1;
}
return entry1.getKey().compareTo(entry2.getKey());
}
});
for (Map.Entry<String, HashSet<String>> pair : sortedIds) {
String cUserId = pair.getKey();
HashSet<String> cEmails = pair.getValue();

View File

@ -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();
}
}

View File

@ -33,6 +33,7 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
@ -100,14 +101,20 @@ public class DeleteKeyDialogFragment extends DialogFragment {
ProviderHelper.FIELD_TYPE_INTEGER
}
);
String userId = (String) data.get(KeyRings.USER_ID);
String name;
String[] mainUserId = KeyRing.splitUserId((String) data.get(KeyRings.USER_ID));
if (mainUserId[0] != null) {
name = mainUserId[0];
} else {
name = getString(R.string.user_id_no_name);
}
hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1;
// Set message depending on which key it is.
mMainMessage.setText(getString(
hasSecret ? R.string.secret_key_deletion_confirmation
: R.string.public_key_deletetion_confirmation,
userId
name
));
} catch (ProviderHelper.NotFoundException e) {
dismiss();

View File

@ -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();
}
}

View File

@ -6,10 +6,10 @@
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:stretchColumns="1">
<TextView

View File

@ -3,10 +3,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="8dp"
android:paddingBottom="8dp">
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">
<org.sufficientlysecure.keychain.ui.widget.AutoCorrectAutoCompleteTextView
android:id="@+id/add_user_id_address"

View File

@ -1,14 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<CheckBox
android:id="@+id/edit_subkey_expiry_no_expiry"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:checked="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="5dip"
android:paddingRight="5dip">
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">
<TextView
android:id="@+id/message"
@ -25,10 +27,11 @@
android:layout_weight="1"
android:gravity="top|left"
android:inputType="textMultiLine|textUri"
android:lines="4"
android:maxLines="10"
android:minLines="2"
android:scrollbars="vertical" />
android:lines="1"
android:maxLines="1"
android:minLines="1"
android:scrollbars="vertical"
android:layout_gravity="center_vertical" />
<ImageButton
android:id="@+id/btn_browse"

View File

@ -3,8 +3,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">
<CheckBox
android:id="@+id/passphrase_no_passphrase"

View File

@ -71,94 +71,6 @@
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/view_key_action_edit"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:clickable="true"
style="@style/SelectableItem"
android:text="@string/key_view_action_edit"
android:layout_weight="1"
android:drawableRight="@drawable/ic_action_edit"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
<View
android:id="@+id/view_key_action_edit_divider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/view_key_action_encrypt_text"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
style="@style/SelectableItem"
android:orientation="horizontal">
<TextView
android:id="@+id/view_key_action_encrypt_text_text"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dip"
android:layout_height="match_parent"
android:text="@string/key_view_action_encrypt"
android:layout_weight="1"
android:drawableRight="@drawable/ic_action_secure"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/view_key_action_encrypt_files"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/key_view_action_encrypt_files"
android:drawableRight="@drawable/ic_action_secure"
android:drawablePadding="8dp"
android:gravity="center_vertical"
style="@style/SelectableItem" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/view_key_action_update"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:clickable="true"
style="@style/SelectableItem"
android:text="@string/key_view_action_update"
android:layout_weight="1"
android:drawableRight="@drawable/ic_action_download"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
</LinearLayout>
</ScrollView>

View File

@ -1,4 +1,5 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -67,15 +68,26 @@
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<ImageView
android:id="@+id/view_key_fingerprint_qr_code_image"
android:paddingTop="8dp"
android:paddingBottom="8dp"
<android.support.v7.widget.CardView
android:id="@+id/view_key_qr_code_layout"
android:transitionName="qr_code"
android:visibility="visible"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
style="@style/SelectableItem" />
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
card_view:cardBackgroundColor="@android:color/white"
card_view:cardUseCompatPadding="true"
card_view:cardCornerRadius="4dp">
<ImageView
android:id="@+id/view_key_qr_code"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v7.widget.CardView>
<TextView
style="@style/SectionHeader"
@ -162,51 +174,6 @@
android:drawablePadding="8dp"
android:gravity="center_vertical" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/view_key_action_nfc_help"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
style="@style/SelectableItem"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dip"
android:layout_height="match_parent"
android:text="@string/key_view_action_share_nfc"
android:layout_weight="1"
android:drawableRight="@drawable/ic_action_help"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<ImageButton
android:id="@+id/view_key_action_nfc_prefs"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:src="@drawable/ic_action_settings"
android:layout_gravity="center_vertical"
style="@style/SelectableItem" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -8,7 +12,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/mainMessage"
android:layout_margin="4dp"
android:textAppearance="?android:textAppearanceMedium" />
</LinearLayout>

View 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>

View 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>

View File

@ -46,6 +46,9 @@
<!-- section -->
<string name="section_user_ids">"Identities"</string>
<string name="section_should_you_trust">"Should you trust this key?"</string>
<string name="section_proof_details">Proof verification</string>
<string name="section_cloud_evidence">"Evidence from the cloud"</string>
<string name="section_keys">"Subkeys"</string>
<string name="section_cloud_search">"Cloud search"</string>
<string name="section_general">"General"</string>
@ -545,6 +548,7 @@
<string name="key_view_tab_share">"Share"</string>
<string name="key_view_tab_keys">"Subkeys"</string>
<string name="key_view_tab_certs">"Certificates"</string>
<string name="key_view_tab_keybase">"Keybase.io"</string>
<string name="user_id_info_revoked_title">"Revoked"</string>
<string name="user_id_info_revoked_text">"This identity has been revoked by the key owner. It is no longer valid."</string>
<string name="user_id_info_certified_title">"Certified"</string>
@ -554,6 +558,47 @@
<string name="user_id_info_invalid_title">"Invalid"</string>
<string name="user_id_info_invalid_text">"Something is wrong with this identity!"</string>
<!-- Key trust -->
<string name="key_trust_already_verified">"You have already certified this key!"</string>
<string name="key_trust_it_is_yours">"This is one of your keys!"</string>
<string name="key_trust_maybe">"This key is neither revoked nor expired.\nYou havent 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 keys 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 doesnt match that in proof post"</string>
<string name="keybase_dns_query_failure">"DNS TXT Record retrieval failed"</string>
<string name="keybase_no_prover_found">"No proof checker found for"</string>
<string name="keybase_message_payload_mismatch">"Decrypted proof post does not match expected value"</string>
<string name="keybase_message_fetching_data">"Fetching proof evidence"</string>
<string name="keybase_proof_succeeded">"This proof has been verified!"</string>
<string name="keybase_a_post">"A post"</string>
<string name="keybase_fetched_from">"fetched from"</string>
<string name="keybase_for_the_domain">"for the domain"</string>
<string name="keybase_contained_signature">"contains a message which could only have been created by the owner of this key."</string>
<string name="keybase_twitter_proof">"A tweet"</string>
<string name="keybase_dns_proof">"A DNS TXT record"</string>
<string name="keybase_web_site_proof">"A text file"</string>
<string name="keybase_github_proof">"A gist"</string>
<string name="keybase_reddit_proof">"A JSON file"</string>
<string name="keybase_reddit_attribution">"attributed by Reddit to"</string>
<string name="keybase_verify">"Verify"</string>
<!-- Edit key -->
<string name="edit_key_action_change_passphrase">"Change Passphrase"</string>
<string name="edit_key_action_add_identity">"Add Identity"</string>
@ -978,6 +1023,19 @@
<string name="msg_dc_trail_unknown">"Encountered trailing data of unknown type"</string>
<string name="msg_dc_unlocking">"Unlocking secret key"</string>
<!-- Messages for VerifySignedLiteralData operation -->
<string name="msg_vl">"Starting signature check"</string>
<string name="msg_vl_error_no_siglist">"No signature list in signed literal data"</string>
<string name="msg_vl_error_wrong_key">"Message not signed with right key"</string>
<string name="msg_vl_error_missing_literal">"No payload in signed literal data"</string>
<string name="msg_vl_clear_meta_file">"Filename: %s"</string>
<string name="msg_vl_clear_meta_mime">"MIME type: %s"</string>
<string name="msg_vl_clear_meta_time">"Modification time: %s"</string>
<string name="msg_vl_clear_meta_size">"Filesize: %s"</string>
<string name="msg_vl_clear_signature_check">"Verifying signature data"</string>
<string name="msg_vl_error_integrity_check">"Integrity check error!"</string>
<string name="msg_vl_ok">"OK"</string>
<!-- Messages for SignEncrypt operation -->
<string name="msg_se">"Starting sign/encrypt operation"</string>
<string name="msg_se_input_bytes">"Processing input from byte array"</string>