package com.duosecurity.x_ray; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; import android.util.Log; import com.duosecurity.duokit.crypto.Crypto; import org.thoughtcrime.ssl.pinning.util.PinningHelper; 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.net.MalformedURLException; import java.net.SocketTimeoutException; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLHandshakeException; public class XrayUpdateTask extends AsyncTask<Void, Void, XrayUpdater.UpdateResult> { private final static String TAG = XrayUpdateTask.class.getSimpleName(); protected Context context = null; private Crypto crypto = null; private TaskListener callback = null; public interface TaskListener { void onFinished(XrayUpdater.UpdateResult updateResult); } public XrayUpdateTask(Context ctx, TaskListener taskListener) { context = ctx; crypto = Crypto.getInstance(); callback = taskListener; } private String getFileChecksum (String fullPath) { String result = null; InputStream inputStream = null; try { MessageDigest md = MessageDigest.getInstance(XrayUpdater.CHECKSUM_ALGORITHM); inputStream = new FileInputStream(fullPath); byte[] buffer = new byte[4096]; int nRead; while ((nRead = inputStream.read(buffer, 0, buffer.length)) > 0) { md.update(buffer, 0, nRead); } result = crypto.hex(md.digest()); } catch (NoSuchAlgorithmException e) { Log.d(TAG, "Unable to get digest instance when calculating apk checksum"); } catch (FileNotFoundException e) { Log.d(TAG, "Unable to find apk file when calculating apk checksum"); } catch (IOException e) { Log.d(TAG, "Unable to read apk file when calculating apk checksum"); } catch (Exception e) { Log.d(TAG, "Found error when calculating apk checksum: " + e.toString()); } finally { Log.d(TAG, "Cleaning up after calculating apk checksum..."); if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { Log.d(TAG, "Found IO exception when trying to close inputstream: " + e.toString()); } } } return result; } protected void promptInstall (String filePath) { Intent installIntent = new Intent(Intent.ACTION_VIEW) .setDataAndType(Uri.parse("file://" + filePath), XrayUpdater.FILE_TYPE) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(installIntent); } protected XrayUpdater.UpdateResult doInBackground (Void... v) { HttpsURLConnection urlConnection = null; InputStream inputStream = null; XrayUpdater.UpdateResult result = XrayUpdater.UpdateResult.DOWNLOAD_SUCCESS; Log.d(TAG, "Attempting to fetch apk update..."); try { // make sure necessary info has been set String apkName = XrayUpdater.getSharedPreference("apkName"); String actualChecksum = XrayUpdater.getSharedPreference("apkChecksum"); if ("".equals(apkName) || "".equals(actualChecksum)) { throw new Exception("Missing apkName or apkChecksum"); } // issue GET request to download new apk URL url = new URL(XrayUpdater.DOWNLOAD_URL); urlConnection = PinningHelper.getPinnedHttpsURLConnection( context, XrayUpdater.CERT_PINS, url ); urlConnection.setConnectTimeout(XrayUpdater.CONNECTION_TIMEOUT); urlConnection.setReadTimeout(XrayUpdater.READ_TIMEOUT); urlConnection.setRequestMethod("GET"); urlConnection.setDoInput(true); urlConnection.connect(); int responseCode = urlConnection.getResponseCode(); String responseType = urlConnection.getContentType(); if (responseCode != HttpsURLConnection.HTTP_OK || !responseType.equalsIgnoreCase(XrayUpdater.FILE_TYPE)) { throw new Exception("Error fetching apk update"); } String storagePath = Environment.getExternalStorageDirectory().getAbsolutePath(); // join storagePath and download directory String downloadPath = new File(storagePath, XrayUpdater.DOWNLOAD_DIR).getAbsolutePath(); // create download dir if doesn't exist File directory = new File(downloadPath); directory.mkdirs(); // write the results into an apk file File outputFile = new File(directory, apkName); if (!outputFile.exists()) { outputFile.createNewFile(); } FileOutputStream outputStream = new FileOutputStream(outputFile); inputStream = urlConnection.getInputStream(); boolean writeSuccess = XrayUpdater.writeToOutputStream(inputStream, outputStream); if (writeSuccess) { String fullPath = outputFile.getAbsolutePath(); // verify checksum of apk String calculatedChecksum = getFileChecksum(fullPath); if (actualChecksum.equals(calculatedChecksum)) { Log.d(TAG, "Checksum of apk file valid. Prompting install..."); promptInstall(fullPath); } else { Log.d(TAG, "Checksum of apk file invalid, deleting apk file"); result = XrayUpdater.UpdateResult.DOWNLOAD_ERROR; outputFile.delete(); } } else { Log.d(TAG, "Unable to write apk when trying to update, apk likely corrupted"); result = XrayUpdater.UpdateResult.DOWNLOAD_ERROR; } } catch (MalformedURLException e) { Log.d(TAG, "Found malformed URL when trying to update"); } catch (SocketTimeoutException e) { Log.d(TAG, "Socket timed out when trying to update: " + e.toString()); } catch (SSLHandshakeException e) { Log.d(TAG, "Failed SSL Handshake when trying to update: " + e.toString()); result = XrayUpdater.UpdateResult.SSL_ERROR; } catch (IOException e) { Log.d(TAG, "Found IO exception when trying to update: " + e.toString()); } catch (Exception e) { Log.d(TAG, "Received error when trying to update: " + e.toString()); } finally { Log.d(TAG, "Cleaning up update task..."); // close the GET connection if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { Log.d(TAG, "Found IO exception when trying to close inputstream: " + e.toString()); } } if (urlConnection != null) { urlConnection.disconnect(); } Log.d(TAG, "Exiting update task"); } return result; } @Override protected void onPostExecute(XrayUpdater.UpdateResult updateResult) { super.onPostExecute(updateResult); if (callback != null) { callback.onFinished(updateResult); } } }