Signature Verifier
This commit is contained in:
parent
0bab2e6c11
commit
609309ab47
5 changed files with 211 additions and 0 deletions
0
app/src/main/assets/revoked_certs.txt
Normal file
0
app/src/main/assets/revoked_certs.txt
Normal file
|
@ -49,6 +49,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.receivers.AckAlarmRec
|
||||||
import info.nightscout.androidaps.plugins.general.nsclient.receivers.DBAccessReceiver;
|
import info.nightscout.androidaps.plugins.general.nsclient.receivers.DBAccessReceiver;
|
||||||
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin;
|
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin;
|
||||||
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin;
|
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin;
|
||||||
|
import info.nightscout.androidaps.plugins.general.signatureVerifier.SignatureVerifier;
|
||||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin;
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin;
|
||||||
import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerPlugin;
|
import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerPlugin;
|
||||||
import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
|
import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
|
||||||
|
@ -187,6 +188,7 @@ public class MainApp extends Application {
|
||||||
if (Config.SAFETY) pluginsList.add(SafetyPlugin.getPlugin());
|
if (Config.SAFETY) pluginsList.add(SafetyPlugin.getPlugin());
|
||||||
if (Config.SAFETY) pluginsList.add(VersionCheckerPlugin.INSTANCE);
|
if (Config.SAFETY) pluginsList.add(VersionCheckerPlugin.INSTANCE);
|
||||||
if (Config.SAFETY) pluginsList.add(StorageConstraintPlugin.getPlugin());
|
if (Config.SAFETY) pluginsList.add(StorageConstraintPlugin.getPlugin());
|
||||||
|
if (Config.SAFETY) pluginsList.add(SignatureVerifier.getPlugin());
|
||||||
if (Config.APS) pluginsList.add(ObjectivesPlugin.getPlugin());
|
if (Config.APS) pluginsList.add(ObjectivesPlugin.getPlugin());
|
||||||
pluginsList.add(SourceXdripPlugin.getPlugin());
|
pluginsList.add(SourceXdripPlugin.getPlugin());
|
||||||
pluginsList.add(SourceNSClientPlugin.getPlugin());
|
pluginsList.add(SourceNSClientPlugin.getPlugin());
|
||||||
|
|
|
@ -75,6 +75,7 @@ public class Notification {
|
||||||
public static final int DST_IN_24H = 50;
|
public static final int DST_IN_24H = 50;
|
||||||
public static final int DISKFULL = 51;
|
public static final int DISKFULL = 51;
|
||||||
public static final int OLDVERSION = 52;
|
public static final int OLDVERSION = 52;
|
||||||
|
public static final int INVALID_VERSION = 53;
|
||||||
|
|
||||||
|
|
||||||
public int id;
|
public int id;
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
package info.nightscout.androidaps.plugins.general.signatureVerifier;
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.Signature;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import com.j256.ormlite.stmt.query.In;
|
||||||
|
|
||||||
|
import org.mozilla.javascript.tools.jsc.Main;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.interfaces.Constraint;
|
||||||
|
import info.nightscout.androidaps.interfaces.ConstraintsInterface;
|
||||||
|
import info.nightscout.androidaps.interfaces.PluginBase;
|
||||||
|
import info.nightscout.androidaps.interfaces.PluginDescription;
|
||||||
|
import info.nightscout.androidaps.interfaces.PluginType;
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification;
|
||||||
|
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For legal versions, AndroidAPS is meant to build by the user. In case someone decides to publish a ready-to-use APK nonetheless, we can still disable it.
|
||||||
|
*/
|
||||||
|
public class SignatureVerifier extends PluginBase implements ConstraintsInterface {
|
||||||
|
|
||||||
|
private static final String REVOKED_CERTS_URL = "https://raw.githubusercontent.com/MilosKozak/AndroidAPS/master/app/src/main/assets/revoked_certs.txt";
|
||||||
|
private static final long UPDATE_INTERVAL = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
private static SignatureVerifier plugin = new SignatureVerifier();
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(L.CORE);
|
||||||
|
private final Object $lock = new Object[0];
|
||||||
|
private File revokedCertsFile;
|
||||||
|
private List<byte[]> revokedCerts;
|
||||||
|
|
||||||
|
public static SignatureVerifier getPlugin() {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SignatureVerifier() {
|
||||||
|
super(new PluginDescription()
|
||||||
|
.mainType(PluginType.CONSTRAINTS)
|
||||||
|
.neverVisible(true)
|
||||||
|
.alwaysEnabled(true)
|
||||||
|
.showInList(false)
|
||||||
|
.pluginName(R.string.signature_verifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
revokedCertsFile = new File(MainApp.instance().getFilesDir(), "revoked_certs.txt");
|
||||||
|
new Thread(() -> {
|
||||||
|
loadLocalRevokedCerts();
|
||||||
|
if (shouldDownloadCerts()) {
|
||||||
|
try {
|
||||||
|
downloadAndSaveRevokedCerts();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Could not download revoked certs", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasIllegalSignature()) showNotification();
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Constraint<Boolean> isLoopInvocationAllowed(Constraint<Boolean> value) {
|
||||||
|
if (hasIllegalSignature()) {
|
||||||
|
showNotification();
|
||||||
|
value.set(false);
|
||||||
|
}
|
||||||
|
if (shouldDownloadCerts()) {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
downloadAndSaveRevokedCerts();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Could not download revoked certs", e);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showNotification() {
|
||||||
|
Notification notification = new Notification(Notification.INVALID_VERSION, MainApp.gs(R.string.running_invalid_version), Notification.URGENT);
|
||||||
|
MainApp.bus().post(new EventNewNotification(notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasIllegalSignature() {
|
||||||
|
try {
|
||||||
|
synchronized ($lock) {
|
||||||
|
if (revokedCerts == null) return false;
|
||||||
|
Signature[] signatures = MainApp.instance().getPackageManager().getPackageInfo(MainApp.instance().getPackageName(), PackageManager.GET_SIGNATURES).signatures;
|
||||||
|
for (Signature signature : signatures) {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA256");
|
||||||
|
byte[] fingerprint = digest.digest(signature.toByteArray());
|
||||||
|
for (byte[] cert : revokedCerts) {
|
||||||
|
if (Arrays.equals(cert, fingerprint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
log.error("Error in SignatureVerifier", e);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
log.error("Error in SignatureVerifier", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldDownloadCerts() {
|
||||||
|
return System.currentTimeMillis() - SP.getLong(R.string.key_last_revoked_certs_check, 0L) >= UPDATE_INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadAndSaveRevokedCerts() throws IOException {
|
||||||
|
String download = downloadRevokedCerts();
|
||||||
|
saveRevokedCerts(download);
|
||||||
|
SP.putLong(R.string.key_last_revoked_certs_check, System.currentTimeMillis());
|
||||||
|
synchronized ($lock) {
|
||||||
|
revokedCerts = parseRevokedCertsFile(download);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadLocalRevokedCerts() {
|
||||||
|
try {
|
||||||
|
String revokedCerts = readCachedDownloadedRevokedCerts();
|
||||||
|
if (revokedCerts == null) revokedCerts = readRevokedCertsInAssets();
|
||||||
|
synchronized ($lock) {
|
||||||
|
this.revokedCerts = parseRevokedCertsFile(revokedCerts);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error in SignatureVerifier", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveRevokedCerts(String revokedCerts) throws IOException {
|
||||||
|
OutputStream outputStream = new FileOutputStream(revokedCertsFile);
|
||||||
|
outputStream.write(revokedCerts.getBytes(StandardCharsets.UTF_8));
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String downloadRevokedCerts() throws IOException {
|
||||||
|
URLConnection connection = new URL(REVOKED_CERTS_URL).openConnection();
|
||||||
|
return readInputStream(connection.getInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readInputStream(InputStream inputStream) throws IOException {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int read;
|
||||||
|
while ((read = inputStream.read(buffer)) != -1) {
|
||||||
|
baos.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
baos.flush();
|
||||||
|
inputStream.close();
|
||||||
|
return new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readRevokedCertsInAssets() throws IOException {
|
||||||
|
InputStream inputStream = MainApp.instance().getAssets().open("revoked_certs.txt");
|
||||||
|
return readInputStream(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readCachedDownloadedRevokedCerts() throws IOException {
|
||||||
|
if (!revokedCertsFile.exists()) return null;
|
||||||
|
return readInputStream(new FileInputStream(revokedCertsFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<byte[]> parseRevokedCertsFile(String file) {
|
||||||
|
List<byte[]> revokedCerts = new ArrayList<>();
|
||||||
|
for (String line : file.split("\n")) {
|
||||||
|
if (line.startsWith("#")) continue;
|
||||||
|
revokedCerts.add(Hex.decode(line.replace(" ", "")));
|
||||||
|
}
|
||||||
|
return revokedCerts;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1330,6 +1330,9 @@
|
||||||
<string name="key_last_time_this_version_detected" translatable="false">last_time_this_version_detected</string>
|
<string name="key_last_time_this_version_detected" translatable="false">last_time_this_version_detected</string>
|
||||||
<string name="key_last_versionchecker_warning" translatable="false">last_versionchecker_waring</string>
|
<string name="key_last_versionchecker_warning" translatable="false">last_versionchecker_waring</string>
|
||||||
<string name="key_last_versionchecker_plugin_warning" translatable="false">last_versionchecker_plugin_waring</string>
|
<string name="key_last_versionchecker_plugin_warning" translatable="false">last_versionchecker_plugin_waring</string>
|
||||||
|
<string name="key_last_revoked_certs_check" translatable="false">last_revoked_certs_check</string>
|
||||||
|
<string name="signature_verifier">Signature verifier</string>
|
||||||
|
<string name="running_invalid_version">We have detected that you are running an invalid version. Loop disabled!</string>
|
||||||
|
|
||||||
<string name="old_version">old version</string>
|
<string name="old_version">old version</string>
|
||||||
<string name="very_old_version">very old version</string>
|
<string name="very_old_version">very old version</string>
|
||||||
|
|
Loading…
Reference in a new issue