Skip to content

Commit

Permalink
feat: add private key system to have end to end encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
riderx committed Nov 23, 2022
1 parent a51733e commit 5f3bd66
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 95 deletions.
1 change: 1 addition & 0 deletions CapgoCapacitorUpdater.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ Pod::Spec.new do |s|
s.dependency 'SSZipArchive'
s.dependency 'Alamofire'
s.dependency 'Version'
s.dependency 'SwiftyRSA'
s.swift_version = '5.1'
end
239 changes: 145 additions & 94 deletions android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ee.forgr.capacitor_updater;

import static ee.forgr.capacitor_updater.RSACipher.stringToPrivateKey;

import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
Expand All @@ -9,6 +11,7 @@
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.getcapacitor.JSObject;
import com.getcapacitor.android.BuildConfig;
import com.getcapacitor.plugin.WebView;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
Expand All @@ -21,6 +24,9 @@
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Date;
Expand All @@ -32,6 +38,10 @@
import org.json.JSONException;
import org.json.JSONObject;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

interface Callback {
void callback(JSONObject jsonObject);
}
Expand Down Expand Up @@ -68,6 +78,7 @@ public class CapacitorUpdater {
public String statsUrl = "";
public String channelUrl = "";
public String appId = "";
public String privateKey = "";
public String deviceID = "";

private final FilenameFilter filter = new FilenameFilter() {
Expand All @@ -84,23 +95,23 @@ private boolean isProd() {

private boolean isEmulator() {
return (
(Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) ||
Build.FINGERPRINT.startsWith("generic") ||
Build.FINGERPRINT.startsWith("unknown") ||
Build.HARDWARE.contains("goldfish") ||
Build.HARDWARE.contains("ranchu") ||
Build.MODEL.contains("google_sdk") ||
Build.MODEL.contains("Emulator") ||
Build.MODEL.contains("Android SDK built for x86") ||
Build.MANUFACTURER.contains("Genymotion") ||
Build.PRODUCT.contains("sdk_google") ||
Build.PRODUCT.contains("google_sdk") ||
Build.PRODUCT.contains("sdk") ||
Build.PRODUCT.contains("sdk_x86") ||
Build.PRODUCT.contains("sdk_gphone64_arm64") ||
Build.PRODUCT.contains("vbox86p") ||
Build.PRODUCT.contains("emulator") ||
Build.PRODUCT.contains("simulator")
(Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) ||
Build.FINGERPRINT.startsWith("generic") ||
Build.FINGERPRINT.startsWith("unknown") ||
Build.HARDWARE.contains("goldfish") ||
Build.HARDWARE.contains("ranchu") ||
Build.MODEL.contains("google_sdk") ||
Build.MODEL.contains("Emulator") ||
Build.MODEL.contains("Android SDK built for x86") ||
Build.MANUFACTURER.contains("Genymotion") ||
Build.PRODUCT.contains("sdk_google") ||
Build.PRODUCT.contains("google_sdk") ||
Build.PRODUCT.contains("sdk") ||
Build.PRODUCT.contains("sdk_x86") ||
Build.PRODUCT.contains("sdk_gphone64_arm64") ||
Build.PRODUCT.contains("vbox86p") ||
Build.PRODUCT.contains("emulator") ||
Build.PRODUCT.contains("simulator")
);
}

Expand Down Expand Up @@ -139,7 +150,7 @@ private File unzip(final String id, final File zipFile, final String dest) throw

if (!canonicalPath.startsWith(canonicalDir)) {
throw new FileNotFoundException(
"SecurityException, Failed to ensure directory is the start path : " + canonicalDir + " of " + canonicalPath
"SecurityException, Failed to ensure directory is the start path : " + canonicalDir + " of " + canonicalPath
);
}

Expand Down Expand Up @@ -254,6 +265,45 @@ private String getChecksum(File file) throws IOException {
return enc.toLowerCase();
}

private void decodeFile(File file) throws IOException {
if(this.privateKey.equal("")) {
return;
}
try {
PrivateKey pKey = RSACipher.stringToPrivateKey(this.privateKey);
FileInputStream fis = new FileInputStream(file.getAbsolutePath());
byte[] buffer = new byte[10];
StringBuilder sb = new StringBuilder();
while (fis.read(buffer) != -1) {
sb.append(new String(buffer));
buffer = new byte[10];
}
fis.close();
String content = sb.toString();
String decrypted = RSACipher.decryptRSA(content, pKey);
// write the decrypted string to the file
FileOutputStream fos = new FileOutputStream(file
.getAbsolutePath());
fos.write(decrypted.getBytes());
fos.close();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
throw new IOException("NoSuchPaddingException");
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
throw new IOException("IllegalBlockSizeException");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new IOException("NoSuchAlgorithmException");
} catch (BadPaddingException e) {
e.printStackTrace();
throw new IOException("BadPaddingException");
} catch (InvalidKeyException e) {
e.printStackTrace();
throw new IOException("InvalidKeyException");
}
}

public BundleInfo download(final String url, final String version) throws IOException {
final String id = this.randomString(10);
this.saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
Expand All @@ -262,6 +312,7 @@ public BundleInfo download(final String url, final String version) throws IOExce
this.notifyDownload(id, 5);
final File downloaded = this.downloadFile(id, url, this.randomString(10));
final String checksum = this.getChecksum(downloaded);
this.decodeFile(downloaded);
this.notifyDownload(id, 71);
final File unzipped = this.unzip(id, downloaded, this.randomString(10));
downloaded.delete();
Expand Down Expand Up @@ -408,21 +459,21 @@ public void getLatest(final String updateUrl, final Callback callback) {
Log.i(CapacitorUpdater.TAG, "Auto-update parameters: " + json);
// Building a request
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.POST,
updateUrl,
json,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
callback.callback(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error getting Latest", error);
Request.Method.POST,
updateUrl,
json,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
callback.callback(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error getting Latest" + error.toString());
}
}
}
);
this.requestQueue.add(request);
} catch (JSONException ex) {
Expand All @@ -442,34 +493,34 @@ public void setChannel(final String channel, final CallbackChannel callback) {

// Building a request
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.POST,
channelUrl,
json,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject res) {
final JSObject ret = new JSObject();
Iterator<String> keys = res.keys();
while (keys.hasNext()) {
String key = keys.next();
if (res.has(key)) {
try {
ret.put(key, res.get(key));
} catch (JSONException e) {
e.printStackTrace();
Request.Method.POST,
channelUrl,
json,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject res) {
final JSObject ret = new JSObject();
Iterator<String> keys = res.keys();
while (keys.hasNext()) {
String key = keys.next();
if (res.has(key)) {
try {
ret.put(key, res.get(key));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
Log.i(TAG, "Channel set to \"" + channel);
callback.callback(ret);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error set channel: " + error.toString());
}
Log.i(TAG, "Channel set to \"" + channel);
callback.callback(ret);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error set channel: " + error);
}
}
);
this.requestQueue.add(request);
} catch (JSONException ex) {
Expand All @@ -488,34 +539,34 @@ public void getChannel(final CallbackChannel callback) {

// Building a request
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.PUT,
channelUrl,
json,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject res) {
final JSObject ret = new JSObject();
Iterator<String> keys = res.keys();
while (keys.hasNext()) {
String key = keys.next();
if (res.has(key)) {
try {
ret.put(key, res.get(key));
} catch (JSONException e) {
e.printStackTrace();
Request.Method.PUT,
channelUrl,
json,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject res) {
final JSObject ret = new JSObject();
Iterator<String> keys = res.keys();
while (keys.hasNext()) {
String key = keys.next();
if (res.has(key)) {
try {
ret.put(key, res.get(key));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
Log.i(TAG, "Channel get to \"" + ret);
callback.callback(ret);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error get channel: " + error.toString());
}
Log.i(TAG, "Channel get to \"" + ret);
callback.callback(ret);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error get channel: " + error);
}
}
);
this.requestQueue.add(request);
} catch (JSONException ex) {
Expand All @@ -535,21 +586,21 @@ public void sendStats(final String action, final String versionName) {

// Building a request
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.POST,
statsUrl,
json,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.i(TAG, "Stats send for \"" + action + "\", version " + versionName);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error sending stats: " + error);
Request.Method.POST,
statsUrl,
json,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.i(TAG, "Stats send for \"" + action + "\", version " + versionName);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error sending stats: " + error.toString());
}
}
}
);
this.requestQueue.add(request);
} catch (JSONException ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public void notifyDownload(final String id, final int percent) {

final CapConfig config = CapConfig.loadDefault(this.getActivity());
this.implementation.appId = config.getString("appId", "");
this.implementation.privateKey = this.getConfig().getString("privateKey", "");
this.implementation.statsUrl = this.getConfig().getString("statsUrl", statsUrlDefault);
this.implementation.channelUrl = this.getConfig().getString("channelUrl", channelUrlDefault);
this.implementation.documentsDir = this.getContext().getFilesDir();
Expand Down Expand Up @@ -771,6 +772,10 @@ public void run() {
CapacitorUpdater.TAG,
"Error checksum " + next.getChecksum() + " " + checksum
);
CapacitorUpdaterPlugin.this.implementation.sendStats(
"checksum_fail",
current.getVersionName()
);
final Boolean res =
CapacitorUpdaterPlugin.this.implementation.delete(next.getId());
if (res) {
Expand Down

0 comments on commit 5f3bd66

Please sign in to comment.