Merge branch 'master' of github.com:mar-v-in/open-keychain into improve-file-more

This commit is contained in:
mar-v-in 2014-06-22 16:45:07 +02:00
commit 9e7e0be82c
49 changed files with 1001 additions and 529 deletions

3
.gitmodules vendored
View File

@ -31,3 +31,6 @@
[submodule "extern/dnsjava"]
path = extern/dnsjava
url = https://github.com/open-keychain/dnsjava.git
[submodule "extern/KeybaseLib"]
path = extern/KeybaseLib
url = https://github.com/timbray/KeybaseLib.git

View File

@ -1,13 +1,5 @@
apply plugin: 'android'
//apply plugin: 'android-test'
sourceSets {
//androidTest {
//java.srcDir file('src/test/java')
// configure the set of classes for JUnit tests
// include '**/*Test.class'
//}
}
apply plugin: 'robolectric'
dependencies {
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
@ -28,27 +20,15 @@ dependencies {
compile project(':extern:AppMsg:library')
compile project(':extern:SuperToasts:supertoasts')
compile project(':extern:dnsjava')
compile project(':extern:KeybaseLib:Lib')
// Dependencies for the `instrumentTest` task, make sure to list all your global dependencies here as well
androidTestCompile 'junit:junit:4.11'
androidTestCompile 'org.robolectric:robolectric:2.3'
androidTestCompile 'com.squareup:fest-android:1.0.8'
androidTestCompile 'com.google.android:android:4.1.1.4'
androidTestCompile 'com.android.support:support-v4:19.1.0'
androidTestCompile 'com.android.support:appcompat-v7:19.1.0'
androidTestCompile project(':extern:openpgp-api-lib')
androidTestCompile project(':extern:openkeychain-api-lib')
androidTestCompile project(':extern:html-textview')
androidTestCompile project(':extern:StickyListHeaders:library')
androidTestCompile project(':extern:AndroidBootstrap:AndroidBootstrap')
androidTestCompile project(':extern:zxing-qr-code')
androidTestCompile project(':extern:zxing-android-integration')
androidTestCompile project(':extern:spongycastle:core')
androidTestCompile project(':extern:spongycastle:pg')
androidTestCompile project(':extern:spongycastle:pkix')
androidTestCompile project(':extern:spongycastle:prov')
androidTestCompile project(':extern:AppMsg:library')
androidTestCompile project(':extern:SuperToasts:supertoasts')
// Unit tests are run with Robolectric
testCompile 'junit:junit:4.11'
testCompile 'org.robolectric:robolectric:2.3'
testCompile 'com.squareup:fest-android:1.0.8'
testCompile 'com.google.android:android:4.1.1.4'
// compile dependencies are automatically also included in testCompile
}

View File

@ -1,12 +0,0 @@
package tests;
import org.junit.Assert;
import org.junit.Test;
public class SomeTest {
@Test
public void willFail() {
// stub
// Assert.assertThat();
}
}

View File

@ -84,6 +84,11 @@
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_edit_key"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".ui.EditKeyActivityNew"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_edit_key"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".ui.ViewKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"

View File

@ -46,6 +46,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
private String mExtraData;
private String mQuery;
private String mOrigin;
private Integer mHashCode = null;
private boolean mSelected;
@ -98,6 +99,13 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
}
};
public int hashCode() {
if (mHashCode != null) {
return mHashCode;
}
return super.hashCode();
}
public String getKeyIdHex() {
return mKeyIdHex;
}
@ -240,6 +248,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mSecretKey = ring.isSecret();
UncachedPublicKey key = ring.getPublicKey();
mHashCode = key.hashCode();
mPrimaryUserId = key.getPrimaryUserId();
mUserIds = key.getUnorderedUserIds();

View File

@ -17,18 +17,17 @@
package org.sufficientlysecure.keychain.keyimport;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.textuality.keybase.lib.KeybaseException;
import com.textuality.keybase.lib.Match;
import com.textuality.keybase.lib.Search;
import com.textuality.keybase.lib.User;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.util.JWalk;
import org.sufficientlysecure.keychain.util.Log;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
public class KeybaseKeyserver extends Keyserver {
public static final String ORIGIN = "keybase:keybase.io";
@ -43,29 +42,14 @@ public class KeybaseKeyserver extends Keyserver {
// cut off "0x" if a user is searching for a key id
query = query.substring(2);
}
mQuery = query;
JSONObject fromQuery = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query);
try {
JSONArray matches = JWalk.getArray(fromQuery, "completions");
for (int i = 0; i < matches.length(); i++) {
JSONObject match = matches.getJSONObject(i);
// only list them if they have a key
if (JWalk.optObject(match, "components", "key_fingerprint") != null) {
// TODO: needed anymore?
// String keybaseId = JWalk.getString(match, "components", "username", "val");
// String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
// fingerprint = fingerprint.replace(" ", "").toUpperCase();
// if (keybaseId.equals(query) || fingerprint.startsWith(query.toUpperCase())) {
// results.add(makeEntry(match));
// } else {
// results.add(makeEntry(match));
// }
results.add(makeEntry(match));
}
Iterable<Match> matches = Search.search(query);
for (Match match : matches) {
results.add(makeEntry(match));
}
} catch (Exception e) {
} catch (KeybaseException e) {
Log.e(Constants.TAG, "keybase result parsing error", e);
throw new QueryFailedException("Unexpected structure in keybase search result: " + e.getMessage());
}
@ -73,103 +57,43 @@ public class KeybaseKeyserver extends Keyserver {
return results;
}
private JSONObject getUser(String keybaseId) throws QueryFailedException {
try {
return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseId);
} catch (Exception e) {
String detail = "";
if (keybaseId != null) {
detail = ". Query was for user '" + keybaseId + "'";
}
throw new QueryFailedException(e.getMessage() + detail);
}
}
private ImportKeysListEntry makeEntry(JSONObject match) throws QueryFailedException, JSONException {
private ImportKeysListEntry makeEntry(Match match) throws KeybaseException {
final ImportKeysListEntry entry = new ImportKeysListEntry();
entry.setQuery(mQuery);
entry.setOrigin(ORIGIN);
String keybaseId = JWalk.getString(match, "components", "username", "val");
String fullName = JWalk.getString(match, "components", "full_name", "val");
String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
fingerprint = fingerprint.replace(" ", "").toUpperCase(); // not strictly necessary but doesn't hurt
String username = match.getUsername();
String fullName = match.getFullName();
String fingerprint = match.getFingerprint();
entry.setFingerprintHex(fingerprint);
entry.setKeyIdHex("0x" + fingerprint.substring(Math.max(0, fingerprint.length() - 16)));
entry.setKeyIdHex("0x" + match.getKeyID());
// store extra info, so we can query for the keybase id directly
entry.setExtraData(keybaseId);
entry.setExtraData(username);
final int algorithmId = JWalk.getInt(match, "components", "key_fingerprint", "algo");
final int algorithmId = match.getAlgorithmId();
entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId));
final int bitStrength = JWalk.getInt(match, "components", "key_fingerprint", "nbits");
final int bitStrength = match.getBitStrength();
entry.setBitStrength(bitStrength);
ArrayList<String> userIds = new ArrayList<String>();
String name = fullName + " <keybase.io/" + keybaseId + ">";
String name = fullName + " <keybase.io/" + username + ">";
userIds.add(name);
try {
userIds.add("github.com/" + JWalk.getString(match, "components", "github", "val"));
} catch (JSONException e) {
// ignore
}
try {
userIds.add("twitter.com/" + JWalk.getString(match, "components", "twitter", "val"));
} catch (JSONException e) {
// ignore
}
try {
JSONArray array = JWalk.getArray(match, "components", "websites");
JSONObject website = array.getJSONObject(0);
userIds.add(JWalk.getString(website, "val"));
} catch (JSONException e) {
// ignore
List<String> proofLabels = match.getProofLabels();
for (String proofLabel : proofLabels) {
userIds.add(proofLabel);
}
entry.setUserIds(userIds);
entry.setPrimaryUserId(name);
return entry;
}
private JSONObject getFromKeybase(String path, String query) throws QueryFailedException {
try {
String url = "https://keybase.io/" + path + URLEncoder.encode(query, "utf8");
Log.d(Constants.TAG, "keybase query: " + url);
URL realUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
conn.setConnectTimeout(5000); // TODO: Reasonable values for keybase
conn.setReadTimeout(25000);
conn.connect();
int response = conn.getResponseCode();
if (response >= 200 && response < 300) {
String text = readAll(conn.getInputStream(), conn.getContentEncoding());
try {
JSONObject json = new JSONObject(text);
if (JWalk.getInt(json, "status", "code") != 0) {
throw new QueryFailedException("Keybase.io query failed: " + path + "?" +
query);
}
return json;
} catch (JSONException e) {
throw new QueryFailedException("Keybase.io query returned broken JSON");
}
} else {
String message = readAll(conn.getErrorStream(), conn.getContentEncoding());
throw new QueryFailedException("Keybase.io query error (status=" + response +
"): " + message);
}
} catch (Exception e) {
throw new QueryFailedException("Keybase.io query error");
}
}
@Override
public String get(String id) throws QueryFailedException {
try {
JSONObject user = getUser(id);
return JWalk.getString(user, "them", "public_keys", "primary", "bundle");
} catch (Exception e) {
return User.keyForUsername(id);
} catch (KeybaseException e) {
throw new QueryFailedException(e.getMessage());
}
}

View File

@ -27,7 +27,9 @@ public class ParcelableKeyRing implements Parcelable {
public static final Creator<ParcelableKeyRing> CREATOR = new Creator<ParcelableKeyRing>() {
public ParcelableKeyRing createFromParcel(final Parcel source) {
return new ParcelableKeyRing(source.createByteArray());
byte[] bytes = source.createByteArray();
String expectedFingerprint = source.readString();
return new ParcelableKeyRing(bytes, expectedFingerprint);
}
public ParcelableKeyRing[] newArray(final int size) {

View File

@ -0,0 +1,19 @@
package org.sufficientlysecure.keychain.pgp;
/**
* No-op implementation of Progressable
*/
public class NullProgressable implements Progressable {
@Override
public void setProgress(String message, int current, int total) {
}
@Override
public void setProgress(int resourceId, int current, int total) {
}
@Override
public void setProgress(int current, int total) {
}
}

View File

@ -139,6 +139,8 @@ public class PgpImportExport {
String expectedFp = entry.getExpectedFingerprint();
if(expectedFp != null) {
if(!PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()).equals(expectedFp)) {
Log.d(Constants.TAG, "fingerprint: " + PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()));
Log.d(Constants.TAG, "expected fingerprint: " + expectedFp);
Log.e(Constants.TAG, "Actual key fingerprint is not the same as expected!");
badKeys += 1;
continue;

View File

@ -104,7 +104,7 @@ public class PgpKeyHelper {
* @return
*/
public static String convertFingerprintToHex(byte[] fingerprint) {
String hexString = Hex.toHexString(fingerprint);
String hexString = Hex.toHexString(fingerprint).toLowerCase(Locale.US);
return hexString;
}

View File

@ -481,58 +481,4 @@ public class PgpKeyOperation {
}
/**
* Certify the given pubkeyid with the given masterkeyid.
*
* @param certificationKey Certifying key
* @param publicKey public key to certify
* @param userIds User IDs to certify, must not be null or empty
* @param passphrase Passphrase of the secret key
* @return A keyring with added certifications
*/
public PGPPublicKey certifyKey(PGPSecretKey certificationKey, PGPPublicKey publicKey,
List<String> userIds, String passphrase)
throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
PGPException, SignatureException {
// create a signatureGenerator from the supplied masterKeyId and passphrase
PGPSignatureGenerator signatureGenerator;
{
if (certificationKey == null) {
throw new PgpGeneralMsgIdException(R.string.error_no_signature_key);
}
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) {
throw new PgpGeneralMsgIdException(R.string.error_could_not_extract_private_key);
}
// TODO: SHA256 fixed?
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
}
{ // supply signatureGenerator with a SubpacketVector
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketVector packetVector = spGen.generate();
signatureGenerator.setHashedSubpackets(packetVector);
}
// fetch public key ring, add the certification and return it
for (String userId : new IterableIterator<String>(userIds.iterator())) {
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
}
return publicKey;
}
}

View File

@ -29,6 +29,7 @@ import android.support.v4.util.LongSparseArray;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.NullProgressable;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.WrappedPublicKey;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
@ -497,13 +498,12 @@ public class ProviderHelper {
}
}
mIndent -= 1;
} catch (IOException e) {
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC);
Log.e(Constants.TAG, "IOException during import", e);
mIndent -= 1;
return SaveKeyringResult.RESULT_ERROR;
} finally {
mIndent -= 1;
}
try {
@ -522,19 +522,16 @@ public class ProviderHelper {
mContentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
log(LogLevel.OK, LogType.MSG_IP_SUCCESS);
mIndent -= 1;
progress.setProgress(LogType.MSG_IP_SUCCESS.getMsgId(), 90, 100);
return result;
} catch (RemoteException e) {
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_REMOTE_EX);
Log.e(Constants.TAG, "RemoteException during import", e);
mIndent -= 1;
return SaveKeyringResult.RESULT_ERROR;
} catch (OperationApplicationException e) {
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_OP_EXC);
Log.e(Constants.TAG, "OperationApplicationException during import", e);
mIndent -= 1;
return SaveKeyringResult.RESULT_ERROR;
}
@ -580,94 +577,87 @@ public class ProviderHelper {
log(LogLevel.START, LogType.MSG_IS,
new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) });
mIndent += 1;
// Canonicalize this key, to assert a number of assumptions made about it.
keyRing = keyRing.canonicalize(mLog, mIndent);
if (keyRing == null) {
return SaveKeyringResult.RESULT_ERROR;
}
// IF this is successful, it's a secret key
int result = SaveKeyringResult.SAVED_SECRET;
// save secret keyring
try {
ContentValues values = new ContentValues();
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
// insert new version of this keyRing
Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
if (mContentResolver.insert(uri, values) == null) {
log(LogLevel.ERROR, LogType.MSG_IS_DB_EXCEPTION);
// Canonicalize this key, to assert a number of assumptions made about it.
keyRing = keyRing.canonicalize(mLog, mIndent);
if (keyRing == null) {
return SaveKeyringResult.RESULT_ERROR;
}
} catch (IOException e) {
Log.e(Constants.TAG, "Failed to encode key!", e);
log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC);
return SaveKeyringResult.RESULT_ERROR;
}
{
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
// IF this is successful, it's a secret key
int result = SaveKeyringResult.SAVED_SECRET;
// first, mark all keys as not available
ContentValues values = new ContentValues();
values.put(Keys.HAS_SECRET, 0);
mContentResolver.update(uri, values, null, null);
// save secret keyring
try {
ContentValues values = new ContentValues();
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
// insert new version of this keyRing
Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
if (mContentResolver.insert(uri, values) == null) {
log(LogLevel.ERROR, LogType.MSG_IS_DB_EXCEPTION);
return SaveKeyringResult.RESULT_ERROR;
}
} catch (IOException e) {
Log.e(Constants.TAG, "Failed to encode key!", e);
log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC);
return SaveKeyringResult.RESULT_ERROR;
}
values.put(Keys.HAS_SECRET, 1);
// then, mark exactly the keys we have available
log(LogLevel.INFO, LogType.MSG_IS_IMPORTING_SUBKEYS);
mIndent += 1;
Set<Long> available = keyRing.getAvailableSubkeys();
for (UncachedPublicKey sub :
new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) {
long id = sub.getKeyId();
if(available.contains(id)) {
int upd = mContentResolver.update(uri, values, Keys.KEY_ID + " = ?",
new String[] { Long.toString(id) });
if (upd == 1) {
log(LogLevel.DEBUG, LogType.MSG_IS_SUBKEY_OK, new String[]{
PgpKeyHelper.convertKeyIdToHex(id)
});
{
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
// first, mark all keys as not available
ContentValues values = new ContentValues();
values.put(Keys.HAS_SECRET, 0);
mContentResolver.update(uri, values, null, null);
values.put(Keys.HAS_SECRET, 1);
// then, mark exactly the keys we have available
log(LogLevel.INFO, LogType.MSG_IS_IMPORTING_SUBKEYS);
mIndent += 1;
Set<Long> available = keyRing.getAvailableSubkeys();
for (UncachedPublicKey sub :
new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) {
long id = sub.getKeyId();
if (available.contains(id)) {
int upd = mContentResolver.update(uri, values, Keys.KEY_ID + " = ?",
new String[]{Long.toString(id)});
if (upd == 1) {
log(LogLevel.DEBUG, LogType.MSG_IS_SUBKEY_OK, new String[]{
PgpKeyHelper.convertKeyIdToHex(id)
});
} else {
log(LogLevel.WARN, LogType.MSG_IS_SUBKEY_NONEXISTENT, new String[]{
PgpKeyHelper.convertKeyIdToHex(id)
});
}
} else {
log(LogLevel.WARN, LogType.MSG_IS_SUBKEY_NONEXISTENT, new String[]{
log(LogLevel.INFO, LogType.MSG_IS_SUBKEY_STRIPPED, new String[]{
PgpKeyHelper.convertKeyIdToHex(id)
});
}
} else {
log(LogLevel.INFO, LogType.MSG_IS_SUBKEY_STRIPPED, new String[]{
PgpKeyHelper.convertKeyIdToHex(id)
});
}
mIndent -= 1;
// this implicitly leaves all keys which were not in the secret key ring
// with has_secret = 0
}
log(LogLevel.OK, LogType.MSG_IS_SUCCESS);
return result;
} finally {
mIndent -= 1;
// this implicitly leaves all keys which were not in the secret key ring
// with has_secret = 0
}
log(LogLevel.OK, LogType.MSG_IS_SUCCESS);
return result;
}
@Deprecated
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) {
return savePublicKeyRing(keyRing, new Progressable() {
@Override
public void setProgress(String message, int current, int total) {
}
@Override
public void setProgress(int resourceId, int current, int total) {
}
@Override
public void setProgress(int current, int total) {
}
});
return savePublicKeyRing(keyRing, new NullProgressable());
}
/** Save a public keyring into the database.
@ -749,12 +739,13 @@ public class ProviderHelper {
}
}
mIndent -= 1;
return new SaveKeyringResult(result, mLog);
} catch (IOException e) {
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
} finally {
mIndent -= 1;
}
}
@ -844,6 +835,8 @@ public class ProviderHelper {
} catch (IOException e) {
log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC, null);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
} finally {
mIndent -= 1;
}
}

View File

@ -4,7 +4,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
import java.util.HashMap;
import java.util.ArrayList;
/** This class is a a transferable representation for a collection of changes
* to be done on a keyring.
@ -29,14 +29,14 @@ public class SaveKeyringParcel implements Parcelable {
public String newPassphrase;
public String[] addUserIds;
public SubkeyAdd[] addSubKeys;
public ArrayList<String> addUserIds;
public ArrayList<SubkeyAdd> addSubKeys;
public SubkeyChange[] changeSubKeys;
public ArrayList<SubkeyChange> changeSubKeys;
public String changePrimaryUserId;
public String[] revokeUserIds;
public long[] revokeSubKeys;
public ArrayList<String> revokeUserIds;
public ArrayList<Long> revokeSubKeys;
public SaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
mMasterKeyId = masterKeyId;
@ -73,14 +73,14 @@ public class SaveKeyringParcel implements Parcelable {
mMasterKeyId = source.readLong();
mFingerprint = source.createByteArray();
addUserIds = source.createStringArray();
addSubKeys = (SubkeyAdd[]) source.readSerializable();
addUserIds = source.createStringArrayList();
addSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable();
changeSubKeys = (SubkeyChange[]) source.readSerializable();
changeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable();
changePrimaryUserId = source.readString();
revokeUserIds = source.createStringArray();
revokeSubKeys = source.createLongArray();
revokeUserIds = source.createStringArrayList();
revokeSubKeys = (ArrayList<Long>) source.readSerializable();
}
@Override
@ -88,14 +88,14 @@ public class SaveKeyringParcel implements Parcelable {
destination.writeLong(mMasterKeyId);
destination.writeByteArray(mFingerprint);
destination.writeStringArray(addUserIds);
destination.writeStringList(addUserIds);
destination.writeSerializable(addSubKeys);
destination.writeSerializable(changeSubKeys);
destination.writeString(changePrimaryUserId);
destination.writeStringArray(revokeUserIds);
destination.writeLongArray(revokeSubKeys);
destination.writeStringList(revokeUserIds);
destination.writeSerializable(revokeSubKeys);
}
public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {

View File

@ -0,0 +1,56 @@
package org.sufficientlysecure.keychain.testsupport;
import android.content.Context;
import org.sufficientlysecure.keychain.pgp.NullProgressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.service.OperationResults;
/**
* Helper for tests of the Keyring import in ProviderHelper.
*/
public class KeyringTestingHelper {
private final Context context;
public KeyringTestingHelper(Context robolectricContext) {
this.context = robolectricContext;
}
public boolean addKeyring() throws Exception {
ProviderHelper providerHelper = new ProviderHelper(context);
// providerHelper.insertApiApp(new AppSettings("robo-test-package", new byte[]{5, 4, 3, 2, 1}));
byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data);
long masterKeyId = ring.getMasterKeyId();
// Should throw an exception; key is not yet saved
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId);
OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable());
boolean saveSuccess = saveKeyringResult.success();
// Now re-retrieve the saved key. Should not throw an exception.
providerHelper.getWrappedPublicKeyRing(masterKeyId);
// A different ID should still fail
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
return saveSuccess;
}
private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
try {
providerHelper.getWrappedPublicKeyRing(masterKeyId);
throw new AssertionError("Was expecting the previous call to fail!");
} catch (ProviderHelper.NotFoundException expectedException) {
// good
}
}
}

View File

@ -0,0 +1,49 @@
package org.sufficientlysecure.keychain.testsupport;
import android.content.Context;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.InputData;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* For functional tests of PgpDecryptVerify
*/
public class PgpVerifyTestingHelper {
private final Context context;
public PgpVerifyTestingHelper(Context robolectricContext) {
this.context = robolectricContext;
}
public int doTestFile(String testFileName) throws Exception {
ProviderHelper providerHelper = new ProviderHelperStub(context);
PgpDecryptVerify.PassphraseCache passphraseCache = new PgpDecryptVerify.PassphraseCache() {
public String getCachedPassphrase(long masterKeyId) {
return "I am a passphrase";
}
};
byte[] sampleInputBytes = TestDataUtil.readFully(getClass().getResourceAsStream(testFileName));
InputStream sampleInput = new ByteArrayInputStream(sampleInputBytes);
InputData data = new InputData(sampleInput, sampleInputBytes.length);
OutputStream outStream = new ByteArrayOutputStream();
PgpDecryptVerify verify = new PgpDecryptVerify.Builder(providerHelper, passphraseCache, data, outStream).build();
PgpDecryptVerifyResult result = verify.execute();
return result.getSignatureResult().getStatus();
}
}

View File

@ -0,0 +1,22 @@
package org.sufficientlysecure.keychain.testsupport;
import android.content.Context;
import android.net.Uri;
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
/**
* Created by art on 21/06/14.
*/
class ProviderHelperStub extends ProviderHelper {
public ProviderHelperStub(Context context) {
super(context);
}
@Override
public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException {
byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
return new WrappedPublicKeyRing(data, false, 0);
}
}

View File

@ -0,0 +1,25 @@
package org.sufficientlysecure.keychain.testsupport;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Misc support functions. Would just use Guava / Apache Commons but
* avoiding extra dependencies.
*/
public class TestDataUtil {
public static byte[] readFully(InputStream input) {
byte[] buffer = new byte[8192];
int bytesRead;
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return output.toByteArray();
}
}

View File

@ -0,0 +1,7 @@
/**
* Test support classes.
* This is only in main code because of gradle-Android Studio-robolectric issues. Having
* classes in main code means IDE autocomplete, class detection, etc., all works.
* TODO Move into test package when possible
*/
package org.sufficientlysecure.keychain.testsupport;

View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 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;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.remote.ui.AccountsListFragment;
import org.sufficientlysecure.keychain.util.Log;
public class EditKeyActivityNew extends ActionBarActivity {
private Uri mDataUri;
private EditKeyFragment mEditKeyFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_key_activity_new);
// // Inflate a "Done"/"Cancel" custom action bar view
// ActionBarHelper.setTwoButtonView(getSupportActionBar(),
// R.string.btn_save, R.drawable.ic_action_save,
// new OnClickListener() {
// @Override
// public void onClick(View v) {
// // Save
//
// }
// }, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel,
// new OnClickListener() {
// @Override
// public void onClick(View v) {
// // Cancel
//
// }
// }
// );
Uri dataUri = getIntent().getData();
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
finish();
return;
}
loadFragment(savedInstanceState, dataUri);
}
private void loadFragment(Bundle savedInstanceState, Uri dataUri) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of the fragment
mEditKeyFragment = EditKeyFragment.newInstance(dataUri);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.edit_key_fragment_container, mEditKeyFragment)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
}

View File

@ -0,0 +1,200 @@
/*
* Copyright (C) 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;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ListView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;
import org.sufficientlysecure.keychain.util.Log;
public class EditKeyFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
private ListView mUserIds;
private ListView mKeys;
private static final int LOADER_ID_USER_IDS = 0;
private static final int LOADER_ID_KEYS = 1;
private ViewKeyUserIdsAdapter mUserIdsAdapter;
private ViewKeyKeysAdapter mKeysAdapter;
private Uri mDataUri;
/**
* Creates new instance of this fragment
*/
public static EditKeyFragment newInstance(Uri dataUri) {
EditKeyFragment frag = new EditKeyFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.edit_key_fragment, getContainer());
mUserIds = (ListView) view.findViewById(R.id.edit_key_user_ids);
mKeys = (ListView) view.findViewById(R.id.edit_key_keys);
// mActionEdit = view.findViewById(R.id.view_key_action_edit);
return root;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setTwoButtonView(((ActionBarActivity) getActivity()).getSupportActionBar(),
R.string.btn_save, R.drawable.ic_action_save,
new OnClickListener() {
@Override
public void onClick(View v) {
// Save
save();
}
}, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel,
new OnClickListener() {
@Override
public void onClick(View v) {
// Cancel
getActivity().finish();
}
}
);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
getActivity().finish();
return;
}
loadData(dataUri);
}
private void loadData(Uri dataUri) {
mDataUri = dataUri;
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
// mActionEncrypt.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// encrypt(mDataUri);
// }
// });
mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0);
mUserIds.setAdapter(mUserIdsAdapter);
mKeysAdapter = new ViewKeyKeysAdapter(getActivity(), null, 0);
mKeys.setAdapter(mKeysAdapter);
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
getLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
switch (id) {
case LOADER_ID_USER_IDS: {
Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
return new CursorLoader(getActivity(), baseUri,
ViewKeyUserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
}
case LOADER_ID_KEYS: {
Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri);
return new CursorLoader(getActivity(), baseUri,
ViewKeyKeysAdapter.KEYS_PROJECTION, null, null, null);
}
default:
return null;
}
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
case LOADER_ID_USER_IDS:
mUserIdsAdapter.swapCursor(data);
break;
case LOADER_ID_KEYS:
mKeysAdapter.swapCursor(data);
break;
}
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) {
switch (loader.getId()) {
case LOADER_ID_USER_IDS:
mUserIdsAdapter.swapCursor(null);
break;
case LOADER_ID_KEYS:
mKeysAdapter.swapCursor(null);
break;
}
}
private void save() {
getActivity().finish();
// TODO
}
}

View File

@ -44,6 +44,7 @@ import com.github.johnpersano.supertoasts.util.Style;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
@ -88,8 +89,6 @@ public class ImportKeysActivity extends ActionBarActivity {
// view
private ImportKeysListFragment mListFragment;
private String[] mNavigationStrings;
private Fragment mCurrentFragment;
private View mImportButton;
private ViewPager mViewPager;
private SlidingTabLayout mSlidingTabLayout;
@ -97,12 +96,12 @@ public class ImportKeysActivity extends ActionBarActivity {
public static final int VIEW_PAGER_HEIGHT = 64; // dp
private static final int NAV_SERVER = 0;
private static final int NAV_QR_CODE = 1;
private static final int NAV_FILE = 2;
private static final int NAV_KEYBASE = 3;
private static final int TAB_KEYSERVER = 0;
private static final int TAB_QR_CODE = 1;
private static final int TAB_FILE = 2;
private static final int TAB_KEYBASE = 3;
private int mSwitchToTab = NAV_SERVER;
private int mSwitchToTab = TAB_KEYSERVER;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -121,12 +120,6 @@ public class ImportKeysActivity extends ActionBarActivity {
}
});
mNavigationStrings = getResources().getStringArray(R.array.import_action_list);
// TODO: add actionbar button for this action?
// if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
// }
handleActions(savedInstanceState, getIntent());
}
@ -142,26 +135,31 @@ public class ImportKeysActivity extends ActionBarActivity {
if (Intent.ACTION_VIEW.equals(action)) {
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
// override action to delegate it to Keychain's ACTION_IMPORT_KEY
// delegate action to ACTION_IMPORT_KEY
action = ACTION_IMPORT_KEY;
}
if (scheme != null && scheme.toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) {
/* Scanning a fingerprint directly with Barcode Scanner */
// delegate action to ACTION_IMPORT_KEY_FROM_KEYSERVER
String fingerprint = getFingerprintFromUri(dataUri);
action = ACTION_IMPORT_KEY_FROM_KEYSERVER;
extras.putString(EXTRA_FINGERPRINT, fingerprint);
}
Bundle serverBundle = null;
boolean serverOnly = false;
if (scheme != null && scheme.toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) {
/* Scanning a fingerprint directly with Barcode Scanner */
loadFromFingerprintUri(savedInstanceState, dataUri);
} else if (ACTION_IMPORT_KEY.equals(action)) {
if (ACTION_IMPORT_KEY.equals(action)) {
/* Keychain's own Actions */
// display file fragment
mViewPager.setCurrentItem(NAV_FILE);
mViewPager.setCurrentItem(TAB_FILE);
if (dataUri != null) {
// action: directly load data
startListFragment(savedInstanceState, null, dataUri, null);
} else if (extras.containsKey(EXTRA_KEY_BYTES)) {
byte[] importData = intent.getByteArrayExtra(EXTRA_KEY_BYTES);
byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES);
// action: directly load data
startListFragment(savedInstanceState, importData, null, null);
@ -180,7 +178,7 @@ public class ImportKeysActivity extends ActionBarActivity {
if (extras.containsKey(EXTRA_QUERY)) {
query = extras.getString(EXTRA_QUERY);
} else if (extras.containsKey(EXTRA_KEY_ID)) {
long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
long keyId = extras.getLong(EXTRA_KEY_ID, 0);
if (keyId != 0) {
query = PgpKeyHelper.convertKeyIdToHex(keyId);
}
@ -190,7 +188,7 @@ public class ImportKeysActivity extends ActionBarActivity {
// display keyserver fragment with query
serverBundle = new Bundle();
serverBundle.putString(ImportKeysServerFragment.ARG_QUERY, query);
mSwitchToTab = NAV_SERVER;
mSwitchToTab = TAB_KEYSERVER;
// action: search immediately
startListFragment(savedInstanceState, null, null, query);
@ -204,7 +202,7 @@ public class ImportKeysActivity extends ActionBarActivity {
* if the right key has been downloaded
*/
String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT);
String fingerprint = extras.getString(EXTRA_FINGERPRINT);
if (isFingerprintValid(fingerprint)) {
String query = "0x" + fingerprint;
@ -212,8 +210,9 @@ public class ImportKeysActivity extends ActionBarActivity {
serverBundle = new Bundle();
serverBundle.putString(ImportKeysServerFragment.ARG_QUERY, query);
serverBundle.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true);
// display server tab only
serverOnly = true;
mSwitchToTab = NAV_SERVER;
mSwitchToTab = TAB_KEYSERVER;
// action: search immediately
startListFragment(savedInstanceState, null, null, query);
@ -227,7 +226,7 @@ public class ImportKeysActivity extends ActionBarActivity {
}
} else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
// NOTE: this only displays the appropriate fragment, no actions are taken
mSwitchToTab = NAV_FILE;
mSwitchToTab = TAB_FILE;
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
@ -235,20 +234,20 @@ public class ImportKeysActivity extends ActionBarActivity {
// also exposed in AndroidManifest
// NOTE: this only displays the appropriate fragment, no actions are taken
mSwitchToTab = NAV_QR_CODE;
mSwitchToTab = TAB_QR_CODE;
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
} else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) {
// NOTE: this only displays the appropriate fragment, no actions are taken
mSwitchToTab = NAV_QR_CODE;
mSwitchToTab = TAB_QR_CODE;
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
} else if (ACTION_IMPORT_KEY_FROM_KEYBASE.equals(action)) {
// NOTE: this only displays the appropriate fragment, no actions are taken
mSwitchToTab = NAV_KEYBASE;
mSwitchToTab = TAB_KEYBASE;
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
@ -273,6 +272,8 @@ public class ImportKeysActivity extends ActionBarActivity {
@Override
public void onPageSelected(int position) {
// cancel loader and clear list
mListFragment.destroyLoader();
}
@Override
@ -329,23 +330,45 @@ public class ImportKeysActivity extends ActionBarActivity {
return OtherHelper.pxToDp(this, params.height);
}
public void loadFromFingerprintUri(Bundle savedInstanceState, Uri dataUri) {
private String getFingerprintFromUri(Uri dataUri) {
String fingerprint = dataUri.toString().split(":")[1].toLowerCase(Locale.ENGLISH);
Log.d(Constants.TAG, "fingerprint: " + fingerprint);
return fingerprint;
}
// TODO: reload fragment when coming from qr code!
// loadFromFingerprint(savedInstanceState, fingerprint);
public void loadFromFingerprintUri(Uri dataUri) {
String query = "0x" + getFingerprintFromUri(dataUri);
// setCurrentItem does not work directly after onResume (from qr code scanner)
// see http://stackoverflow.com/q/19316729
// so, reset adapter completely!
if (mViewPager.getAdapter() != null)
mViewPager.setAdapter(null);
mViewPager.setAdapter(mTabsAdapter);
mViewPager.setCurrentItem(TAB_KEYSERVER);
// String query = "0x" + fingerprint;
//
// // display keyserver fragment with query
// Bundle serverBundle = new Bundle();
// serverBundle.putString(ImportKeysServerFragment.ARG_QUERY, query);
// serverBundle.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true);
//
// return serverBundle;
ImportKeysServerFragment f = (ImportKeysServerFragment)
getActiveFragment(mViewPager, TAB_KEYSERVER);
// TODO: Currently it simply uses keyserver nr 0
String keyserver = Preferences.getPreferences(ImportKeysActivity.this)
.getKeyServers()[0];
// set fields of ImportKeysServerFragment
f.setQueryAndKeyserver(query, keyserver);
// search directly
loadCallback(new ImportKeysListFragment.KeyserverLoaderState(query, keyserver));
}
// http://stackoverflow.com/a/9293207
public Fragment getActiveFragment(ViewPager container, int position) {
String name = makeFragmentName(container.getId(), position);
return getSupportFragmentManager().findFragmentByTag(name);
}
// http://stackoverflow.com/a/9293207
private static String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
private boolean isFingerprintValid(String fingerprint) {
@ -372,7 +395,13 @@ public class ImportKeysActivity extends ActionBarActivity {
boolean result = super.onTouchEvent(event);
if (!result) {
mViewPager.onTouchEvent(event);
try {
mViewPager.onTouchEvent(event);
} catch (IllegalArgumentException e) {
// workaround for Android bug?
// http://stackoverflow.com/q/16459196
Log.d(Constants.TAG, "Workaround: Catched IllegalArgumentException");
}
}
return result;

View File

@ -81,7 +81,7 @@ public class ImportKeysFileFragment extends Fragment {
if (clipboardText != null) {
sendText = clipboardText.toString();
if (sendText.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) {
mImportActivity.loadFromFingerprintUri(null, Uri.parse(sendText));
mImportActivity.loadFromFingerprintUri(Uri.parse(sendText));
return;
}
}

View File

@ -24,7 +24,9 @@ import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.util.LongSparseArray;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ListView;
import org.sufficientlysecure.keychain.Constants;
@ -77,7 +79,7 @@ public class ImportKeysListFragment extends ListFragment implements
public ArrayList<ParcelableKeyRing> getSelectedData() {
ArrayList<ParcelableKeyRing> result = new ArrayList<ParcelableKeyRing>();
for (ImportKeysListEntry entry : getSelectedEntries()) {
result.add(mCachedKeyData.get(entry.getKeyId()));
result.add(mCachedKeyData.get(entry.hashCode()));
}
return result;
}
@ -149,19 +151,30 @@ public class ImportKeysListFragment extends ListFragment implements
mAdapter = new ImportKeysAdapter(mActivity);
setListAdapter(mAdapter);
if (getArguments().containsKey(ARG_DATA_URI) || getArguments().containsKey(ARG_BYTES)) {
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
byte[] bytes = getArguments().getByteArray(ARG_BYTES);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
byte[] bytes = getArguments().getByteArray(ARG_BYTES);
String query = getArguments().getString(ARG_SERVER_QUERY);
if (dataUri != null || bytes != null) {
mLoaderState = new BytesLoaderState(bytes, dataUri);
} else if (getArguments().containsKey(ARG_SERVER_QUERY)) {
String query = getArguments().getString(ARG_SERVER_QUERY);
// TODO: this is used when scanning QR Code or updating a key.
} else if (query != null) {
// TODO: this is used when updating a key.
// Currently it simply uses keyserver nr 0
String keyserver = Preferences.getPreferences(getActivity())
.getKeyServers()[0];
mLoaderState = new KeyserverLoaderState(query, keyserver);
}
getListView().setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (!mAdapter.isEmpty()) {
mActivity.onTouchEvent(event);
}
return false;
}
});
restartLoaders();
}
@ -184,27 +197,34 @@ public class ImportKeysListFragment extends ListFragment implements
restartLoaders();
}
public void destroyLoader() {
if (getLoaderManager().getLoader(LOADER_ID_BYTES) != null) {
getLoaderManager().destroyLoader(LOADER_ID_BYTES);
}
if (getLoaderManager().getLoader(LOADER_ID_SERVER_QUERY) != null) {
getLoaderManager().destroyLoader(LOADER_ID_SERVER_QUERY);
}
if (getLoaderManager().getLoader(LOADER_ID_KEYBASE) != null) {
getLoaderManager().destroyLoader(LOADER_ID_KEYBASE);
}
setListShown(true);
}
private void restartLoaders() {
if (mLoaderState instanceof BytesLoaderState) {
// Start out with a progress indicator.
setListShown(false);
getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this);
getLoaderManager().destroyLoader(LOADER_ID_SERVER_QUERY);
getLoaderManager().destroyLoader(LOADER_ID_KEYBASE);
} else if (mLoaderState instanceof KeyserverLoaderState) {
// Start out with a progress indicator.
setListShown(false);
getLoaderManager().destroyLoader(LOADER_ID_BYTES);
getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this);
getLoaderManager().destroyLoader(LOADER_ID_KEYBASE);
} else if (mLoaderState instanceof KeybaseLoaderState) {
// Start out with a progress indicator.
setListShown(false);
getLoaderManager().destroyLoader(LOADER_ID_BYTES);
getLoaderManager().destroyLoader(LOADER_ID_SERVER_QUERY);
getLoaderManager().restartLoader(LOADER_ID_KEYBASE, null, this);
}
}

View File

@ -132,7 +132,7 @@ public class ImportKeysQrCodeFragment extends Fragment {
}
public void importFingerprint(Uri dataUri) {
mImportActivity.loadFromFingerprintUri(null, dataUri);
mImportActivity.loadFromFingerprintUri(dataUri);
}
}

View File

@ -40,7 +40,7 @@ import org.sufficientlysecure.keychain.util.Log;
public class ImportKeysServerFragment extends Fragment {
public static final String ARG_QUERY = "query";
public static final String ARG_KEY_SERVER = "key_server";
public static final String ARG_KEYSERVER = "keyserver";
public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
private ImportKeysActivity mImportActivity;
@ -55,12 +55,12 @@ public class ImportKeysServerFragment extends Fragment {
/**
* Creates new instance of this fragment
*/
public static ImportKeysServerFragment newInstance(String query, String keyServer) {
public static ImportKeysServerFragment newInstance(String query, String keyserver) {
ImportKeysServerFragment frag = new ImportKeysServerFragment();
Bundle args = new Bundle();
args.putString(ARG_QUERY, query);
args.putString(ARG_KEY_SERVER, keyServer);
args.putString(ARG_KEYSERVER, keyserver);
frag.setArguments(args);
@ -149,12 +149,12 @@ public class ImportKeysServerFragment extends Fragment {
Log.d(Constants.TAG, "query: " + query);
}
if (getArguments().containsKey(ARG_KEY_SERVER)) {
String keyServer = getArguments().getString(ARG_KEY_SERVER);
int keyServerPos = mServerAdapter.getPosition(keyServer);
mServerSpinner.setSelection(keyServerPos);
if (getArguments().containsKey(ARG_KEYSERVER)) {
String keyserver = getArguments().getString(ARG_KEYSERVER);
int keyserverPos = mServerAdapter.getPosition(keyserver);
mServerSpinner.setSelection(keyserverPos);
Log.d(Constants.TAG, "keyServer: " + keyServer);
Log.d(Constants.TAG, "keyserver: " + keyserver);
}
if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) {
@ -174,4 +174,10 @@ public class ImportKeysServerFragment extends Fragment {
mImportActivity.loadCallback(new ImportKeysListFragment.KeyserverLoaderState(query, keyserver));
}
public void setQueryAndKeyserver(String query, String keyserver) {
mQueryEditText.setText(query, TextView.BufferType.EDITABLE);
int keyServerPos = mServerAdapter.getPosition(keyserver);
mServerSpinner.setSelection(keyServerPos);
}
}

View File

@ -1,13 +1,28 @@
/*
* Copyright (C) 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;
import android.os.Bundle;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v7.app.ActionBarActivity;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
public class LogDisplayActivity extends ActionBarActivity {
@ -15,6 +30,18 @@ public class LogDisplayActivity extends ActionBarActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate a "Done" custom action bar
ActionBarHelper.setOneButtonView(getSupportActionBar(),
R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() {
@Override
public void onClick(View v) {
// "Done"
finish();
}
}
);
setContentView(R.layout.log_display_activity);
}

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 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;
import android.content.Context;

View File

@ -83,17 +83,11 @@ public class ViewKeyKeysFragment extends LoaderFragment implements
getLoaderManager().initLoader(0, null, this);
}
static final String[] KEYS_PROJECTION = new String[] {
Keys._ID,
Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.HAS_SECRET,
Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED,
Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT
};
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
Uri baseUri = Keys.buildKeysUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null);
return new CursorLoader(getActivity(), baseUri,
ViewKeyKeysAdapter.KEYS_PROJECTION, null, null, null);
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

View File

@ -49,6 +49,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
public static final String ARG_DATA_URI = "uri";
private View mActionEdit;
private View mActionEditNew;
private View mActionEditDivider;
private View mActionEncrypt;
private View mActionCertify;
@ -73,6 +74,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
mActionEdit = view.findViewById(R.id.view_key_action_edit);
mActionEditNew = view.findViewById(R.id.view_key_action_edit_new);
mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider);
mActionEncrypt = view.findViewById(R.id.view_key_action_encrypt);
mActionCertify = view.findViewById(R.id.view_key_action_certify);
@ -116,6 +118,11 @@ public class ViewKeyMainFragment extends LoaderFragment implements
editKey(mDataUri);
}
});
mActionEditNew.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
editKeyNew(mDataUri);
}
});
mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0);
mUserIds.setAdapter(mUserIdsAdapter);
@ -256,4 +263,12 @@ public class ViewKeyMainFragment extends LoaderFragment implements
startActivityForResult(editIntent, 0);
}
private void editKeyNew(Uri dataUri) {
Intent editIntent = new Intent(getActivity(), EditKeyActivityNew.class);
// editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
startActivityForResult(editIntent, 0);
}
}

View File

@ -170,7 +170,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
} else {
holder.userIdsList.setVisibility(View.VISIBLE);
// clear view from holder
// destroyLoader view from holder
holder.userIdsList.removeAllViews();
Iterator<String> it = entry.getUserIds().iterator();

View File

@ -138,7 +138,7 @@ public class ImportKeysListLoader
for(UncachedKeyRing key : rings) {
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), key);
mData.add(item);
mParcelableRings.put(key.getMasterKeyId(), new ParcelableKeyRing(key.getEncoded()));
mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(key.getEncoded()));
isEmpty = false;
}
}

View File

@ -47,7 +47,6 @@ public class ImportKeysListServerLoader
@Override
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
if (mServerQuery == null) {

View File

@ -53,6 +53,22 @@ public class ViewKeyKeysAdapter extends CursorAdapter {
private ColorStateList mDefaultTextColor;
public static final String[] KEYS_PROJECTION = new String[] {
Keys._ID,
Keys.KEY_ID,
Keys.RANK,
Keys.ALGORITHM,
Keys.KEY_SIZE,
Keys.HAS_SECRET,
Keys.CAN_CERTIFY,
Keys.CAN_ENCRYPT,
Keys.CAN_SIGN,
Keys.IS_REVOKED,
Keys.CREATION,
Keys.EXPIRY,
Keys.FINGERPRINT
};
public ViewKeyKeysAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);

View File

@ -1,121 +0,0 @@
/*
* 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.util;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Minimal hierarchy selector
*
* This is for picking out an item in a large multilevel JSON object, for example look at
* the Keybase.io User object, documentation at https://keybase.io/__/api-docs/1.0#user-objects
* an example available via
* curl https://keybase.io/_/api/1.0/user/lookup.json?username=timbray
*
* If you want to retrieve the ascii-armored key, you'd say
* String key = JWalk.getString(match,"them", "public_keys", "primary", "bundle");
*/
public class JWalk {
/**
* Returns an int member value from the JSON sub-object addressed by the path
*
* @param json The object
* @param path list of string object member selectors
* @return the int addressed by the path, assuming such a thing exists
* @throws JSONException if any step in the path doesnt work
*/
public static int getInt(JSONObject json, String... path) throws JSONException {
json = walk(json, path);
return json.getInt(path[path.length - 1]);
}
/**
* Returns a long member value from the JSON sub-object addressed by the path
*
* @param json The object
* @param path list of string object member selectors
* @return the int addressed by the path, assuming such a thing exists
* @throws JSONException if any step in the path doesnt work
*/
public static long getLong(JSONObject json, String... path) throws JSONException {
json = walk(json, path);
return json.getLong(path[path.length - 1]);
}
/**
* Returns a String member value from the JSON sub-object addressed by the path
*
* @param json The object
* @param path list of string object member selectors
* @return the int addressed by the path, assuming such a thing exists
* @throws JSONException if any step in the path doesnt work
*/
public static String getString(JSONObject json, String... path) throws JSONException {
json = walk(json, path);
return json.getString(path[path.length - 1]);
}
/**
* Returns a JSONArray member value from the JSON sub-object addressed by the path
*
* @param json The object
* @param path list of string object member selectors
* @return the int addressed by the path, assuming such a thing exists
* @throws JSONException if any step in the path doesnt work
*/
public static JSONArray getArray(JSONObject json, String... path) throws JSONException {
json = walk(json, path);
return json.getJSONArray(path[path.length - 1]);
}
/**
* Returns a JSONObject member value from the JSON sub-object addressed by the path, or null
*
* @param json The object
* @param path list of string object member selectors
* @return the int addressed by the path, assuming such a thing exists
* @throws JSONException if any step in the path, except for the last, doesnt work
*/
public static JSONObject optObject(JSONObject json, String... path) throws JSONException {
json = walk(json, path);
return json.optJSONObject(path[path.length - 1]);
}
private static JSONObject walk(JSONObject json, String... path) throws JSONException {
int len = path.length - 1;
int pathIndex = 0;
try {
while (pathIndex < len) {
json = json.getJSONObject(path[pathIndex]);
pathIndex++;
}
} catch (JSONException e) {
// try to give em a nice-looking error
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
sb.append(path[i]).append('.');
}
sb.append(path[len]);
throw new JSONException("JWalk error at step " + pathIndex + " of " + sb);
}
return json;
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/edit_key_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</LinearLayout>

View File

@ -0,0 +1,44 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
style="@style/SectionHeader"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginBottom="4dp"
android:layout_marginTop="8dp"
android:text="@string/section_user_ids"
android:layout_weight="1" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/edit_key_user_ids"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="4dp"
android:layout_weight="1" />
<TextView
style="@style/SectionHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginTop="8dp"
android:text="@string/section_keys" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/edit_key_keys"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp" />
</LinearLayout>
</ScrollView>

View File

@ -13,34 +13,4 @@
android:layout_marginRight="8dp"
android:layout_marginLeft="8dp" />
<LinearLayout
android:id="@+id/import_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/import_import"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_marginBottom="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Close"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:clickable="true"
style="@style/SelectableItem" />
</LinearLayout>
</LinearLayout>

View File

@ -74,6 +74,22 @@
android:drawablePadding="8dp"
android:gravity="center_vertical" />
<TextView
android:id="@+id/view_key_action_edit_new"
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="NEW 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"

View File

@ -48,13 +48,5 @@
<item>@string/key_size_1024</item>
<item>@string/key_size_custom</item>
</string-array>
<string-array name="import_action_list" translatable="false">
<item>@string/menu_import_from_key_server</item>
<item>@string/menu_import_from_file</item>
<item>@string/menu_import_from_qr_code</item>
<item>@string/import_from_clipboard</item>
<item>@string/menu_import_from_nfc</item>
<item>@string/menu_import_from_keybase</item>
</string-array>
</resources>

View File

@ -0,0 +1,37 @@
package tests;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.*;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.testsupport.PgpVerifyTestingHelper;
@RunWith(RobolectricTestRunner.class)
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
public class PgpDecryptVerifyTest {
@Test
public void testVerifySuccess() throws Exception {
String testFileName = "/sample.txt";
int expectedSignatureResult = OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED;
int status = new PgpVerifyTestingHelper(Robolectric.application).doTestFile(testFileName);
Assert.assertEquals(expectedSignatureResult, status);
}
@Test
public void testVerifyFailure() throws Exception {
String testFileName = "/sample-altered.txt";
int expectedSignatureResult = OpenPgpSignatureResult.SIGNATURE_ERROR;
int status = new PgpVerifyTestingHelper(Robolectric.application).doTestFile(testFileName);
Assert.assertEquals(expectedSignatureResult, status);
}
}

View File

@ -0,0 +1,20 @@
package tests;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.*;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.testsupport.KeyringTestingHelper;
import org.sufficientlysecure.keychain.testsupport.PgpVerifyTestingHelper;
@RunWith(RobolectricTestRunner.class)
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
public class ProviderHelperKeyringTest {
@Test
public void testSavePublicKeyring() throws Exception {
Assert.assertTrue(new KeyringTestingHelper(Robolectric.application).addKeyring());
}
}

View File

@ -0,0 +1,26 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
This is a simple text document, which is used to illustrate
the concept of signing simple text files. There are no
control characters or special formatting commands in this
text, just simple printable ASCII characters.
MALICIOUS TEXT
To make this a slightly less uninteresting document, there
follows a short shopping list.
eggs, 1 doz
milk, 1 gal
bacon, 1 lb
olive oil
bread, 1 loaf
That's all there is to this document.
-----BEGIN PGP SIGNATURE-----
Version: PGPfreeware 5.5.5 for non-commercial use <http://www.nai.com>
iQA/AwUBN78ib3S9RCOKzj55EQKqDACg1NV2/iyPKrDlOVJvJwz6ArcQ0UQAnjNC
CDxKAFyaaGa835l1vpbFkAJk
=3r/N
-----END PGP SIGNATURE-----

View File

@ -0,0 +1,26 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
This is a simple text document, which is used to illustrate
the concept of signing simple text files. There are no
control characters or special formatting commands in this
text, just simple printable ASCII characters.
To make this a slightly less uninteresting document, there
follows a short shopping list.
eggs, 1 doz
milk, 1 gal
bacon, 1 lb
olive oil
bread, 1 loaf
That's all there is to this document.
-----BEGIN PGP SIGNATURE-----
Version: PGPfreeware 5.5.5 for non-commercial use <http://www.nai.com>
iQA/AwUBN78ib3S9RCOKzj55EQKqDACg1NV2/iyPKrDlOVJvJwz6ArcQ0UQAnjNC
CDxKAFyaaGa835l1vpbFkAJk
=3r/N
-----END PGP SIGNATURE-----

View File

@ -38,7 +38,7 @@ Expand the Extras directory and install "Android Support Repository"
Select everything for the newest SDK Platform (API-Level 19)
4. Export ANDROID_HOME pointing to your Android SDK
5. Execute ``./gradlew build``
6. You can install the app with ``adb install -r OpenKeychain/build/apk/OpenKeychain-debug-unaligned.apk``
6. You can install the app with ``adb install -r OpenKeychain/build/outputs/apk/OpenKeychain-debug-unaligned.apk``
### Build API Demo with Gradle

View File

@ -6,7 +6,7 @@ buildscript {
dependencies {
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
classpath 'com.android.tools.build:gradle:0.11.1'
//classpath 'org.robolectric.gradle:gradle-android-test-plugin:0.10.0'
classpath 'org.robolectric:robolectric-gradle-plugin:0.11.0'
}
}

1
extern/KeybaseLib vendored Submodule

@ -0,0 +1 @@
Subproject commit 838800d2cb57fdd4caca1bdbd919e9d96dae13e9

@ -1 +1 @@
Subproject commit a77887d32fae68171fcd0d2989bf537c0c11f0b9
Subproject commit 289d48b633b7493f2a760583c88670b9fc4ef96f

View File

@ -13,3 +13,4 @@ include ':extern:spongycastle:prov'
include ':extern:AppMsg:library'
include ':extern:SuperToasts:supertoasts'
include ':extern:dnsjava'
include ':extern:KeybaseLib:Lib'