Signature Verifier
This commit is contained in:
parent
0bab2e6c11
commit
609309ab47
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.overview.OverviewPlugin;
|
||||
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.versionChecker.VersionCheckerPlugin;
|
||||
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(VersionCheckerPlugin.INSTANCE);
|
||||
if (Config.SAFETY) pluginsList.add(StorageConstraintPlugin.getPlugin());
|
||||
if (Config.SAFETY) pluginsList.add(SignatureVerifier.getPlugin());
|
||||
if (Config.APS) pluginsList.add(ObjectivesPlugin.getPlugin());
|
||||
pluginsList.add(SourceXdripPlugin.getPlugin());
|
||||
pluginsList.add(SourceNSClientPlugin.getPlugin());
|
||||
|
|
|
@ -75,6 +75,7 @@ public class Notification {
|
|||
public static final int DST_IN_24H = 50;
|
||||
public static final int DISKFULL = 51;
|
||||
public static final int OLDVERSION = 52;
|
||||
public static final int INVALID_VERSION = 53;
|
||||
|
||||
|
||||
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_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_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="very_old_version">very old version</string>
|
||||
|
|
Loading…
Reference in a new issue