From f8daccffc061e2f05f6605913c19d4aa807eaddb Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 9 Nov 2020 15:37:31 +0100 Subject: initial auto-update implementation: introducing fatweb flavor, pgpverify go library and bitmask core library, basic update mechanism --- .../appUpdate/UpdateDownloadManager.java | 231 +++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/UpdateDownloadManager.java (limited to 'app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/UpdateDownloadManager.java') diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/UpdateDownloadManager.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/UpdateDownloadManager.java new file mode 100644 index 00000000..698a0d17 --- /dev/null +++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/UpdateDownloadManager.java @@ -0,0 +1,231 @@ +/** + * Copyright (c) 2020 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient.appUpdate; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.util.Log; + +import androidx.annotation.NonNull; + +import java.io.File; + +import okhttp3.OkHttpClient; +import pgpverify.Logger; +import pgpverify.PgpVerifier; +import se.leap.bitmaskclient.BuildConfig; +import se.leap.bitmaskclient.OkHttpClientGenerator; +import se.leap.bitmaskclient.R; + +import static android.text.TextUtils.isEmpty; +import static se.leap.bitmaskclient.Constants.BROADCAST_DOWNLOAD_SERVICE_EVENT; +import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; +import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.ProviderAPI.RECEIVER_KEY; +import static se.leap.bitmaskclient.appUpdate.DownloadService.DOWNLOAD_FAILED; +import static se.leap.bitmaskclient.appUpdate.DownloadService.DOWNLOAD_PROGRESS; +import static se.leap.bitmaskclient.appUpdate.DownloadService.NO_NEW_VERISON; +import static se.leap.bitmaskclient.appUpdate.DownloadService.NO_PUB_KEY; +import static se.leap.bitmaskclient.appUpdate.DownloadService.PROGRESS_VALUE; +import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_DOWNLOADED; +import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_DOWNLOAD_FAILED; +import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_FOUND; +import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_NOT_FOUND; +import static se.leap.bitmaskclient.appUpdate.DownloadService.VERIFICATION_ERROR; +import static se.leap.bitmaskclient.appUpdate.DownloadServiceCommand.CHECK_VERSION_FILE; +import static se.leap.bitmaskclient.appUpdate.DownloadServiceCommand.DOWNLOAD_UPDATE; +import static se.leap.bitmaskclient.utils.FileHelper.readPublicKey; + +public class UpdateDownloadManager implements Logger, DownloadConnector.DownloadProgress { + + + private static final String TAG = UpdateDownloadManager.class.getSimpleName(); + + public interface DownloadServiceCallback { + void broadcastEvent(Intent intent); + } + + private Context context; + + private PgpVerifier pgpVerifier; + private DownloadServiceCallback serviceCallback; + OkHttpClientGenerator clientGenerator; + + + public UpdateDownloadManager(Context context, OkHttpClientGenerator clientGenerator, DownloadServiceCallback callback) { + this.context = context; + this.clientGenerator = clientGenerator; + pgpVerifier = new PgpVerifier(); + pgpVerifier.setLogger(this); + serviceCallback = callback; + } + + //pgpverify Logger interface + @Override + public void log(String s) { + + } + + @Override + public void onUpdate(int progress) { + Bundle resultData = new Bundle(); + resultData.putInt(PROGRESS_VALUE, progress); + broadcastEvent(DOWNLOAD_PROGRESS, resultData); + } + + public void handleIntent(Intent command) { + ResultReceiver receiver = null; + if (command.getParcelableExtra(RECEIVER_KEY) != null) { + receiver = command.getParcelableExtra(RECEIVER_KEY); + } + String action = command.getAction(); + + Bundle result = new Bundle(); + switch (action) { + case CHECK_VERSION_FILE: + result = checkVersionFile(result); + if (result.getBoolean(BROADCAST_RESULT_KEY)) { + sendToReceiverOrBroadcast(receiver, UPDATE_FOUND, result); + } else { + sendToReceiverOrBroadcast(receiver, UPDATE_NOT_FOUND, result); + } + break; + case DOWNLOAD_UPDATE: + result = downloadUpdate(result); + if (result.getBoolean(BROADCAST_RESULT_KEY)) { + sendToReceiverOrBroadcast(receiver, UPDATE_DOWNLOADED, result); + } else { + sendToReceiverOrBroadcast(receiver, UPDATE_DOWNLOAD_FAILED, result); + } + break; + + } + } + + public static File getUpdateFile(Context context) { + return new File(context.getExternalFilesDir(null) + "/" + context.getString(R.string.app_name) + "_update.apk"); + } + + private Bundle downloadUpdate(Bundle task) { + + String publicKey = readPublicKey(context); + if (isEmpty(publicKey)) { + task.putBoolean(BROADCAST_RESULT_KEY, false); + task.putBoolean(NO_PUB_KEY, true); + return task; + } + + OkHttpClient client = clientGenerator.init(); + String signature = DownloadConnector.requestTextFileFromServer(BuildConfig.signature_url, client); + if (signature == null) { + task.putBoolean(BROADCAST_RESULT_KEY, false); + task.putBoolean(DOWNLOAD_FAILED, true); + return task; + } + + File destinationFile = getUpdateFile(context); + if (destinationFile.exists()) { + destinationFile.delete(); + } + + destinationFile = DownloadConnector.requestFileFromServer(BuildConfig.update_apk_url, client, destinationFile, this); + + if (destinationFile == null) { + task.putBoolean(BROADCAST_RESULT_KEY, false); + task.putBoolean(DOWNLOAD_FAILED, true); + return task; + } + + boolean successfulVerified = pgpVerifier.verify(signature, publicKey, destinationFile.getAbsolutePath()); + if (!successfulVerified) { + destinationFile.delete(); + task.putBoolean(BROADCAST_RESULT_KEY, false); + task.putBoolean(VERIFICATION_ERROR, true); + return task; + } + + task.putBoolean(BROADCAST_RESULT_KEY, true); + return task; + } + + private static void clearPreviousDownloads(@NonNull Context context, String destinationFile) { + File directory = context.getExternalFilesDir(null); + + if (directory == null) { + Log.w(TAG, "Failed to read external files directory."); + return; + } + + for (File file : directory.listFiles()) { + if (file.getName().equals(destinationFile)) { + if (file.delete()) { + Log.d(TAG, "Deleted " + file.getName()); + } + } + } + } + + private Bundle checkVersionFile(Bundle task) { + OkHttpClient client = clientGenerator.init(); + String versionString = DownloadConnector.requestTextFileFromServer(BuildConfig.version_file_url, client); + + if (versionString != null) { + versionString = versionString.replace("\n", "").trim(); + } + + int version = -1; + try { + version = Integer.valueOf(versionString); + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "could not parse version code: " + versionString); + } + + if (version == -1) { + task.putBoolean(BROADCAST_RESULT_KEY, false); + task.putBoolean(DOWNLOAD_FAILED, true); + } else if (BuildConfig.VERSION_CODE >= version) { + task.putBoolean(BROADCAST_RESULT_KEY, false); + task.putBoolean(NO_NEW_VERISON, true); + } else { + task.putBoolean(BROADCAST_RESULT_KEY, true); + } + return task; + } + + private void sendToReceiverOrBroadcast(ResultReceiver receiver, int resultCode, Bundle resultData) { + if (resultData == null || resultData == Bundle.EMPTY) { + resultData = new Bundle(); + } + if (receiver != null) { + receiver.send(resultCode, resultData); + } else { + broadcastEvent(resultCode, resultData); + } + } + + private void broadcastEvent(int resultCode , Bundle resultData) { + Intent intentUpdate = new Intent(BROADCAST_DOWNLOAD_SERVICE_EVENT); + intentUpdate.addCategory(Intent.CATEGORY_DEFAULT); + intentUpdate.putExtra(BROADCAST_RESULT_CODE, resultCode); + intentUpdate.putExtra(BROADCAST_RESULT_KEY, resultData); + serviceCallback.broadcastEvent(intentUpdate); + } + +} -- cgit v1.2.3