Signature Verifier

This commit is contained in:
Tebbe Ubben 2019-05-01 16:08:47 +02:00
parent 0bab2e6c11
commit 609309ab47
5 changed files with 211 additions and 0 deletions

View file

View 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());

View file

@ -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;

View file

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

View file

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