support new index-v1 format using Jackson parser
This adds support for parsing the new index-v1.json data as defined in fdroidserver!221. This new index metadata format is required to support localization, graphics, screenshots, etc. refs #15
This commit is contained in:
parent
7e0ae10e84
commit
d769dcfc60
@ -58,13 +58,14 @@ dependencies {
|
|||||||
exclude module: 'design'
|
exclude module: 'design'
|
||||||
}
|
}
|
||||||
|
|
||||||
testCompile 'junit:junit:4.12'
|
compile 'com.fasterxml.jackson.core:jackson-core:2.8.7'
|
||||||
|
compile 'com.fasterxml.jackson.core:jackson-annotations:2.8.7'
|
||||||
|
compile 'com.fasterxml.jackson.core:jackson-databind:2.8.7'
|
||||||
|
|
||||||
testCompile "org.robolectric:robolectric:3.3.1"
|
testCompile "org.robolectric:robolectric:3.3.1"
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
// As per https://github.com/robolectric/robolectric/issues/1932#issuecomment-219796474
|
// As per https://github.com/robolectric/robolectric/issues/1932#issuecomment-219796474
|
||||||
testCompile 'org.khronos:opengl-api:gl1.1-android-2.1_r1'
|
testCompile 'org.khronos:opengl-api:gl1.1-android-2.1_r1'
|
||||||
|
|
||||||
testCompile "org.mockito:mockito-core:1.10.19"
|
testCompile "org.mockito:mockito-core:1.10.19"
|
||||||
|
|
||||||
androidTestCompile "com.android.support:support-annotations:${supportLibVersion}"
|
androidTestCompile "com.android.support:support-annotations:${supportLibVersion}"
|
||||||
@ -129,6 +130,9 @@ if (!hasProperty('sourceDeps')) {
|
|||||||
'com.android.support:support-v4:cd030f875dc7ee73b58e17598f368a2e12824fb3ceb4ed515ed815a47160228c',
|
'com.android.support:support-v4:cd030f875dc7ee73b58e17598f368a2e12824fb3ceb4ed515ed815a47160228c',
|
||||||
'com.android.support:support-vector-drawable:d79752fd68db5a8f5c18125517dafb9e4d7b593c755d188986010e15edd62454',
|
'com.android.support:support-vector-drawable:d79752fd68db5a8f5c18125517dafb9e4d7b593c755d188986010e15edd62454',
|
||||||
'com.android.support:transition:5a4adefb1b410b23ad62b4477bc612edc47d3dfc8efed488deb8223b70b510d7',
|
'com.android.support:transition:5a4adefb1b410b23ad62b4477bc612edc47d3dfc8efed488deb8223b70b510d7',
|
||||||
|
'com.fasterxml.jackson.core:jackson-annotations:6b7802f6c22c09c4a92a2ebeb76e755c3c0a58dfbf419835fae470d89e469b86',
|
||||||
|
'com.fasterxml.jackson.core:jackson-core:256ff34118ab292d1b4f3ee4d2c3e5e5f0f609d8e07c57e8ad1f51c46d4fbb46',
|
||||||
|
'com.fasterxml.jackson.core:jackson-databind:4f74337b6d18664be0f5b15c6664b17aa3972c9c175092328b139b894ff66f19',
|
||||||
'com.github.pserwylo:BottomNavigation:83d7941a7a8d21ba1a8a708cd683b1bb07c6cf898044dc92eadf18a7a7d54f90',
|
'com.github.pserwylo:BottomNavigation:83d7941a7a8d21ba1a8a708cd683b1bb07c6cf898044dc92eadf18a7a7d54f90',
|
||||||
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
|
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
|
||||||
'com.hannesdorfmann:adapterdelegates3:1b20d099d6e7afe57aceca13b713b386959d94a247c3c06a7aeb65b866ece02f',
|
'com.hannesdorfmann:adapterdelegates3:1b20d099d6e7afe57aceca13b713b386959d94a247c3c06a7aeb65b866ece02f',
|
||||||
|
10
app/proguard-rules.pro
vendored
10
app/proguard-rules.pro
vendored
@ -44,3 +44,13 @@
|
|||||||
# - https://github.com/ReactiveX/RxJava/issues/1415#issuecomment-48390883
|
# - https://github.com/ReactiveX/RxJava/issues/1415#issuecomment-48390883
|
||||||
# - https://github.com/ReactiveX/RxJava/blob/1.x/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java#L23
|
# - https://github.com/ReactiveX/RxJava/blob/1.x/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java#L23
|
||||||
-dontwarn rx.internal.util.**
|
-dontwarn rx.internal.util.**
|
||||||
|
|
||||||
|
-keepattributes *Annotation*,EnclosingMethod,Signature
|
||||||
|
-keepnames class com.fasterxml.jackson.** { *; }
|
||||||
|
-dontwarn com.fasterxml.jackson.databind.ext.**
|
||||||
|
-keep class org.codehaus.** { *; }
|
||||||
|
-keepclassmembers public final enum org.codehaus.jackson.annotate.JsonAutoDetect$Visibility {
|
||||||
|
public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *; }
|
||||||
|
-keep public class your.class.** {
|
||||||
|
*;
|
||||||
|
}
|
335
app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java
Normal file
335
app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.data.Repo;
|
||||||
|
import org.fdroid.fdroid.data.RepoPersister;
|
||||||
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
|
import org.fdroid.fdroid.data.Schema;
|
||||||
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
|
import org.fdroid.fdroid.net.DownloaderFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives the index data about all available apps and packages via the V1
|
||||||
|
* JSON data {@link #DATA_FILE_NAME}, embedded in a signed jar
|
||||||
|
* {@link #SIGNED_FILE_NAME}. This uses the Jackson library to parse the JSON,
|
||||||
|
* with {@link App} and {@link Apk} being instantiated directly from the JSON
|
||||||
|
* by Jackson. This is possible but not wise to do with {@link Repo} since that
|
||||||
|
* class has many fields that are related to security components of the
|
||||||
|
* implementation internal to this app.
|
||||||
|
*/
|
||||||
|
public class IndexV1Updater extends RepoUpdater {
|
||||||
|
public static final String TAG = "IndexV1Updater";
|
||||||
|
|
||||||
|
private static final String SIGNED_FILE_NAME = "index-v1.jar";
|
||||||
|
public static final String DATA_FILE_NAME = "index-v1.json";
|
||||||
|
|
||||||
|
private final LocalBroadcastManager localBroadcastManager;
|
||||||
|
|
||||||
|
public IndexV1Updater(@NonNull Context context, @NonNull Repo repo) {
|
||||||
|
super(context, repo);
|
||||||
|
this.localBroadcastManager = LocalBroadcastManager.getInstance(this.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether this successfully found an index of this version
|
||||||
|
* @throws RepoUpdater.UpdateException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean update() throws RepoUpdater.UpdateException {
|
||||||
|
|
||||||
|
if (repo.isSwap) {
|
||||||
|
// swap repos do not support index-v1
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Downloader downloader = null;
|
||||||
|
InputStream indexInputStream = null;
|
||||||
|
try {
|
||||||
|
// read file name from file
|
||||||
|
final Uri dataUri = Uri.parse(repo.address).buildUpon().appendPath(SIGNED_FILE_NAME).build();
|
||||||
|
downloader = DownloaderFactory.create(context, dataUri.toString());
|
||||||
|
downloader.setCacheTag(repo.lastetag);
|
||||||
|
downloader.setListener(new ProgressListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
|
||||||
|
Intent intent = new Intent(Downloader.ACTION_PROGRESS);
|
||||||
|
intent.setData(dataUri);
|
||||||
|
intent.putExtra(Downloader.EXTRA_BYTES_READ, bytesRead);
|
||||||
|
intent.putExtra(Downloader.EXTRA_TOTAL_BYTES, totalBytes);
|
||||||
|
localBroadcastManager.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
downloader.download();
|
||||||
|
if (downloader.isNotFound()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (downloader.isCached()) {
|
||||||
|
// The index is unchanged since we last read it. We just mark
|
||||||
|
// everything that came from this repo as being updated.
|
||||||
|
Utils.debugLog(TAG, "Repo index for " + dataUri + " is up to date (by etag)");
|
||||||
|
}
|
||||||
|
hasChanged = downloader.hasChanged();
|
||||||
|
|
||||||
|
if (!hasChanged) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
JarFile jarFile = new JarFile(downloader.outputFile, true);
|
||||||
|
JarEntry indexEntry = (JarEntry) jarFile.getEntry(DATA_FILE_NAME);
|
||||||
|
indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
|
||||||
|
processXmlProgressListener, new URL(repo.address), (int) indexEntry.getSize());
|
||||||
|
processIndexV1(indexInputStream, indexEntry, downloader.getCacheTag());
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (downloader != null) {
|
||||||
|
FileUtils.deleteQuietly(downloader.outputFile);
|
||||||
|
}
|
||||||
|
throw new RepoUpdater.UpdateException(repo, "Error getting index file", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignored if canceled, the local database just won't be updated
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the index and feeds it to the database via {@link Repo}, {@link App},
|
||||||
|
* and {@link Apk} instances.
|
||||||
|
*
|
||||||
|
* @param indexInputStream {@link InputStream} to {@code index-v1.json}
|
||||||
|
* @param cacheTag the {@code etag} value from HTTP headers
|
||||||
|
* @throws IOException
|
||||||
|
* @throws UpdateException
|
||||||
|
*/
|
||||||
|
public void processIndexV1(InputStream indexInputStream, JarEntry indexEntry, String cacheTag)
|
||||||
|
throws IOException, UpdateException {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonFactory f = mapper.getFactory();
|
||||||
|
JsonParser parser = f.createParser(indexInputStream);
|
||||||
|
HashMap<String, Object> repoMap = null;
|
||||||
|
App[] apps = null;
|
||||||
|
Map<String, String[]> requests = null;
|
||||||
|
Map<String, List<Apk>> packages = null;
|
||||||
|
|
||||||
|
parser.nextToken(); // go into the main object block
|
||||||
|
while (true) {
|
||||||
|
String fieldName = parser.nextFieldName();
|
||||||
|
if (fieldName == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (fieldName) {
|
||||||
|
case "repo":
|
||||||
|
repoMap = parseRepo(mapper, parser);
|
||||||
|
break;
|
||||||
|
case "requests":
|
||||||
|
requests = parseRequests(mapper, parser);
|
||||||
|
break;
|
||||||
|
case "apps":
|
||||||
|
apps = parseApps(mapper, parser);
|
||||||
|
break;
|
||||||
|
case "packages":
|
||||||
|
packages = parsePackages(mapper, parser);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parser.close(); // ensure resources get cleaned up timely and properly
|
||||||
|
|
||||||
|
if (repoMap == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO
|
||||||
|
if (timestamp < repo.timestamp) {
|
||||||
|
throw new RepoUpdater.UpdateException(repo, "index.jar is older that current index! "
|
||||||
|
+ timestamp + " < " + repo.timestamp);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// TODO handle maxage, convert to "expiration" Date instance
|
||||||
|
|
||||||
|
X509Certificate certificate = getSigningCertFromJar(indexEntry);
|
||||||
|
verifySigningCertificate(certificate);
|
||||||
|
Utils.debugLog(TAG, "Repo signature verified, saving app metadata to database.");
|
||||||
|
|
||||||
|
// timestamp is absolutely required
|
||||||
|
repo.timestamp = (Long) repoMap.get("timestamp");
|
||||||
|
// below are optional, can be null
|
||||||
|
repo.name = getStringRepoValue(repoMap, "name");
|
||||||
|
repo.icon = getStringRepoValue(repoMap, "icon");
|
||||||
|
repo.description = getStringRepoValue(repoMap, "description");
|
||||||
|
repo.mirrors = getStringArrayRepoValue(repoMap, "mirrors");
|
||||||
|
// below are optional, can be default value
|
||||||
|
repo.maxage = getIntRepoValue(repoMap, "maxage");
|
||||||
|
repo.version = getIntRepoValue(repoMap, "version");
|
||||||
|
|
||||||
|
RepoPersister repoPersister = new RepoPersister(context, repo);
|
||||||
|
if (apps != null && apps.length > 0) {
|
||||||
|
for (App app : apps) {
|
||||||
|
app.repoId = repo.getId(); // TODO this should be "injected" i.e. @JacksonInject
|
||||||
|
List<Apk> apks = null;
|
||||||
|
if (packages != null) {
|
||||||
|
apks = packages.get(app.packageName);
|
||||||
|
}
|
||||||
|
if (apks == null) {
|
||||||
|
Log.i(TAG, "processIndexV1 empty packages");
|
||||||
|
apks = new ArrayList<Apk>(0);
|
||||||
|
}
|
||||||
|
repoPersister.saveToDb(app, apks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO send event saying moving on to committing to db
|
||||||
|
ContentValues values = prepareRepoDetailsForSaving(repo.name,
|
||||||
|
repo.description, repo.maxage, repo.version, repo.timestamp, repo.icon,
|
||||||
|
repo.mirrors, cacheTag);
|
||||||
|
repoPersister.commit(values);
|
||||||
|
|
||||||
|
|
||||||
|
// TODO RepoUpdater.processRepoPushRequests(context, repoPushRequestList);
|
||||||
|
Utils.debugLog(TAG, "Repo Push Requests: " + requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getIntRepoValue(Map<String, Object> repoMap, String key) {
|
||||||
|
Object value = repoMap.get(key);
|
||||||
|
if (value != null && value instanceof Integer) {
|
||||||
|
return (Integer) value;
|
||||||
|
}
|
||||||
|
return Repo.INT_UNSET_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStringRepoValue(Map<String, Object> repoMap, String key) {
|
||||||
|
Object value = repoMap.get(key);
|
||||||
|
if (value != null && value instanceof String) {
|
||||||
|
return (String) value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getStringArrayRepoValue(Map<String, Object> repoMap, String key) {
|
||||||
|
Object value = repoMap.get(key);
|
||||||
|
if (value != null && value instanceof String[]) {
|
||||||
|
return (String[]) value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<String, Object> parseRepo(ObjectMapper mapper, JsonParser parser) throws IOException {
|
||||||
|
TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {
|
||||||
|
};
|
||||||
|
parser.nextToken();
|
||||||
|
parser.nextToken();
|
||||||
|
return mapper.readValue(parser, typeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String[]> parseRequests(ObjectMapper mapper, JsonParser parser) throws IOException {
|
||||||
|
TypeReference<HashMap<String, String[]>> typeRef = new TypeReference<HashMap<String, String[]>>() {
|
||||||
|
};
|
||||||
|
parser.nextToken(); // START_OBJECT
|
||||||
|
return mapper.readValue(parser, typeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private App[] parseApps(ObjectMapper mapper, JsonParser parser) throws IOException {
|
||||||
|
TypeReference<App[]> typeRef = new TypeReference<App[]>() {
|
||||||
|
};
|
||||||
|
parser.nextToken(); // START_ARRAY
|
||||||
|
return mapper.readValue(parser, typeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, List<Apk>> parsePackages(ObjectMapper mapper, JsonParser parser) throws IOException {
|
||||||
|
TypeReference<HashMap<String, List<Apk>>> typeRef = new TypeReference<HashMap<String, List<Apk>>>() {
|
||||||
|
};
|
||||||
|
parser.nextToken(); // START_OBJECT
|
||||||
|
return mapper.readValue(parser, typeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the signing certificate used to sign {@link #SIGNED_FILE_NAME}
|
||||||
|
* matches the signing stored in the database for this repo. {@link #repo} and
|
||||||
|
* {@code repo.signingCertificate} must be pre-loaded from the database before
|
||||||
|
* running this, if this is an existing repo. If the repo does not exist,
|
||||||
|
* this will run the TOFU process.
|
||||||
|
* <p>
|
||||||
|
* Index V1 works with two copies of the signing certificate:
|
||||||
|
* <li>in the downloaded jar</li>
|
||||||
|
* <li>stored in the local database</li>
|
||||||
|
* <p>
|
||||||
|
* A new repo can be added with or without the fingerprint of the signing
|
||||||
|
* certificate. If no fingerprint is supplied, then do a pure TOFU and just
|
||||||
|
* store the certificate as valid. If there is a fingerprint, then first
|
||||||
|
* check that the signing certificate in the jar matches that fingerprint.
|
||||||
|
* <p>
|
||||||
|
* This is also responsible for adding the {@link Repo} instance to the
|
||||||
|
* database for the first time.
|
||||||
|
* <p>
|
||||||
|
* This is the same as {@link RepoUpdater#verifyCerts(String, X509Certificate)},
|
||||||
|
* {@link RepoUpdater#verifyAndStoreTOFUCerts(String, X509Certificate)}, and
|
||||||
|
* {@link RepoUpdater#assertSigningCertFromXmlCorrect()} except there is no
|
||||||
|
* embedded copy of the signing certificate in the index data.
|
||||||
|
*
|
||||||
|
* @param rawCertFromJar the {@link X509Certificate} embedded in the downloaded jar
|
||||||
|
* @see RepoUpdater#verifyAndStoreTOFUCerts(String, X509Certificate)
|
||||||
|
* @see RepoUpdater#verifyCerts(String, X509Certificate)
|
||||||
|
* @see RepoUpdater#assertSigningCertFromXmlCorrect()
|
||||||
|
*/
|
||||||
|
private void verifySigningCertificate(X509Certificate rawCertFromJar) throws SigningException {
|
||||||
|
String certFromJar = Hasher.hex(rawCertFromJar);
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(certFromJar)) {
|
||||||
|
throw new SigningException(repo,
|
||||||
|
SIGNED_FILE_NAME + " must have an included signing certificate!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repo.signingCertificate == null) {
|
||||||
|
if (repo.fingerprint != null) {
|
||||||
|
String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar);
|
||||||
|
if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) {
|
||||||
|
throw new SigningException(repo,
|
||||||
|
"Supplied certificate fingerprint does not match!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Utils.debugLog(TAG, "Saving new signing certificate to database for " + repo.address);
|
||||||
|
ContentValues values = new ContentValues(2);
|
||||||
|
values.put(Schema.RepoTable.Cols.LAST_UPDATED, Utils.formatDate(new Date(), ""));
|
||||||
|
values.put(Schema.RepoTable.Cols.SIGNING_CERT, Hasher.hex(rawCertFromJar));
|
||||||
|
RepoProvider.Helper.update(context, repo, values);
|
||||||
|
repo.signingCertificate = certFromJar;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(repo.signingCertificate)) {
|
||||||
|
throw new SigningException(repo, "A empty repo signing certificate is invalid!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repo.signingCertificate.equals(certFromJar)) {
|
||||||
|
return; // we have a match!
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SigningException(repo, "Signing certificate does not match!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -21,6 +21,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
// TODO move to org.fdroid.fdroid.updater
|
||||||
|
// TODO reduce visibility of methods once in .updater package (.e.g tests need it public now)
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
@ -83,21 +85,22 @@ import javax.xml.parsers.SAXParserFactory;
|
|||||||
* very careful with the changes that you are making!
|
* very careful with the changes that you are making!
|
||||||
*/
|
*/
|
||||||
public class RepoUpdater {
|
public class RepoUpdater {
|
||||||
|
//TODO rename RepoUpdater to IndexV0Updater
|
||||||
private static final String TAG = "RepoUpdater";
|
private static final String TAG = "RepoUpdater";
|
||||||
|
|
||||||
private final String indexUrl;
|
private final String indexUrl;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Context context;
|
final Context context;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Repo repo;
|
final Repo repo;
|
||||||
private boolean hasChanged;
|
boolean hasChanged;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ProgressListener downloadProgressListener;
|
ProgressListener downloadProgressListener;
|
||||||
private ProgressListener committingProgressListener;
|
ProgressListener committingProgressListener;
|
||||||
private ProgressListener processXmlProgressListener;
|
ProgressListener processXmlProgressListener;
|
||||||
|
|
||||||
private String cacheTag;
|
private String cacheTag;
|
||||||
private X509Certificate signingCertFromJar;
|
private X509Certificate signingCertFromJar;
|
||||||
|
|
||||||
@ -174,10 +177,10 @@ public class RepoUpdater {
|
|||||||
* a single file, {@code index.xml}. This takes the {@code index.jar}, verifies the
|
* a single file, {@code index.xml}. This takes the {@code index.jar}, verifies the
|
||||||
* signature, then returns the unzipped {@code index.xml}.
|
* signature, then returns the unzipped {@code index.xml}.
|
||||||
*
|
*
|
||||||
|
* @return whether this version of the repo index was found and processed
|
||||||
* @throws UpdateException All error states will come from here.
|
* @throws UpdateException All error states will come from here.
|
||||||
*/
|
*/
|
||||||
public void update() throws UpdateException {
|
public boolean update() throws UpdateException {
|
||||||
|
|
||||||
final Downloader downloader = downloadIndex();
|
final Downloader downloader = downloadIndex();
|
||||||
hasChanged = downloader.hasChanged();
|
hasChanged = downloader.hasChanged();
|
||||||
|
|
||||||
@ -188,6 +191,7 @@ public class RepoUpdater {
|
|||||||
processDownloadedFile(downloader.outputFile);
|
processDownloadedFile(downloader.outputFile);
|
||||||
processRepoPushRequests();
|
processRepoPushRequests();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContentValues repoDetailsToSave;
|
private ContentValues repoDetailsToSave;
|
||||||
@ -200,7 +204,7 @@ public class RepoUpdater {
|
|||||||
int version, long timestamp, String icon, String[] mirrors) {
|
int version, long timestamp, String icon, String[] mirrors) {
|
||||||
signingCertFromIndexXml = signingCert;
|
signingCertFromIndexXml = signingCert;
|
||||||
repoDetailsToSave = prepareRepoDetailsForSaving(name, description, maxAge, version,
|
repoDetailsToSave = prepareRepoDetailsForSaving(name, description, maxAge, version,
|
||||||
timestamp, icon, mirrors);
|
timestamp, icon, mirrors, cacheTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -297,9 +301,9 @@ public class RepoUpdater {
|
|||||||
* Update tracking data for the repo represented by this instance (index version, etag,
|
* Update tracking data for the repo represented by this instance (index version, etag,
|
||||||
* description, human-readable name, etc.
|
* description, human-readable name, etc.
|
||||||
*/
|
*/
|
||||||
private ContentValues prepareRepoDetailsForSaving(String name, String description, int maxAge,
|
ContentValues prepareRepoDetailsForSaving(String name, String description, int maxAge,
|
||||||
int version, long timestamp, String icon,
|
int version, long timestamp, String icon,
|
||||||
String[] mirrors) {
|
String[] mirrors, String cacheTag) {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
|
|
||||||
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), ""));
|
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), ""));
|
||||||
@ -308,12 +312,12 @@ public class RepoUpdater {
|
|||||||
values.put(RepoTable.Cols.LAST_ETAG, cacheTag);
|
values.put(RepoTable.Cols.LAST_ETAG, cacheTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version != -1 && version != repo.version) {
|
if (version != Repo.INT_UNSET_VALUE && version != repo.version) {
|
||||||
Utils.debugLog(TAG, "Repo specified a new version: from " + repo.version + " to " + version);
|
Utils.debugLog(TAG, "Repo specified a new version: from " + repo.version + " to " + version);
|
||||||
values.put(RepoTable.Cols.VERSION, version);
|
values.put(RepoTable.Cols.VERSION, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxAge != -1 && maxAge != repo.maxage) {
|
if (maxAge != Repo.INT_UNSET_VALUE && maxAge != repo.maxage) {
|
||||||
Utils.debugLog(TAG, "Repo specified a new maximum age - updated");
|
Utils.debugLog(TAG, "Repo specified a new maximum age - updated");
|
||||||
values.put(RepoTable.Cols.MAX_AGE, maxAge);
|
values.put(RepoTable.Cols.MAX_AGE, maxAge);
|
||||||
}
|
}
|
||||||
@ -372,7 +376,7 @@ public class RepoUpdater {
|
|||||||
* signing setups that would be valid for a regular jar. This validates those
|
* signing setups that would be valid for a regular jar. This validates those
|
||||||
* restrictions.
|
* restrictions.
|
||||||
*/
|
*/
|
||||||
private X509Certificate getSigningCertFromJar(JarEntry jarEntry) throws SigningException {
|
X509Certificate getSigningCertFromJar(JarEntry jarEntry) throws SigningException {
|
||||||
final CodeSigner[] codeSigners = jarEntry.getCodeSigners();
|
final CodeSigner[] codeSigners = jarEntry.getCodeSigners();
|
||||||
if (codeSigners == null || codeSigners.length == 0) {
|
if (codeSigners == null || codeSigners.length == 0) {
|
||||||
throw new SigningException(repo, "No signature found in index");
|
throw new SigningException(repo, "No signature found in index");
|
||||||
@ -458,7 +462,7 @@ public class RepoUpdater {
|
|||||||
* should always accept, prompt the user, or ignore those requests on a
|
* should always accept, prompt the user, or ignore those requests on a
|
||||||
* per repo basis.
|
* per repo basis.
|
||||||
*/
|
*/
|
||||||
private void processRepoPushRequests() {
|
void processRepoPushRequests() {
|
||||||
PackageManager pm = context.getPackageManager();
|
PackageManager pm = context.getPackageManager();
|
||||||
|
|
||||||
for (RepoPushRequest repoPushRequest : repoPushRequestList) {
|
for (RepoPushRequest repoPushRequest : repoPushRequestList) {
|
||||||
|
@ -393,10 +393,16 @@ public class UpdateService extends IntentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendStatus(this, STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
|
sendStatus(this, STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
|
||||||
RepoUpdater updater = new RepoUpdater(getBaseContext(), repo);
|
|
||||||
setProgressListeners(updater);
|
|
||||||
try {
|
try {
|
||||||
|
RepoUpdater updater = new IndexV1Updater(this, repo);
|
||||||
|
//TODO setProgressListeners(updater);
|
||||||
|
if (!updater.update()) {
|
||||||
|
updater = new RepoUpdater(getBaseContext(), repo);
|
||||||
|
setProgressListeners(updater);
|
||||||
updater.update();
|
updater.update();
|
||||||
|
}
|
||||||
if (updater.hasChanged()) {
|
if (updater.hasChanged()) {
|
||||||
updatedRepos++;
|
updatedRepos++;
|
||||||
changes = true;
|
changes = true;
|
||||||
|
@ -8,6 +8,7 @@ import android.os.Build;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
import org.fdroid.fdroid.RepoXMLHandler;
|
import org.fdroid.fdroid.RepoXMLHandler;
|
||||||
@ -18,6 +19,21 @@ import java.io.File;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single package of an application. This represents one particular
|
||||||
|
* package of a given application, for info about the app in general, see
|
||||||
|
* {@link App}.
|
||||||
|
* <p>
|
||||||
|
* <b>Do not rename these instance variables without careful consideration!</b>
|
||||||
|
* They are mapped to JSON field names, the {@code fdroidserver} internal variable
|
||||||
|
* names, and the {@code fdroiddata} YAML field names. Only the instance variables
|
||||||
|
* listed in {@code @JsonIgnoreProperties} are not directly mapped.
|
||||||
|
*
|
||||||
|
* @see <a href="https://gitlab.com/fdroid/fdroiddata">fdroiddata</a>
|
||||||
|
* @see <a href="https://gitlab.com/fdroid/fdroidserver">fdroidserver</a>
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties({"compatible", "CREATOR", "installedFile", "repo", "repoAddress",
|
||||||
|
"repoVersion",})
|
||||||
public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
|
public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
|
||||||
|
|
||||||
// Using only byte-range keeps it only 8-bits in the SQLite database
|
// Using only byte-range keeps it only 8-bits in the SQLite database
|
||||||
@ -94,16 +110,16 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
|
|||||||
* If you need an {@link Apk} but it is no longer in the database any more (e.g. because the
|
* If you need an {@link Apk} but it is no longer in the database any more (e.g. because the
|
||||||
* version you have installed is no longer in the repository metadata) then you can instantiate
|
* version you have installed is no longer in the repository metadata) then you can instantiate
|
||||||
* an {@link Apk} via an {@link InstalledApp} instance.
|
* an {@link Apk} via an {@link InstalledApp} instance.
|
||||||
*
|
* <p>
|
||||||
* Note: Many of the fields on this instance will not be known in this circumstance. Currently
|
* Note: Many of the fields on this instance will not be known in this circumstance. Currently
|
||||||
* the only things that are known are:
|
* the only things that are known are:
|
||||||
*
|
* <p>
|
||||||
* + {@link Apk#packageName}
|
* + {@link Apk#packageName}
|
||||||
* + {@link Apk#versionName}
|
* + {@link Apk#versionName}
|
||||||
* + {@link Apk#versionCode}
|
* + {@link Apk#versionCode}
|
||||||
* + {@link Apk#hash}
|
* + {@link Apk#hash}
|
||||||
* + {@link Apk#hashType}
|
* + {@link Apk#hashType}
|
||||||
*
|
* <p>
|
||||||
* This could instead be implemented by accepting a {@link PackageInfo} and it would get much
|
* This could instead be implemented by accepting a {@link PackageInfo} and it would get much
|
||||||
* the same information, but it wouldn't have the hash of the package. Seeing as we've already
|
* the same information, but it wouldn't have the hash of the package. Seeing as we've already
|
||||||
* done the hard work to calculate that hash and stored it in the database, we may as well use
|
* done the hard work to calculate that hash and stored it in the database, we may as well use
|
||||||
|
@ -16,6 +16,8 @@ import android.support.annotation.Nullable;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
import org.apache.commons.io.filefilter.RegexFileFilter;
|
import org.apache.commons.io.filefilter.RegexFileFilter;
|
||||||
import org.fdroid.fdroid.AppFilter;
|
import org.fdroid.fdroid.AppFilter;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
@ -39,6 +41,21 @@ import java.util.jar.JarFile;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an application, its availability, and its current installed state.
|
||||||
|
* This represents the app in general, for a specific version of this app, see
|
||||||
|
* {@link Apk}.
|
||||||
|
* <p>
|
||||||
|
* <b>Do not rename these instance variables without careful consideration!</b>
|
||||||
|
* They are mapped to JSON field names, the {@code fdroidserver} internal variable
|
||||||
|
* names, and the {@code fdroiddata} YAML field names. Only the instance variables
|
||||||
|
* listed in {@code @JsonIgnoreProperties} are not directly mapped.
|
||||||
|
*
|
||||||
|
* @see <a href="https://gitlab.com/fdroid/fdroiddata">fdroiddata</a>
|
||||||
|
* @see <a href="https://gitlab.com/fdroid/fdroidserver">fdroidserver</a>
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties({"compatible", "CREATOR", "id", "installedApk", "installedSig",
|
||||||
|
"installedVersionCode", "installedVersionName", "prefs", "repoId", })
|
||||||
public class App extends ValueObject implements Comparable<App>, Parcelable {
|
public class App extends ValueObject implements Comparable<App>, Parcelable {
|
||||||
|
|
||||||
private static final String TAG = "App";
|
private static final String TAG = "App";
|
||||||
|
@ -34,6 +34,14 @@ import java.net.MalformedURLException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a the descriptive info and metadata about a given repo, as provided
|
||||||
|
* by the repo index. This also keeps track of the state of the repo.
|
||||||
|
*
|
||||||
|
* @see <a href="https://gitlab.com/fdroid/fdroiddata">fdroiddata</a>
|
||||||
|
* @see <a href="https://gitlab.com/fdroid/fdroidserver">fdroidserver</a>
|
||||||
|
*/
|
||||||
public class Repo extends ValueObject {
|
public class Repo extends ValueObject {
|
||||||
|
|
||||||
public static final int VERSION_DENSITY_SPECIFIC_ICONS = 11;
|
public static final int VERSION_DENSITY_SPECIFIC_ICONS = 11;
|
||||||
@ -42,6 +50,8 @@ public class Repo extends ValueObject {
|
|||||||
public static final int PUSH_REQUEST_PROMPT = 1;
|
public static final int PUSH_REQUEST_PROMPT = 1;
|
||||||
public static final int PUSH_REQUEST_ACCEPT_ALWAYS = 2;
|
public static final int PUSH_REQUEST_ACCEPT_ALWAYS = 2;
|
||||||
|
|
||||||
|
public static final int INT_UNSET_VALUE = -1;
|
||||||
|
// these are never set by the Apk/package index metadata
|
||||||
protected long id;
|
protected long id;
|
||||||
|
|
||||||
public String address;
|
public String address;
|
||||||
|
@ -34,6 +34,7 @@ public abstract class Downloader {
|
|||||||
|
|
||||||
final URL sourceUrl;
|
final URL sourceUrl;
|
||||||
String cacheTag;
|
String cacheTag;
|
||||||
|
boolean notFound;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For sending download progress, should only be called in {@link #progressTask}
|
* For sending download progress, should only be called in {@link #progressTask}
|
||||||
@ -86,6 +87,13 @@ public abstract class Downloader {
|
|||||||
|
|
||||||
public abstract boolean isCached();
|
public abstract boolean isCached();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the requested file was not found in the repo (e.g. HTTP 404 Not Found)
|
||||||
|
*/
|
||||||
|
public boolean isNotFound() {
|
||||||
|
return notFound;
|
||||||
|
}
|
||||||
|
|
||||||
void downloadFromStream(int bufferSize, boolean resumable) throws IOException, InterruptedException {
|
void downloadFromStream(int bufferSize, boolean resumable) throws IOException, InterruptedException {
|
||||||
Utils.debugLog(TAG, "Downloading from stream");
|
Utils.debugLog(TAG, "Downloading from stream");
|
||||||
InputStream input = null;
|
InputStream input = null;
|
||||||
|
@ -79,17 +79,25 @@ public class HttpDownloader extends Downloader {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void download() throws IOException, InterruptedException {
|
public void download() throws IOException, InterruptedException {
|
||||||
boolean resumable = false;
|
|
||||||
long fileLength = outputFile.length();
|
|
||||||
|
|
||||||
// get the file size from the server
|
// get the file size from the server
|
||||||
HttpURLConnection tmpConn = getConnection();
|
HttpURLConnection tmpConn = getConnection();
|
||||||
tmpConn.setRequestMethod("HEAD");
|
tmpConn.setRequestMethod("HEAD");
|
||||||
int contentLength = -1;
|
int contentLength = -1;
|
||||||
if (tmpConn.getResponseCode() == 200) {
|
int statusCode = tmpConn.getResponseCode();
|
||||||
contentLength = tmpConn.getContentLength();
|
|
||||||
}
|
|
||||||
tmpConn.disconnect();
|
tmpConn.disconnect();
|
||||||
|
switch (statusCode) {
|
||||||
|
case 200:
|
||||||
|
contentLength = tmpConn.getContentLength();
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
notFound = true;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
Utils.debugLog(TAG, "HEAD check of " + sourceUrl + " returned " + statusCode + ": "
|
||||||
|
+ tmpConn.getResponseMessage());
|
||||||
|
}
|
||||||
|
boolean resumable = false;
|
||||||
|
long fileLength = outputFile.length();
|
||||||
if (fileLength > contentLength) {
|
if (fileLength > contentLength) {
|
||||||
FileUtils.deleteQuietly(outputFile);
|
FileUtils.deleteQuietly(outputFile);
|
||||||
} else if (fileLength == contentLength && outputFile.isFile()) {
|
} else if (fileLength == contentLength && outputFile.isFile()) {
|
||||||
@ -127,9 +135,6 @@ public class HttpDownloader extends Downloader {
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Whether the connection is resumable or not
|
|
||||||
*/
|
|
||||||
private void setupConnection(boolean resumable) throws IOException {
|
private void setupConnection(boolean resumable) throws IOException {
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
return;
|
return;
|
||||||
|
@ -43,7 +43,11 @@ public class LocalFileDownloader extends Downloader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void download() throws IOException, InterruptedException {
|
public void download() throws IOException, InterruptedException {
|
||||||
|
if (new File(sourceUrl.getPath()).exists()) {
|
||||||
downloadFromStream(1024 * 50, false);
|
downloadFromStream(1024 * 50, false);
|
||||||
|
} else {
|
||||||
|
notFound = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,205 @@
|
|||||||
|
package org.fdroid.fdroid.updater;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectReader;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
|
import org.fdroid.fdroid.IndexV1Updater;
|
||||||
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
import org.fdroid.fdroid.RepoUpdater;
|
||||||
|
import org.fdroid.fdroid.TestUtils;
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.data.FDroidProviderTest;
|
||||||
|
import org.fdroid.fdroid.data.Repo;
|
||||||
|
import org.fdroid.fdroid.data.RepoPushRequest;
|
||||||
|
import org.fdroid.fdroid.mock.RepoDetails;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricGradleTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
// TODO: Use sdk=24 when Robolectric supports this
|
||||||
|
@Config(constants = BuildConfig.class, sdk = 23)
|
||||||
|
@RunWith(RobolectricGradleTestRunner.class)
|
||||||
|
public class IndexV1UpdaterTest extends FDroidProviderTest {
|
||||||
|
public static final String TAG = "IndexV1UpdaterTest";
|
||||||
|
|
||||||
|
private static final String TESTY_JAR = "testy.at.or.at_index-v1.jar";
|
||||||
|
private static final String TESTY_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIndexV1Processing() throws IOException, RepoUpdater.UpdateException {
|
||||||
|
Preferences.setup(context);
|
||||||
|
Repo repo = MultiRepoUpdaterTest.createRepo("Testy", TESTY_JAR, context, TESTY_CERT);
|
||||||
|
IndexV1Updater updater = new IndexV1Updater(context, repo);
|
||||||
|
JarFile jarFile = new JarFile(TestUtils.copyResourceToTempFile(TESTY_JAR), true);
|
||||||
|
Log.i(TAG, "jarFile " + jarFile);
|
||||||
|
JarEntry indexEntry = (JarEntry) jarFile.getEntry(IndexV1Updater.DATA_FILE_NAME);
|
||||||
|
InputStream indexInputStream = jarFile.getInputStream(indexEntry);
|
||||||
|
updater.processIndexV1(indexInputStream, indexEntry, "fakeEtag");
|
||||||
|
IOUtils.closeQuietly(indexInputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RepoUpdater.SigningException.class)
|
||||||
|
public void testIndexV1WithWrongCert() throws IOException, RepoUpdater.UpdateException {
|
||||||
|
String badCert = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37";
|
||||||
|
Repo repo = MultiRepoUpdaterTest.createRepo("Testy", TESTY_JAR, context, badCert);
|
||||||
|
IndexV1Updater updater = new IndexV1Updater(context, repo);
|
||||||
|
JarFile jarFile = new JarFile(TestUtils.copyResourceToTempFile(TESTY_JAR), true);
|
||||||
|
JarEntry indexEntry = (JarEntry) jarFile.getEntry(IndexV1Updater.DATA_FILE_NAME);
|
||||||
|
InputStream indexInputStream = jarFile.getInputStream(indexEntry);
|
||||||
|
updater.processIndexV1(indexInputStream, indexEntry, "fakeEtag");
|
||||||
|
fail(); // it should never reach here, it should throw a SigningException
|
||||||
|
getClass().getResourceAsStream("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RepoUpdater.SigningException.class)
|
||||||
|
public void testIndexV1WithBadTestyJarNoManifest() throws IOException, RepoUpdater.UpdateException {
|
||||||
|
testBadTestyJar("testy.at.or.at_no-MANIFEST.MF_index-v1.jar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RepoUpdater.SigningException.class)
|
||||||
|
public void testIndexV1WithBadTestyJarNoSigningCert() throws IOException, RepoUpdater.UpdateException {
|
||||||
|
testBadTestyJar("testy.at.or.at_no-.RSA_index-v1.jar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RepoUpdater.SigningException.class)
|
||||||
|
public void testIndexV1WithBadTestyJarNoSignature() throws IOException, RepoUpdater.UpdateException {
|
||||||
|
testBadTestyJar("testy.at.or.at_no-.SF_index-v1.jar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RepoUpdater.SigningException.class)
|
||||||
|
public void testIndexV1WithBadTestyJarNoSignatureFiles() throws IOException, RepoUpdater.UpdateException {
|
||||||
|
testBadTestyJar("testy.at.or.at_no-signature_index-v1.jar");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testBadTestyJar(String jar) throws IOException, RepoUpdater.UpdateException {
|
||||||
|
Repo repo = MultiRepoUpdaterTest.createRepo("Testy", jar, context, TESTY_CERT);
|
||||||
|
IndexV1Updater updater = new IndexV1Updater(context, repo);
|
||||||
|
JarFile jarFile = new JarFile(TestUtils.copyResourceToTempFile(jar), true);
|
||||||
|
JarEntry indexEntry = (JarEntry) jarFile.getEntry(IndexV1Updater.DATA_FILE_NAME);
|
||||||
|
InputStream indexInputStream = jarFile.getInputStream(indexEntry);
|
||||||
|
updater.processIndexV1(indexInputStream, indexEntry, "fakeEtag");
|
||||||
|
fail(); // it should never reach here, it should throw a SigningException
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJacksonParsing() throws IOException {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
// the app ignores all unknown fields when complete, do not ignore during dev to catch mistakes
|
||||||
|
mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||||
|
JsonFactory f = mapper.getFactory();
|
||||||
|
JsonParser parser = f.createParser(TestUtils.copyResourceToTempFile("guardianproject_index-v1.json"));
|
||||||
|
|
||||||
|
Repo repo = null;
|
||||||
|
App[] apps = null;
|
||||||
|
Map<String, String[]> requests = null;
|
||||||
|
Map<String, List<Apk>> packages = null;
|
||||||
|
|
||||||
|
parser.nextToken(); // go into the main object block
|
||||||
|
while (true) {
|
||||||
|
String fieldName = parser.nextFieldName();
|
||||||
|
if (fieldName == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (fieldName) {
|
||||||
|
case "repo":
|
||||||
|
repo = parseRepo(mapper, parser);
|
||||||
|
break;
|
||||||
|
case "requests":
|
||||||
|
requests = parseRequests(mapper, parser);
|
||||||
|
break;
|
||||||
|
case "apps":
|
||||||
|
apps = parseApps(mapper, parser);
|
||||||
|
break;
|
||||||
|
case "packages":
|
||||||
|
packages = parsePackages(mapper, parser);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parser.close(); // ensure resources get cleaned up timely and properly
|
||||||
|
|
||||||
|
RepoDetails indexV0Details = getFromFile("guardianproject_index.xml",
|
||||||
|
Repo.PUSH_REQUEST_ACCEPT_ALWAYS);
|
||||||
|
indexV0Details.apps.size();
|
||||||
|
|
||||||
|
System.out.println("total apps: " + apps.length + " " + indexV0Details.apps.size());
|
||||||
|
assertEquals(indexV0Details.apps.size(), apps.length);
|
||||||
|
assertEquals(apps.length, packages.size());
|
||||||
|
|
||||||
|
int totalApks = 0;
|
||||||
|
for (String packageName : packages.keySet()) {
|
||||||
|
totalApks += packages.get(packageName).size();
|
||||||
|
}
|
||||||
|
assertEquals(totalApks, indexV0Details.apks.size());
|
||||||
|
|
||||||
|
assertEquals(indexV0Details.icon, repo.icon);
|
||||||
|
assertEquals(indexV0Details.timestamp, repo.timestamp / 1000); // V1 is in millis
|
||||||
|
assertEquals(indexV0Details.name, repo.name);
|
||||||
|
assertArrayEquals(indexV0Details.mirrors, repo.mirrors);
|
||||||
|
|
||||||
|
ArrayList<String> installRequests = new ArrayList<>();
|
||||||
|
for (RepoPushRequest repoPushRequest : indexV0Details.repoPushRequestList) {
|
||||||
|
if ("install".equals(repoPushRequest.request)) {
|
||||||
|
installRequests.add(repoPushRequest.packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertArrayEquals(installRequests.toArray(), requests.get("install"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Repo parseRepo(ObjectMapper mapper, JsonParser parser) throws IOException {
|
||||||
|
System.out.println("parseRepo ");
|
||||||
|
parser.nextToken();
|
||||||
|
parser.nextToken();
|
||||||
|
ObjectReader repoReader = mapper.readerFor(Repo.class);
|
||||||
|
return repoReader.readValue(parser, Repo.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String[]> parseRequests(ObjectMapper mapper, JsonParser parser) throws IOException {
|
||||||
|
TypeReference<HashMap<String, String[]>> typeRef = new TypeReference<HashMap<String, String[]>>() {
|
||||||
|
};
|
||||||
|
parser.nextToken(); // START_OBJECT
|
||||||
|
return mapper.readValue(parser, typeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private App[] parseApps(ObjectMapper mapper, JsonParser parser) throws IOException {
|
||||||
|
TypeReference<App[]> typeRef = new TypeReference<App[]>() {
|
||||||
|
};
|
||||||
|
parser.nextToken(); // START_ARRAY
|
||||||
|
return mapper.readValue(parser, typeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, List<Apk>> parsePackages(ObjectMapper mapper, JsonParser parser) throws IOException {
|
||||||
|
TypeReference<HashMap<String, List<Apk>>> typeRef = new TypeReference<HashMap<String, List<Apk>>>() {
|
||||||
|
};
|
||||||
|
parser.nextToken(); // START_OBJECT
|
||||||
|
return mapper.readValue(parser, typeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private RepoDetails getFromFile(String indexFilename, int pushRequests) {
|
||||||
|
return RepoXMLHandlerTest.getFromFile(getClass().getClassLoader(), indexFilename, pushRequests);
|
||||||
|
}
|
||||||
|
}
|
@ -159,7 +159,7 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest {
|
|||||||
return createRepo(name, uri, context, PUB_KEY);
|
return createRepo(name, uri, context, PUB_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Repo createRepo(String name, String uri, Context context, String signingCert) {
|
static Repo createRepo(String name, String uri, Context context, String signingCert) {
|
||||||
Repo repo = new Repo();
|
Repo repo = new Repo();
|
||||||
repo.signingCertificate = signingCert;
|
repo.signingCertificate = signingCert;
|
||||||
repo.address = uri;
|
repo.address = uri;
|
||||||
|
@ -842,8 +842,13 @@ public class RepoXMLHandlerTest {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private RepoDetails getFromFile(String indexFilename, int pushRequests) {
|
private RepoDetails getFromFile(String indexFilename, int pushRequests) {
|
||||||
Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(indexFilename));
|
return getFromFile(getClass().getClassLoader(), indexFilename, pushRequests);
|
||||||
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(indexFilename);
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
static RepoDetails getFromFile(ClassLoader classLoader, String indexFilename, int pushRequests) {
|
||||||
|
Log.i(TAG, "test file: " + classLoader.getResource(indexFilename));
|
||||||
|
InputStream inputStream = classLoader.getResourceAsStream(indexFilename);
|
||||||
return RepoDetails.getFromFile(inputStream, pushRequests);
|
return RepoDetails.getFromFile(inputStream, pushRequests);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1974
app/src/test/resources/guardianproject_index-v1.json
Normal file
1974
app/src/test/resources/guardianproject_index-v1.json
Normal file
File diff suppressed because it is too large
Load Diff
790
app/src/test/resources/guardianproject_index.xml
Normal file
790
app/src/test/resources/guardianproject_index.xml
Normal file
@ -0,0 +1,790 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<fdroid>
|
||||||
|
<repo icon="guardianproject.png" name="Guardian Project Official Releases" pubkey="308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6" timestamp="1488828510" url="https://guardianproject.info/fdroid/repo" version="18">
|
||||||
|
<description>The official app repository of The Guardian Project. Applications in this repository are official binaries build by the original application developers and signed by the same key as the APKs that are released in the Google Play store. </description>
|
||||||
|
<mirror>http://bdf2wcxujkg6qqff.onion/fdroid/repo</mirror>
|
||||||
|
<mirror>https://guardianproject.info/fdroid/repo</mirror>
|
||||||
|
<mirror>https://s3.amazonaws.com/guardianproject/fdroid/repo</mirror>
|
||||||
|
</repo>
|
||||||
|
<install packageName="org.torproject.android"/>
|
||||||
|
<install packageName="info.guardianproject.orfox"/>
|
||||||
|
<application id="info.guardianproject.cacert">
|
||||||
|
<id>info.guardianproject.cacert</id>
|
||||||
|
<added>2013-08-19</added>
|
||||||
|
<lastupdated>2013-10-31</lastupdated>
|
||||||
|
<name>CACertMan</name>
|
||||||
|
<summary>Disable untrusted certificates</summary>
|
||||||
|
<icon>info.guardianproject.cacert.4.png</icon>
|
||||||
|
<desc><p>Android 4+ allows you to disable certificates from the system Settings and root isn't required, so try that first if you want to manually mess with the certificates. The app won't work with Android 4+ anyway.</p><p>An app to manage security certificates on your phone also containing a version of the Android CACert keystore derived from Mozilla. If a certificate has recently become untrusted you can either install an update to this app or you can backup and remove certificates by yourself.</p><p>Requires root: Yes, it writes to the system partition. You will need a device that has the ‘grep’ command on it (via busybox: present on most custom ROMs). If the ‘save’ doesn’t work, then you will need to make your /system partition read-write by using a file explorer like <a href="fdroid.app:com.ghostsq.commander">Ghost Commander</a> or via a command in <a href="fdroid.app:jackpal.androidterm">Terminal Emulator</a>.</p></desc>
|
||||||
|
<license>GPLv3</license>
|
||||||
|
<categories>Security,GuardianProject</categories>
|
||||||
|
<category>Security</category>
|
||||||
|
<web>https://guardianproject.info/2011/09/05/cacertman-app-to-address-diginotar-other-bad-cas</web>
|
||||||
|
<source>https://github.com/guardianproject/cacert</source>
|
||||||
|
<tracker>https://github.com/guardianproject/cacert/issues</tracker>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<requirements>root</requirements>
|
||||||
|
<package>
|
||||||
|
<version>0.0.2.20111012</version>
|
||||||
|
<versioncode>4</versioncode>
|
||||||
|
<apkname>CACertMan-0.0.2-alpha-20111011.apk</apkname>
|
||||||
|
<hash type="sha256">251ebd40ce4a281a2292692707fb1e9c91428994cbad80a416a297db51069eb8</hash>
|
||||||
|
<size>172263</size>
|
||||||
|
<sdkver>7</sdkver>
|
||||||
|
<targetSdkVersion>7</targetSdkVersion>
|
||||||
|
<added>2013-08-19</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.0.2-20110906</version>
|
||||||
|
<versioncode>3</versioncode>
|
||||||
|
<apkname>CACertMan-0.0.2-20110906.apk</apkname>
|
||||||
|
<hash type="sha256">c217c49abe5134007ceb2623a6189a73fa02af9d2b2bbcc5cbc4cb5da7b36a5d</hash>
|
||||||
|
<size>170305</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>8</targetSdkVersion>
|
||||||
|
<added>2013-10-31</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="org.witness.informacam.app">
|
||||||
|
<id>org.witness.informacam.app</id>
|
||||||
|
<added>2013-12-11</added>
|
||||||
|
<lastupdated>2015-11-03</lastupdated>
|
||||||
|
<name>CameraV</name>
|
||||||
|
<summary>An InformaCam app to generate verifiable media</summary>
|
||||||
|
<icon>org.witness.informacam.app.206.png</icon>
|
||||||
|
<desc><p>An InformaCam app to generate verifiable media.</p></desc>
|
||||||
|
<license>GPLv3</license>
|
||||||
|
<categories>Development,GuardianProject</categories>
|
||||||
|
<category>Development</category>
|
||||||
|
<web>https://guardianproject.info/apps/camerav/</web>
|
||||||
|
<source>https://github.com/guardianproject/CameraV</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/informacam/issues</tracker>
|
||||||
|
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>9999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>0.2.6</version>
|
||||||
|
<versioncode>206</versioncode>
|
||||||
|
<apkname>CameraVApp-release-0.2.6.apk</apkname>
|
||||||
|
<hash type="sha256">508f453e26c8c83dba858b53b21d909d549fe5646d01eb198c96c22d8e521e7c</hash>
|
||||||
|
<size>24123646</size>
|
||||||
|
<sdkver>16</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2015-11-03</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>CAMERA,READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,USE_CREDENTIALS,RECORD_AUDIO,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,ACCESS_FINE_LOCATION,GET_ACCOUNTS,INTERNET,ACCESS_COARSE_LOCATION,READ_PHONE_STATE,KILL_BACKGROUND_PROCESSES,GET_TOP_ACTIVITY_INFO,ACCESS_NETWORK_STATE,BLUETOOTH,WAKE_LOCK</permissions>
|
||||||
|
<nativecode>armeabi,armeabi-v7a,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.2.4</version>
|
||||||
|
<versioncode>204</versioncode>
|
||||||
|
<apkname>CameraV-release-0.2.4.apk</apkname>
|
||||||
|
<hash type="sha256">a10eefaed5a12c353525b07e655f6959fe1eb06cd5c549be56afaca6db0c6ce0</hash>
|
||||||
|
<size>24062229</size>
|
||||||
|
<sdkver>16</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2015-10-02</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>CAMERA,READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,USE_CREDENTIALS,RECORD_AUDIO,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,ACCESS_FINE_LOCATION,GET_ACCOUNTS,INTERNET,ACCESS_COARSE_LOCATION,READ_PHONE_STATE,KILL_BACKGROUND_PROCESSES,ACCESS_NETWORK_STATE,GET_TASKS,BLUETOOTH,WAKE_LOCK</permissions>
|
||||||
|
<nativecode>armeabi,armeabi-v7a,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.2.2</version>
|
||||||
|
<versioncode>202</versioncode>
|
||||||
|
<apkname>CameraVApp-release-0.2.2.apk</apkname>
|
||||||
|
<hash type="sha256">8b17cbe2a5cb777b49f5ef67a390f9d9d68765c90213ac64f3ca0456860dc9b7</hash>
|
||||||
|
<size>24283932</size>
|
||||||
|
<sdkver>16</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2015-09-16</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>CAMERA,READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,USE_CREDENTIALS,RECORD_AUDIO,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,ACCESS_FINE_LOCATION,GET_ACCOUNTS,INTERNET,ACCESS_COARSE_LOCATION,READ_PHONE_STATE,KILL_BACKGROUND_PROCESSES,ACCESS_NETWORK_STATE,GET_TASKS,BLUETOOTH,WAKE_LOCK</permissions>
|
||||||
|
<nativecode>armeabi,armeabi-v7a,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.otr.app.im">
|
||||||
|
<id>info.guardianproject.otr.app.im</id>
|
||||||
|
<added>2013-03-19</added>
|
||||||
|
<lastupdated>2016-12-06</lastupdated>
|
||||||
|
<name>ChatSecure</name>
|
||||||
|
<summary>Instant Messaging client with OTR</summary>
|
||||||
|
<icon>info.guardianproject.otr.app.im.1423001.png</icon>
|
||||||
|
<desc><p>XMPP (Jabber) client that can do end-to-end encryption using the <a href="http://en.wikipedia.org/wiki/Off-the-Record_Messaging">Off-the-Record Messaging</a> protocol and can anonymize your chats via the <a href="fdroid.app:org.torproject.android">Orbot</a> app (root not required).</p><p>The app used to be called GibberBot.</p></desc>
|
||||||
|
<license>Apache2</license>
|
||||||
|
<categories>Internet,GuardianProject</categories>
|
||||||
|
<category>Internet</category>
|
||||||
|
<web>https://dev.guardianproject.info/projects/gibberbot</web>
|
||||||
|
<source>https://github.com/guardianproject/Gibberbot</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/gibberbot</tracker>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>14.2.3</version>
|
||||||
|
<versioncode>1423001</versioncode>
|
||||||
|
<apkname>ChatSecure-v14.2.3a.apk</apkname>
|
||||||
|
<hash type="sha256">36d7d71c8a2115bdd2bd63bb639af286ee3242cce11cdb5c53378d1a7f35528e</hash>
|
||||||
|
<size>10502397</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2016-02-03</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>info.guardianproject.otr.app.providers.imps.permission.READ_ONLY,RECEIVE_BOOT_COMPLETED,CHANGE_WIFI_MULTICAST_STATE,READ_EXTERNAL_STORAGE,com.google.android.googleapps.permission.GOOGLE_AUTH,USE_CREDENTIALS,VIBRATE,WRITE_EXTERNAL_STORAGE,GET_ACCOUNTS,ACCESS_WIFI_STATE,UPDATE_APP_OPS_STATS,INTERNET,info.guardianproject.otr.app.im.permission.IM_SERVICE,ACCESS_NETWORK_STATE,MANAGE_ACCOUNTS,info.guardianproject.otr.app.providers.imps.permission.WRITE_ONLY,WAKE_LOCK</permissions>
|
||||||
|
<nativecode>armeabi,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>14.2.2</version>
|
||||||
|
<versioncode>1422001</versioncode>
|
||||||
|
<apkname>ChatSecure-v14.2.2.apk</apkname>
|
||||||
|
<hash type="sha256">9d4620fec0c7837ddffccde7918d7a7db0976fbcd361b96659abd93b5cc0d9e3</hash>
|
||||||
|
<size>10502135</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2016-12-06</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>info.guardianproject.otr.app.providers.imps.permission.READ_ONLY,RECEIVE_BOOT_COMPLETED,CHANGE_WIFI_MULTICAST_STATE,READ_EXTERNAL_STORAGE,com.google.android.googleapps.permission.GOOGLE_AUTH,USE_CREDENTIALS,VIBRATE,WRITE_EXTERNAL_STORAGE,GET_ACCOUNTS,ACCESS_WIFI_STATE,UPDATE_APP_OPS_STATS,INTERNET,info.guardianproject.otr.app.im.permission.IM_SERVICE,ACCESS_NETWORK_STATE,MANAGE_ACCOUNTS,info.guardianproject.otr.app.providers.imps.permission.WRITE_ONLY,WAKE_LOCK</permissions>
|
||||||
|
<nativecode>armeabi,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>14.2.1</version>
|
||||||
|
<versioncode>1421001</versioncode>
|
||||||
|
<apkname>ChatSecure-v14.2.1.apk</apkname>
|
||||||
|
<hash type="sha256">f82a3a7a823f5540b335743eb1399d0fd1f61bc68958750b5ef6aa0d95ad9a54</hash>
|
||||||
|
<size>10463010</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2015-08-24</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>info.guardianproject.otr.app.providers.imps.permission.READ_ONLY,RECEIVE_BOOT_COMPLETED,CHANGE_WIFI_MULTICAST_STATE,READ_EXTERNAL_STORAGE,com.google.android.googleapps.permission.GOOGLE_AUTH,USE_CREDENTIALS,VIBRATE,WRITE_EXTERNAL_STORAGE,GET_ACCOUNTS,ACCESS_WIFI_STATE,UPDATE_APP_OPS_STATS,INTERNET,info.guardianproject.otr.app.im.permission.IM_SERVICE,ACCESS_NETWORK_STATE,MANAGE_ACCOUNTS,info.guardianproject.otr.app.providers.imps.permission.WRITE_ONLY,WAKE_LOCK</permissions>
|
||||||
|
<nativecode>armeabi,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.soundrecorder">
|
||||||
|
<id>info.guardianproject.soundrecorder</id>
|
||||||
|
<added>2013-12-13</added>
|
||||||
|
<lastupdated>2013-12-13</lastupdated>
|
||||||
|
<name>ChatSecureVoicePlugin</name>
|
||||||
|
<summary>ChatSecure Voice Messaging</summary>
|
||||||
|
<icon>info.guardianproject.soundrecorder.2.png</icon>
|
||||||
|
<desc><p>This is a plugin for <a href="fdroid.app:info.guardianproject.otr.app.im">ChatSecure</a>. It does not have any function on its own. For Your Ears Only... completely private, end-to-end encryption voice message recording, sending, receiving and playback.</p><p> * For use with <a href="fdroid.app:info.guardianproject.otr.app.im">ChatSecure</a>'s encrypted "Off-the-record" data stream * Works over Tor - the ONLY Onion-routed voice messaging system, for total anonymity</p></desc>
|
||||||
|
<license>SIL Open Font License, MIT License and the CC 3.0 License [CC-By with attribution requirement waived]</license>
|
||||||
|
<categories>Multimedia,Security,GuardianProject</categories>
|
||||||
|
<category>Multimedia</category>
|
||||||
|
<web>https://guardianproject.info/apps/chatsecure</web>
|
||||||
|
<source>https://github.com/guardianproject/ChatSecureVoicePlugin</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/chatsecure/issues</tracker>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>0.2</version>
|
||||||
|
<versioncode>2</versioncode>
|
||||||
|
<apkname>ChatSecureVoiceMessaging-0.2.apk</apkname>
|
||||||
|
<hash type="sha256">abae18cc9cfa62fca5dce072c4c50d41b4fece506967ce9a3e2711cd1031dbee</hash>
|
||||||
|
<size>394212</size>
|
||||||
|
<sdkver>10</sdkver>
|
||||||
|
<targetSdkVersion>10</targetSdkVersion>
|
||||||
|
<added>2013-12-13</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>READ_PHONE_STATE,READ_EXTERNAL_STORAGE,RECORD_AUDIO,WRITE_EXTERNAL_STORAGE,WAKE_LOCK</permissions>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.checkey">
|
||||||
|
<id>info.guardianproject.checkey</id>
|
||||||
|
<added>2014-07-12</added>
|
||||||
|
<lastupdated>2015-03-09</lastupdated>
|
||||||
|
<name>Checkey</name>
|
||||||
|
<summary>Info on local apps</summary>
|
||||||
|
<icon>info.guardianproject.checkey.102.png</icon>
|
||||||
|
<desc><p>Checkey is a utility for getting information about the APKs that are installed on your device. Starting with a list of all of the apps that you have installed on your device, it will show you the APK signature with a single touch, and provides links to virustotal.com and androidobservatory.org to easily access the profiles of that APK. It will also let you export the signing certificate and generate ApkSignaturePin pin files for use with the TrustedIntents library.</p></desc>
|
||||||
|
<license>GPLv3</license>
|
||||||
|
<categories>Development,GuardianProject</categories>
|
||||||
|
<category>Development</category>
|
||||||
|
<web>https://dev.guardianproject.info/projects/checkey</web>
|
||||||
|
<source>https://github.com/guardianproject/checkey</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/checkey/issues</tracker>
|
||||||
|
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>9999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>0.1.2</version>
|
||||||
|
<versioncode>102</versioncode>
|
||||||
|
<apkname>Checkey-0.1.2.apk</apkname>
|
||||||
|
<hash type="sha256">754701dbac52de5ca3930c2393970c03ef9aa07d1456911e9bf254d6014e0645</hash>
|
||||||
|
<size>842881</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2015-03-09</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>INTERNET</permissions>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.1.1</version>
|
||||||
|
<versioncode>101</versioncode>
|
||||||
|
<apkname>Checkey-0.1.1.apk</apkname>
|
||||||
|
<hash type="sha256">2d81f339bb69626af42e8868dc6928c9072ebcbae76e1ff5ac8172e78ebe9cdd</hash>
|
||||||
|
<size>967083</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2015-01-28</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>INTERNET</permissions>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.1</version>
|
||||||
|
<versioncode>1</versioncode>
|
||||||
|
<apkname>Checkey-0.1.apk</apkname>
|
||||||
|
<hash type="sha256">a8e3c102d5279a3029d0eebdeda2ffdbe1f8a3493ea7dbdc31a11affc708ee57</hash>
|
||||||
|
<size>878679</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>19</targetSdkVersion>
|
||||||
|
<added>2014-07-12</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>INTERNET</permissions>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.courier">
|
||||||
|
<id>info.guardianproject.courier</id>
|
||||||
|
<added>2014-05-14</added>
|
||||||
|
<lastupdated>2014-06-26</lastupdated>
|
||||||
|
<name>Courier</name>
|
||||||
|
<summary>Privacy-aware RSS feed reader</summary>
|
||||||
|
<icon>info.guardianproject.courier.15.png</icon>
|
||||||
|
<desc><p>No description available</p></desc>
|
||||||
|
<license>GPLv3</license>
|
||||||
|
<categories>Reading,GuardianProject</categories>
|
||||||
|
<category>Reading</category>
|
||||||
|
<web/>
|
||||||
|
<source>https://github.com/guardianproject/securereader</source>
|
||||||
|
<tracker/>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>0.1.9</version>
|
||||||
|
<versioncode>15</versioncode>
|
||||||
|
<apkname>Courier-0.1.9.apk</apkname>
|
||||||
|
<hash type="sha256">bf6566da1f90831887f5bf5605f8d816b1f7f694969459dec599b8bc01a827d3</hash>
|
||||||
|
<size>16484753</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>15</targetSdkVersion>
|
||||||
|
<added>2014-06-26</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,VIBRATE,ACCESS_WIFI_STATE,INTERNET,ACCESS_NETWORK_STATE,BLUETOOTH,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<nativecode>armeabi,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.1.8</version>
|
||||||
|
<versioncode>14</versioncode>
|
||||||
|
<apkname>Courier-0.1.8.apk</apkname>
|
||||||
|
<hash type="sha256">e013db095e8da843fae5ac44be6152e51377ee717e5c8a7b6d913d7720566b5a</hash>
|
||||||
|
<size>16536125</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>15</targetSdkVersion>
|
||||||
|
<added>2014-05-14</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,VIBRATE,ACCESS_WIFI_STATE,INTERNET,ACCESS_NETWORK_STATE,BLUETOOTH,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<nativecode>armeabi,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.lildebi">
|
||||||
|
<id>info.guardianproject.lildebi</id>
|
||||||
|
<added>2013-02-06</added>
|
||||||
|
<lastupdated>2015-01-26</lastupdated>
|
||||||
|
<name>Lil' Debi</name>
|
||||||
|
<summary>Run Debian on your phone</summary>
|
||||||
|
<icon>info.guardianproject.lildebi.5400.png</icon>
|
||||||
|
<desc><p>Lil' Debi builds up a whole Debian chroot on your phone entirely using debootstrap. You choose the release, mirror, and size of the disk image, and away it goes. It could take up to an hour on a slow device.</p><p>Then it has a simple chroot manager that fscks your disk, mounts/unmounts things, starts/stops sshd if you have it installed, etc. You can also then use ‘apt-get’ to install any package that is released for ARM processors. This includes things like a complete real shell, Tor, TraceRouteTCP, iwconfig/ipconfig, and other security and crypto tools. Works well with <a href="fdroid.app:jackpal.androidterm">Terminal Emulator</a>—just run `/debian/shell` to get a Debian shell.</p><p>The aim of Lil’ Debi is to provide a transparent and tightly integrated Debian install on your Android device. It mounts all of your Android partitions in Debian space, so you see a fusion of both systems. It's even possible to have Lil’ Debi launch the normal Debian init start-up scripts when it starts, so that all you need to do is apt-get install and any servers you install will just work.</p><p>Lil' Debi works with as few modifications to the Android system as possible. Currently, it only adds a /bin symlink, and a /debian mount directory. It does not touch /system at all.</p><p>Requires root: Yes, because it needs to run debootstrap, create dirs in /, mount/umount, etc.</p></desc>
|
||||||
|
<license>GPLv3</license>
|
||||||
|
<categories>Development,GuardianProject</categories>
|
||||||
|
<category>Development</category>
|
||||||
|
<web>https://github.com/guardianproject/lildebi/wiki</web>
|
||||||
|
<source>https://github.com/guardianproject/lildebi</source>
|
||||||
|
<tracker>https://github.com/guardianproject/lildebi/issues</tracker>
|
||||||
|
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<requirements>root</requirements>
|
||||||
|
<package>
|
||||||
|
<version>0.5.4</version>
|
||||||
|
<versioncode>5400</versioncode>
|
||||||
|
<apkname>LilDebi-0.5.4-release.apk</apkname>
|
||||||
|
<hash type="sha256">2c490376d8853fae04e79541f5d61e66a42ed0e890208945a11036c4a7b111da</hash>
|
||||||
|
<size>1876705</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>20</targetSdkVersion>
|
||||||
|
<maxsdkver>20</maxsdkver>
|
||||||
|
<added>2015-01-26</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>RECEIVE_BOOT_COMPLETED,READ_EXTERNAL_STORAGE,ACCESS_SUPERUSER,WRITE_EXTERNAL_STORAGE,INTERNET,ACCESS_NETWORK_STATE,jackpal.androidterm.permission.RUN_SCRIPT,WAKE_LOCK</permissions>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.5.3</version>
|
||||||
|
<versioncode>5300</versioncode>
|
||||||
|
<apkname>LilDebi-0.5.3-release.apk</apkname>
|
||||||
|
<hash type="sha256">01c5a8e1fd778c141e70633d14f1b69228d6f492961098616e0446c116cf9e44</hash>
|
||||||
|
<size>1879560</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>19</targetSdkVersion>
|
||||||
|
<added>2015-01-26</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>RECEIVE_BOOT_COMPLETED,READ_EXTERNAL_STORAGE,ACCESS_SUPERUSER,WRITE_EXTERNAL_STORAGE,INTERNET,ACCESS_NETWORK_STATE,jackpal.androidterm.permission.RUN_SCRIPT,WAKE_LOCK</permissions>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.5.2</version>
|
||||||
|
<versioncode>5200</versioncode>
|
||||||
|
<apkname>LilDebi-0.5.2-release.apk</apkname>
|
||||||
|
<hash type="sha256">07fa3dfb690e44eb540942ba2a51718c72351c91a253a56a0c90649f6d8903dd</hash>
|
||||||
|
<size>1861790</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>19</targetSdkVersion>
|
||||||
|
<added>2014-10-22</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>RECEIVE_BOOT_COMPLETED,READ_EXTERNAL_STORAGE,ACCESS_SUPERUSER,WRITE_EXTERNAL_STORAGE,INTERNET,ACCESS_NETWORK_STATE,jackpal.androidterm.permission.RUN_SCRIPT,WAKE_LOCK</permissions>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.locationprivacy">
|
||||||
|
<id>info.guardianproject.locationprivacy</id>
|
||||||
|
<added>2015-01-29</added>
|
||||||
|
<lastupdated>2016-12-06</lastupdated>
|
||||||
|
<name>LocationPrivacy</name>
|
||||||
|
<summary>privacy filters for when you are sharing your location</summary>
|
||||||
|
<icon>info.guardianproject.locationprivacy.30.png</icon>
|
||||||
|
<desc><p>LocationPrivacy is not really app but rather a set of "Intent Filters" for all of the various ways of sharing location. When you share location from one app, LocationPrivacy offers itself as an option. It then recognizes insecure methods of sharing location, and then converts them to more secure methods. This mostly means that it rewrites URLs to use https, and even to use `geo:` URIs, which can work on fully offline setups. LocationPrivacy mostly works by reading the location information from the URL itself. For many URLs, LocationPrivacy must actually load some of the webpage in order to get the location.</p><p>LocationPrivacy can also serve as a way to redirect all location links to your favorite mapping app. All map apps in Android can view `geo:` URIs, and LocationPrivacy converts many kinds of links to `geo:` URIs, including: Google Maps, OpenStreetMap, Amap, Baidu Map, QQ Map, Nokia HERE, Yandex Maps.</p><p>This was started as part of the T2 Panic work, since sharing location is so often a part of panic apps. Follow our progress here: https://guardianproject.info/tag/panic</p><p>Don’t see your language? Join us and help translate the app: https://www.transifex.com/projects/p/locationprivacy</p></desc>
|
||||||
|
<license>GPLv3</license>
|
||||||
|
<categories>Navigation,Security,GuardianProject</categories>
|
||||||
|
<category>Navigation</category>
|
||||||
|
<web>https://dev.guardianproject.info/projects/panic</web>
|
||||||
|
<source>https://github.com/guardianproject/LocationPrivacy</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/panic/issues</tracker>
|
||||||
|
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>9999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>0.3</version>
|
||||||
|
<versioncode>30</versioncode>
|
||||||
|
<apkname>LocationPrivacy-0.3.apk</apkname>
|
||||||
|
<hash type="sha256">ec2b2c6e3a99422fbe8229711dfc7b741961c2ba7bc171c745818d8b76fc4d63</hash>
|
||||||
|
<size>1130602</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>22</targetSdkVersion>
|
||||||
|
<added>2016-12-06</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>INTERNET</permissions>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.2</version>
|
||||||
|
<versioncode>20</versioncode>
|
||||||
|
<apkname>LocationPrivacy-0.2.apk</apkname>
|
||||||
|
<hash type="sha256">3cad63152ef9b04e1c2b880c286a80c65c083880612aaa36c0c4480b96adfea8</hash>
|
||||||
|
<size>1129409</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>22</targetSdkVersion>
|
||||||
|
<added>2016-12-06</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>INTERNET</permissions>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.1</version>
|
||||||
|
<versioncode>10</versioncode>
|
||||||
|
<apkname>LocationPrivacy-0.1.apk</apkname>
|
||||||
|
<hash type="sha256">130cfcc8b916682d974aa4e13385b47bdc23d07b0de852640563b880aeb61d1f</hash>
|
||||||
|
<size>818384</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2015-01-29</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>INTERNET</permissions>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.notepadbot">
|
||||||
|
<id>info.guardianproject.notepadbot</id>
|
||||||
|
<added>2013-01-16</added>
|
||||||
|
<lastupdated>2014-03-10</lastupdated>
|
||||||
|
<name>NoteCipher</name>
|
||||||
|
<summary>Notepad with lock</summary>
|
||||||
|
<icon>info.guardianproject.notepadbot.12.png</icon>
|
||||||
|
<desc><p>Simple app for taking notes that encrypts everything behind a password.</p><p>Status: Beta.</p></desc>
|
||||||
|
<license>Apache2</license>
|
||||||
|
<categories>Office,GuardianProject</categories>
|
||||||
|
<category>Office</category>
|
||||||
|
<web>https://guardianproject.info</web>
|
||||||
|
<source>https://github.com/guardianproject/notecipher</source>
|
||||||
|
<tracker>https://github.com/guardianproject/notecipher/issues</tracker>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>0.1</version>
|
||||||
|
<versioncode>12</versioncode>
|
||||||
|
<apkname>NoteCipher-beta-0.1.apk</apkname>
|
||||||
|
<hash type="sha256">b560a3d6364c32990ea7505f53b019f64fde597d67513f41a50e7d034af48caa</hash>
|
||||||
|
<size>7321123</size>
|
||||||
|
<sdkver>10</sdkver>
|
||||||
|
<targetSdkVersion>19</targetSdkVersion>
|
||||||
|
<added>2014-03-10</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>VIBRATE,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<nativecode>armeabi,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.0.7.1</version>
|
||||||
|
<versioncode>11</versioncode>
|
||||||
|
<apkname>NoteCipher-0.0.7.1.apk</apkname>
|
||||||
|
<hash type="sha256">da518f13206d2218234bfcc83205b7b2b81ec67a4cc448f818c617332235e700</hash>
|
||||||
|
<size>3729342</size>
|
||||||
|
<sdkver>11</sdkver>
|
||||||
|
<targetSdkVersion>17</targetSdkVersion>
|
||||||
|
<added>2013-10-31</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<nativecode>armeabi</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.0.7</version>
|
||||||
|
<versioncode>10</versioncode>
|
||||||
|
<apkname>NoteCipher-0.0.7.apk</apkname>
|
||||||
|
<hash type="sha256">8fa7536a87634c6b3441053c4f16315e4fd5aa6ef672a0026a594c107308d7bf</hash>
|
||||||
|
<size>3731119</size>
|
||||||
|
<sdkver>7</sdkver>
|
||||||
|
<targetSdkVersion>17</targetSdkVersion>
|
||||||
|
<added>2013-01-16</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<nativecode>armeabi</nativecode>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="org.witness.sscphase1">
|
||||||
|
<id>org.witness.sscphase1</id>
|
||||||
|
<added>2013-08-19</added>
|
||||||
|
<lastupdated>2013-10-31</lastupdated>
|
||||||
|
<name>ObscuraCam</name>
|
||||||
|
<summary>A camera app that keeps certain information private</summary>
|
||||||
|
<icon>org.witness.sscphase1.34.png</icon>
|
||||||
|
<desc><p>Ever capture someone in a photo or video, then realize they may not want to be in it? Not comfortable posting a friend, family member or child’s face on the internet? Worried about the geolocation data in the picture giving away private hideaway? Tired of Facebook, Google and other sites “auto detecting” faces in your photos? Then this is for you, giving you the power to better protect the identity of those captures in your photos, before you post them online.</p><p>Take a picture or load a photo or video from the Gallery, and ObscuraCam will automatically detect faces that you can pixelate, redact (blackout) or protect with funny nose and glasses. You can also invert pixelate, so that only the person you select is visible, and no one in the background can be recognized.</p><p>This app will also remove all identifying data stored in photos including GPS location data and phone make &amp; model. You can save the protected photo back to the Gallery, or share it directly to Facebook, Twitter or any other “Share” enabled app.</p></desc>
|
||||||
|
<license>GPLv3</license>
|
||||||
|
<categories>Multimedia,Security,GuardianProject</categories>
|
||||||
|
<category>Multimedia</category>
|
||||||
|
<web>https://guardianproject.info/apps/obscuracam</web>
|
||||||
|
<source>https://github.com/guardianproject/obscuracam</source>
|
||||||
|
<tracker>https://github.com/guardianproject/obscuracam/issues</tracker>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>2.0-RC2b</version>
|
||||||
|
<versioncode>34</versioncode>
|
||||||
|
<apkname>ObscuraCam-2.0-RC2b.apk</apkname>
|
||||||
|
<hash type="sha256">eeea54985c96769524ec82fb1d3599b193a2d20d1f57f3afc4c97b11bd48df8f</hash>
|
||||||
|
<size>8240221</size>
|
||||||
|
<sdkver>10</sdkver>
|
||||||
|
<targetSdkVersion>11</targetSdkVersion>
|
||||||
|
<added>2013-10-31</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>READ_EXTERNAL_STORAGE,READ_MEDIA_STORAGE,WRITE_MEDIA_STORAGE,VIBRATE,WAKE_LOCK,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<nativecode>armeabi</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>1.2-FINAL</version>
|
||||||
|
<versioncode>25</versioncode>
|
||||||
|
<apkname>ObscuraCam-1.2-FINAL.apk</apkname>
|
||||||
|
<hash type="sha256">fc4b1e26b09ab79b1ab174e8985b89985a0110f9d97d2b0472e529c85e3a1d89</hash>
|
||||||
|
<size>1728825</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>8</targetSdkVersion>
|
||||||
|
<added>2013-08-19</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>VIBRATE,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<nativecode>armeabi</nativecode>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="org.torproject.android">
|
||||||
|
<id>org.torproject.android</id>
|
||||||
|
<added>2013-07-22</added>
|
||||||
|
<lastupdated>2016-12-06</lastupdated>
|
||||||
|
<name>Orbot</name>
|
||||||
|
<summary>Tor (anonymity) client</summary>
|
||||||
|
<icon>org.torproject.android.15208000.png</icon>
|
||||||
|
<desc><p>Tor is both software and an open network that helps you defend against network surveillance that threatens personal freedom and privacy, confidential business activities and relationships.</p><p>Orbot allows access to Tor by accessing a local SOCKS or HTTP proxy. On a rooted device, the proxying can be completely transparent i.e. the app that accesses the network need not be aware of the proxy's existence; you can choose which apps go via the proxy in the settings.</p><p>If you don't have root access, there are some apps that are designed to work closely with tor or allow proxied connections: <a href="fdroid.app:info.guardianproject.otr.app.im">ChatSecure</a>, <a href="fdroid.app:info.guardianproject.browser">Orweb</a> and <a href="fdroid.app:org.mariotaku.twidere">Twidere</a>. There is also a proxy configurator addon for <a href="fdroid.app:org.mozilla.firefox">org.mozilla.firefox</a> called <a href="https://github.com/guardianproject/ProxyMob/downloads">ProxyMob</a> (not yet available from the Mozilla addon site).</p><p>Requires root: No, but you will need to use apps that allow proxies if root is not granted.</p></desc>
|
||||||
|
<license>NewBSD</license>
|
||||||
|
<categories>Security,Internet,GuardianProject</categories>
|
||||||
|
<category>Security</category>
|
||||||
|
<web>http://www.torproject.org/docs/android.html.en</web>
|
||||||
|
<source>https://gitweb.torproject.org/orbot.git</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/orbot/issues</tracker>
|
||||||
|
<donate>https://www.torproject.org/donate/donate.html.en</donate>
|
||||||
|
<flattr>5649</flattr>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>15.2.0-RC-8-multi</version>
|
||||||
|
<versioncode>15208000</versioncode>
|
||||||
|
<apkname>Orbot-v15.2.0-RC-8-multi.apk</apkname>
|
||||||
|
<hash type="sha256">3758e1b6e6b9a3b7848b253d08d6c0b1b1b3223184da4bd2ba1aaff8cf676357</hash>
|
||||||
|
<size>12296544</size>
|
||||||
|
<sdkver>16</sdkver>
|
||||||
|
<targetSdkVersion>23</targetSdkVersion>
|
||||||
|
<added>2016-11-07</added>
|
||||||
|
<sig>8bd7e51b479aeba908ff46ada3305a29</sig>
|
||||||
|
<permissions>ACCESS_NETWORK_STATE,ACCESS_SUPERUSER,INTERNET,RECEIVE_BOOT_COMPLETED</permissions>
|
||||||
|
<nativecode>armeabi,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>15.2.0-RC-7-multi</version>
|
||||||
|
<versioncode>15207000</versioncode>
|
||||||
|
<apkname>Orbot-v15.2.0-RC-7-multi.apk</apkname>
|
||||||
|
<hash type="sha256">8dc3edf0a9799eb23b5e478e15547e38831b28cc3e88b049aa5f41b7b72e7bf9</hash>
|
||||||
|
<size>12457510</size>
|
||||||
|
<sdkver>16</sdkver>
|
||||||
|
<targetSdkVersion>23</targetSdkVersion>
|
||||||
|
<added>2016-11-04</added>
|
||||||
|
<sig>8bd7e51b479aeba908ff46ada3305a29</sig>
|
||||||
|
<permissions>ACCESS_NETWORK_STATE,ACCESS_SUPERUSER,INTERNET,RECEIVE_BOOT_COMPLETED</permissions>
|
||||||
|
<nativecode>arm64-v8a,armeabi,armeabi-v7a,x86</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>15.2.0-RC-5</version>
|
||||||
|
<versioncode>15205000</versioncode>
|
||||||
|
<apkname>Orbot-v15.2.0-RC-5-arm.apk</apkname>
|
||||||
|
<hash type="sha256">51c7e2b6a6de542e0d44f82d89ddf1d3216ec7a28297381ef15b12da2f3246f7</hash>
|
||||||
|
<size>7600548</size>
|
||||||
|
<sdkver>16</sdkver>
|
||||||
|
<targetSdkVersion>23</targetSdkVersion>
|
||||||
|
<added>2016-11-03</added>
|
||||||
|
<sig>8bd7e51b479aeba908ff46ada3305a29</sig>
|
||||||
|
<permissions>ACCESS_NETWORK_STATE,ACCESS_SUPERUSER,INTERNET,RECEIVE_BOOT_COMPLETED</permissions>
|
||||||
|
<nativecode>arm64-v8a,armeabi,armeabi-v7a</nativecode>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.orfox">
|
||||||
|
<id>info.guardianproject.orfox</id>
|
||||||
|
<added>2016-09-24</added>
|
||||||
|
<lastupdated>2016-12-06</lastupdated>
|
||||||
|
<name>Orfox</name>
|
||||||
|
<summary>Orfox: Tor Browser for Android</summary>
|
||||||
|
<icon>info.guardianproject.orfox.4.png</icon>
|
||||||
|
<desc><p>Orfox is the most privacy-enhancing web browser on Android, for visiting any website, even if it’s normally censored, monitored, or on the hidden web. It is a port of the desktop Tor Browser to the Android version of Firefox.</p><p>Orfox is a companion browser to <a href="fdroid.app:org.torproject.android">Orbot</a>, the port of Tor to Android. Orbot anonymizes internet traffic by routing it through many different stages and you must have that enabled first, though root isn't needed. Orfox disables certain other browser features that could be used to identify you.</p><p>Orfox replaces <a href="fdroid.app:info.guardianproject.browser">Orweb</a> as your private browser.</p></desc>
|
||||||
|
<license>MPL</license>
|
||||||
|
<categories>Internet,Security,GuardianProject</categories>
|
||||||
|
<category>Internet</category>
|
||||||
|
<web/>
|
||||||
|
<source>https://github.com/guardianproject/orfox</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/orfox/issues?set_filter=1</tracker>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>Fennec-45.5.1esr/TorBrowser-6.5-1/Orfox-1.2.1</version>
|
||||||
|
<versioncode>4</versioncode>
|
||||||
|
<apkname>Orfox-1.2.1-TorBrowser-6.5-Fennec45.5.1-build2.apk</apkname>
|
||||||
|
<hash type="sha256">d43032e79c7c31cabb194b8c1c4b14fbf73dd2cfda958ba415879ddf2f38ace2</hash>
|
||||||
|
<size>35273126</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>22</targetSdkVersion>
|
||||||
|
<added>2016-12-06</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>info.guardianproject.orfox.permissions.FORMHISTORY_PROVIDER,info.guardianproject.orfox.permissions.PASSWORD_PROVIDER,READ_EXTERNAL_STORAGE,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,DOWNLOAD_WITHOUT_NOTIFICATION,INTERNET,com.android.launcher.permission.UNINSTALL_SHORTCUT,info.guardianproject.orfox.permissions.BROWSER_PROVIDER,com.android.browser.permission.READ_HISTORY_BOOKMARKS,com.android.launcher.permission.INSTALL_SHORTCUT,ACCESS_NETWORK_STATE,WAKE_LOCK</permissions>
|
||||||
|
<nativecode>armeabi-v7a</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>Fennec-45.4.0esr/TorBrowser-6.5-1/Orfox-1.2</version>
|
||||||
|
<versioncode>3</versioncode>
|
||||||
|
<apkname>Orfox-1.2-TorBrowser-6.5-Fennec45.4.0.apk</apkname>
|
||||||
|
<hash type="sha256">9b5f6614b94a47ae561e8c974d42056ba6cb6da520766deda09aec3699aeff94</hash>
|
||||||
|
<size>35242066</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>22</targetSdkVersion>
|
||||||
|
<added>2016-09-24</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
<permissions>info.guardianproject.orfox.permissions.FORMHISTORY_PROVIDER,info.guardianproject.orfox.permissions.PASSWORD_PROVIDER,READ_EXTERNAL_STORAGE,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,DOWNLOAD_WITHOUT_NOTIFICATION,INTERNET,com.android.launcher.permission.UNINSTALL_SHORTCUT,info.guardianproject.orfox.permissions.BROWSER_PROVIDER,com.android.browser.permission.READ_HISTORY_BOOKMARKS,com.android.launcher.permission.INSTALL_SHORTCUT,ACCESS_NETWORK_STATE,WAKE_LOCK</permissions>
|
||||||
|
<nativecode>armeabi-v7a</nativecode>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.browser">
|
||||||
|
<id>info.guardianproject.browser</id>
|
||||||
|
<added>2012-10-22</added>
|
||||||
|
<lastupdated>2015-11-26</lastupdated>
|
||||||
|
<name>Orweb</name>
|
||||||
|
<summary>Privacy-enhanced browser</summary>
|
||||||
|
<icon>info.guardianproject.browser.7010.png</icon>
|
||||||
|
<desc><p>Orweb is a companion browser to <a href="fdroid.app:org.torproject.android">Orbot</a>, the port of Tor to Android.</p><p>Orbot anonymizes internet traffic by routing it through many different stages and you must have that enabled first, though root isn't needed. Orweb disables certain other browser features that could be used to identify you.</p></desc>
|
||||||
|
<license>GPL</license>
|
||||||
|
<categories>Internet,Security,GuardianProject</categories>
|
||||||
|
<category>Internet</category>
|
||||||
|
<web>https://guardianproject.info/apps/orweb</web>
|
||||||
|
<source>https://github.com/guardianproject/orweb</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/orweb/issues?set_filter=1</tracker>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>0.7.1</version>
|
||||||
|
<versioncode>7010</versioncode>
|
||||||
|
<apkname>Orweb-0.7.1.apk</apkname>
|
||||||
|
<hash type="sha256">949d65d6e8a1eadd0aa626bdc7c5a3e2b0dbe5a38dea1d725cce2a34ec84f0d4</hash>
|
||||||
|
<size>2424394</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>21</targetSdkVersion>
|
||||||
|
<added>2015-11-26</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>READ_EXTERNAL_STORAGE,INTERNET,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.7</version>
|
||||||
|
<versioncode>28</versioncode>
|
||||||
|
<apkname>Orweb-release-0.7.apk</apkname>
|
||||||
|
<hash type="sha256">763541f43f5dc136744b4361fe67d36f25cc036526d6c3e934287d72d1b411ab</hash>
|
||||||
|
<size>1244875</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>18</targetSdkVersion>
|
||||||
|
<added>2014-11-13</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>READ_EXTERNAL_STORAGE,INTERNET,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.6.1</version>
|
||||||
|
<versioncode>27</versioncode>
|
||||||
|
<apkname>Orweb-release-0.6.1.apk</apkname>
|
||||||
|
<hash type="sha256">103f4a98fa282923c07e445b2a383e946b6c15e10ed08005af3d0743249a0359</hash>
|
||||||
|
<size>931433</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>19</targetSdkVersion>
|
||||||
|
<added>2014-06-30</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>READ_EXTERNAL_STORAGE,INTERNET,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.pixelknot">
|
||||||
|
<id>info.guardianproject.pixelknot</id>
|
||||||
|
<added>2013-02-26</added>
|
||||||
|
<lastupdated>2016-12-06</lastupdated>
|
||||||
|
<name>PixelKnot</name>
|
||||||
|
<summary>Hide messages inside files</summary>
|
||||||
|
<icon>info.guardianproject.pixelknot.100.png</icon>
|
||||||
|
<desc><p>Image steganography app with old school F5 steganography</p></desc>
|
||||||
|
<license>GPLv3</license>
|
||||||
|
<categories>Office,GuardianProject</categories>
|
||||||
|
<category>Office</category>
|
||||||
|
<web>https://guardianproject.info</web>
|
||||||
|
<source>https://github.com/guardianproject/PixelKnot</source>
|
||||||
|
<tracker>https://github.com/guardianproject/PixelKnot/issues</tracker>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<versioncode>100</versioncode>
|
||||||
|
<apkname>PixelKnot-release-1.0.0.apk</apkname>
|
||||||
|
<hash type="sha256">f97557cf7ec81ade50c308c5552dc6dc827d0e02ce90f84b1df6b7477d9f5a39</hash>
|
||||||
|
<size>1983586</size>
|
||||||
|
<sdkver>17</sdkver>
|
||||||
|
<targetSdkVersion>25</targetSdkVersion>
|
||||||
|
<added>2016-12-06</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>VIBRATE,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<uses-permission maxSdkVersion="18" name="android.permission.VIBRATE"/>
|
||||||
|
<uses-permission maxSdkVersion="18" name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<nativecode>arm64-v8a,armeabi,armeabi-v7a,mips,mips64,x86,x86_64</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.3.3</version>
|
||||||
|
<versioncode>6</versioncode>
|
||||||
|
<apkname>PixelKnot-release-0.3.3.apk</apkname>
|
||||||
|
<hash type="sha256">6beede8519a9e87ba8edaa5a76f203cfefd5f39eb911e789031cc6e911714b89</hash>
|
||||||
|
<size>4751233</size>
|
||||||
|
<sdkver>14</sdkver>
|
||||||
|
<targetSdkVersion>17</targetSdkVersion>
|
||||||
|
<added>2015-06-26</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>CAMERA,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<nativecode>armeabi</nativecode>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.3.1</version>
|
||||||
|
<versioncode>4</versioncode>
|
||||||
|
<apkname>PixelKnot-release-0.3.1.apk</apkname>
|
||||||
|
<hash type="sha256">a3101fe8a2d47ab205cb00459fa62c639a6fac4538f6cd9d06eb48d2965c4d21</hash>
|
||||||
|
<size>3976822</size>
|
||||||
|
<sdkver>9</sdkver>
|
||||||
|
<targetSdkVersion>17</targetSdkVersion>
|
||||||
|
<added>2013-07-22</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
<permissions>CAMERA,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
|
||||||
|
<nativecode>armeabi</nativecode>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.ripple">
|
||||||
|
<id>info.guardianproject.ripple</id>
|
||||||
|
<added>2016-12-06</added>
|
||||||
|
<lastupdated>2016-12-06</lastupdated>
|
||||||
|
<name>Ripple</name>
|
||||||
|
<summary>Trigger apps to protect your privacy when in anxious or panic situations</summary>
|
||||||
|
<icon>info.guardianproject.ripple.75.png</icon>
|
||||||
|
<desc><p>Ripple is a "panic button" that can send it's trigger message to any app that is a "panic responder". Such apps can do things like lock, disguise themselves, delete private data, send an emergency message, and more. It is meant for situations where there is time to react, but where users need to be sure it is not mistakenly set off.</p><p>This is a BETA version of this app! We are working now to add support to as many other apps as possible. ChatSecure, Orweb, Umbrella, and Zom already have support, these apps are coming soon: Courier, PanicButton, OpenKeychain, Orfox, SMSSecure, and StoryMaker.</p><p>Here are two example scenarios:</p><ul><li> An organization gets regularly raided by the security forces, who search all of the computers and mobile devices on the premises. The organization usually has at least a minute or two of warning before a raid starts. They need a very reliable way to trigger wiping all of the data from the sensitive apps.</li></ul><ul><li> An aid worker has lots of sensitive data about people on their device. They regularly sync that data up with a secure, central database. Occasionally, the aid worker has to leave the country on very short notice. The border guards regularly download the entire contents of mobile devices of people crossing through. While waiting in line at the border, the aid worker sees the border guards seizing people's devices, and then remembers all the data on the device, so she unlocks her phone and hits the wipe trigger, which wipes all sensitive apps from the device. When the aid worker returns to the central office, the device is again synced up with the central database.</li></ul><p>This was started as part of the T2 Panic work, since sharing location is so often a part of panic apps. Follow our progress here:</p><p>★ <a href="https://guardianproject.info/tag/panic">https://guardianproject.info/tag/panic</a> ★ <a href="https://dev.guardianproject.info/projects/panic">https://dev.guardianproject.info/projects/panic</a> Don’t see your language? Join us and help translate the app: <a href="https://www.transifex.com/projects/p/rippleapp">https://www.transifex.com/projects/p/rippleapp</a></p><p>==Learn More==</p><p>★ ABOUT US: Guardian Project is a group of developers that make secure mobile apps and open-source code for a better tomorrow ★ OUR WEBSITE: <a href="https://GuardianProject.info">https://GuardianProject.info</a> ★ ON TWITTER: <a href="https://twitter.com/guardianproject">https://twitter.com/guardianproject</a> ★ FREE SOFTWARE: Ripple is free software. You can take a look at our source code, or contribute to help make Ripple even better: <a href="https://github.com/guardianproject/Ripple">https://github.com/guardianproject/Ripple</a> ★ MESSAGE US: Are we missing your favorite feature? Found an annoying bug? Please tell us! We’d love to hear from you. Send us an email: support@guardianproject.info or find us in our chat room <a href="https://guardianproject.info/contact">https://guardianproject.info/contact</a></p></desc>
|
||||||
|
<license>GPLv3</license>
|
||||||
|
<categories>Navigation,Security,GuardianProject</categories>
|
||||||
|
<category>Navigation</category>
|
||||||
|
<web>https://dev.guardianproject.info/projects/panic</web>
|
||||||
|
<source>https://github.com/guardianproject/ripple</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/panic/issues</tracker>
|
||||||
|
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>9999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>0.2</version>
|
||||||
|
<versioncode>75</versioncode>
|
||||||
|
<apkname>Ripple-0.2-release.apk</apkname>
|
||||||
|
<hash type="sha256">4b14b1b402f0197e1e6ffe2c11e052432fc8a52749f5f02d9cc67799658df239</hash>
|
||||||
|
<size>1669315</size>
|
||||||
|
<sdkver>10</sdkver>
|
||||||
|
<targetSdkVersion>23</targetSdkVersion>
|
||||||
|
<added>2016-12-06</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.1</version>
|
||||||
|
<versioncode>2</versioncode>
|
||||||
|
<apkname>Ripple-0.1.apk</apkname>
|
||||||
|
<hash type="sha256">9fd24cbb3552123e6ee119f912f1646dd21cd7a683734a8d502d8b44854a284b</hash>
|
||||||
|
<size>1670285</size>
|
||||||
|
<sdkver>10</sdkver>
|
||||||
|
<targetSdkVersion>23</targetSdkVersion>
|
||||||
|
<added>2016-12-06</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
</package>
|
||||||
|
<package>
|
||||||
|
<version>0.0</version>
|
||||||
|
<versioncode>1</versioncode>
|
||||||
|
<apkname>Ripple-0.0.apk</apkname>
|
||||||
|
<hash type="sha256">025894a5f3a39a288ee60bb6c9cc2c559d395f22fed020d1086308ba12df85a3</hash>
|
||||||
|
<size>1664407</size>
|
||||||
|
<sdkver>10</sdkver>
|
||||||
|
<targetSdkVersion>23</targetSdkVersion>
|
||||||
|
<added>2016-12-06</added>
|
||||||
|
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
<application id="info.guardianproject.chatsecure.emoji.core">
|
||||||
|
<id>info.guardianproject.chatsecure.emoji.core</id>
|
||||||
|
<added>2013-12-02</added>
|
||||||
|
<lastupdated>2013-12-02</lastupdated>
|
||||||
|
<name>StickerPack</name>
|
||||||
|
<summary>ChatSecure Open Emoji Plugin</summary>
|
||||||
|
<icon>info.guardianproject.chatsecure.emoji.core.1.png</icon>
|
||||||
|
<desc><p>Plugin for <a href="fdroid.app:info.guardianproject.otr.app.im">ChatSecure</a> to support for core emoji input and display. Based on "Phantom Open Emoji" project.</p></desc>
|
||||||
|
<license>SIL Open Font License, MIT License and the CC 3.0 License [CC-By with attribution requirement waived]</license>
|
||||||
|
<categories>Multimedia,Security,GuardianProject</categories>
|
||||||
|
<category>Multimedia</category>
|
||||||
|
<web>https://guardianproject.info/apps/chatsecure</web>
|
||||||
|
<source>https://github.com/guardianproject/ChatSecureVoicePlugin</source>
|
||||||
|
<tracker>https://dev.guardianproject.info/projects/chatsecure/issues</tracker>
|
||||||
|
<marketversion/>
|
||||||
|
<marketvercode>999999999</marketvercode>
|
||||||
|
<package>
|
||||||
|
<version>1.0</version>
|
||||||
|
<versioncode>1</versioncode>
|
||||||
|
<apkname>ChatSecurePluginOpenEmoji-release-v1.apk</apkname>
|
||||||
|
<hash type="sha256">131c1ebaf795c3f053701285699f0b7e517de1c7fdba56e247b1ec31766b2808</hash>
|
||||||
|
<size>1814271</size>
|
||||||
|
<sdkver>8</sdkver>
|
||||||
|
<targetSdkVersion>17</targetSdkVersion>
|
||||||
|
<added>2013-12-02</added>
|
||||||
|
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
|
||||||
|
</package>
|
||||||
|
</application>
|
||||||
|
</fdroid>
|
BIN
app/src/test/resources/testy.at.or.at_index-v1.jar
Normal file
BIN
app/src/test/resources/testy.at.or.at_index-v1.jar
Normal file
Binary file not shown.
BIN
app/src/test/resources/testy.at.or.at_no-.RSA_index-v1.jar
Normal file
BIN
app/src/test/resources/testy.at.or.at_no-.RSA_index-v1.jar
Normal file
Binary file not shown.
BIN
app/src/test/resources/testy.at.or.at_no-.SF_index-v1.jar
Normal file
BIN
app/src/test/resources/testy.at.or.at_no-.SF_index-v1.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/src/test/resources/testy.at.or.at_no-signature_index-v1.jar
Normal file
BIN
app/src/test/resources/testy.at.or.at_no-signature_index-v1.jar
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user