Revision 9e69afe0
| res/layout/activity_main.xml | ||
|---|---|---|
| 1 |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
| 1 |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
| 2 | 2 |
xmlns:tools="http://schemas.android.com/tools" |
| 3 |
android:layout_width="match_parent" |
|
| 4 |
android:layout_height="match_parent" > |
|
| 3 |
android:layout_width="fill_parent" |
|
| 4 |
android:layout_height="fill_parent" |
|
| 5 |
android:orientation="vertical" > |
|
| 6 |
|
|
| 7 |
<TableLayout |
|
| 8 |
android:layout_width="match_parent" |
|
| 9 |
android:layout_height="wrap_content" |
|
| 10 |
android:paddingBottom="5dip" |
|
| 11 |
android:paddingTop="5dip" > |
|
| 12 |
|
|
| 13 |
<TableRow |
|
| 14 |
android:layout_width="wrap_content" |
|
| 15 |
android:layout_height="wrap_content" > |
|
| 16 |
|
|
| 17 |
<TextView |
|
| 18 |
android:layout_width="wrap_content" |
|
| 19 |
android:layout_height="wrap_content" |
|
| 20 |
android:layout_gravity="right|center_vertical" |
|
| 21 |
android:text="SubjectDN:" |
|
| 22 |
android:typeface="monospace" /> |
|
| 23 |
|
|
| 24 |
<TextView |
|
| 25 |
android:id="@+id/subjectdn" |
|
| 26 |
android:layout_width="wrap_content" |
|
| 27 |
android:layout_height="wrap_content" /> |
|
| 28 |
</TableRow> |
|
| 29 |
|
|
| 30 |
<TableRow |
|
| 31 |
android:layout_width="wrap_content" |
|
| 32 |
android:layout_height="wrap_content" > |
|
| 33 |
|
|
| 34 |
<TextView |
|
| 35 |
android:layout_width="wrap_content" |
|
| 36 |
android:layout_height="wrap_content" |
|
| 37 |
android:layout_gravity="right|center_vertical" |
|
| 38 |
android:text="IssuerDN:" |
|
| 39 |
android:typeface="monospace" /> |
|
| 40 |
|
|
| 41 |
<TextView |
|
| 42 |
android:id="@+id/issuerdn" |
|
| 43 |
android:layout_width="wrap_content" |
|
| 44 |
android:layout_height="wrap_content" /> |
|
| 45 |
</TableRow> |
|
| 46 |
</TableLayout> |
|
| 5 | 47 |
|
| 6 | 48 |
<fragment |
| 7 | 49 |
android:id="@+id/fragment_app_list" |
| 50 |
android:name="info.guardianproject.checkey.MainActivity$AppListFragment" |
|
| 8 | 51 |
android:layout_width="fill_parent" |
| 9 |
android:layout_height="fill_parent" |
|
| 10 |
class="info.guardianproject.checkey.AppListFragment" /> |
|
| 52 |
android:layout_height="wrap_content" /> |
|
| 11 | 53 |
|
| 12 |
</RelativeLayout> |
|
| 54 |
</LinearLayout> |
|
| res/menu/context.xml | ||
|---|---|---|
| 1 |
<?xml version="1.0" encoding="utf-8"?> |
|
| 2 |
<menu xmlns:android="http://schemas.android.com/apk/res/android" > |
|
| 3 |
|
|
| 4 |
<item |
|
| 5 |
android:id="@+id/save" |
|
| 6 |
android:icon="@android:drawable/ic_menu_save" |
|
| 7 |
android:showAsAction="ifRoom|withText" |
|
| 8 |
android:title="@string/save"/> |
|
| 9 |
<item |
|
| 10 |
android:id="@+id/virustotal" |
|
| 11 |
android:icon="@drawable/virustotal" |
|
| 12 |
android:showAsAction="ifRoom|withText" |
|
| 13 |
android:title="@string/virustotal"/> |
|
| 14 |
<item |
|
| 15 |
android:id="@+id/by_apk_hash" |
|
| 16 |
android:showAsAction="ifRoom|withText" |
|
| 17 |
android:title="@string/by_apk_hash"/> |
|
| 18 |
<item |
|
| 19 |
android:id="@+id/by_package_name" |
|
| 20 |
android:showAsAction="ifRoom|withText" |
|
| 21 |
android:title="@string/by_package_name"/> |
|
| 22 |
<item |
|
| 23 |
android:id="@+id/by_signing_certificate" |
|
| 24 |
android:showAsAction="ifRoom|withText" |
|
| 25 |
android:title="@string/by_signing_certificate"/> |
|
| 26 |
|
|
| 27 |
</menu> |
|
| res/menu/main.xml | ||
|---|---|---|
| 4 | 4 |
tools:context="info.guardianproject.checkey.MainActivity" > |
| 5 | 5 |
|
| 6 | 6 |
<item |
| 7 |
android:id="@+id/save" |
|
| 8 |
android:icon="@android:drawable/ic_menu_save" |
|
| 9 |
android:showAsAction="ifRoom|withText" |
|
| 10 |
android:title="@string/save"/> |
|
| 11 |
<item |
|
| 12 |
android:id="@+id/virustotal" |
|
| 13 |
android:icon="@drawable/virustotal" |
|
| 14 |
android:showAsAction="ifRoom|withText" |
|
| 15 |
android:title="@string/virustotal"/> |
|
| 16 |
<item |
|
| 17 |
android:id="@+id/by_apk_hash" |
|
| 18 |
android:showAsAction="ifRoom|withText" |
|
| 19 |
android:title="@string/by_apk_hash"/> |
|
| 20 |
<item |
|
| 21 |
android:id="@+id/by_package_name" |
|
| 22 |
android:showAsAction="ifRoom|withText" |
|
| 23 |
android:title="@string/by_package_name"/> |
|
| 24 |
<item |
|
| 25 |
android:id="@+id/by_signing_certificate" |
|
| 26 |
android:showAsAction="ifRoom|withText" |
|
| 27 |
android:title="@string/by_signing_certificate"/> |
|
| 28 |
<item |
|
| 7 | 29 |
android:id="@+id/action_settings" |
| 8 | 30 |
android:icon="@android:drawable/ic_menu_preferences" |
| 9 | 31 |
android:orderInCategory="100" |
| src/info/guardianproject/checkey/AppListFragment.java | ||
|---|---|---|
| 1 |
/* |
|
| 2 |
Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 3 |
you may not use this file except in compliance with the License. |
|
| 4 |
You may obtain a copy of the License at |
|
| 5 |
|
|
| 6 |
http://www.apache.org/licenses/LICENSE-2.0 |
|
| 7 |
|
|
| 8 |
Unless required by applicable law or agreed to in writing, software |
|
| 9 |
distributed under the License is distributed on an "AS IS" BASIS, |
|
| 10 |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 11 |
See the License for the specific language governing permissions and |
|
| 12 |
limitations under the License. |
|
| 13 |
|
|
| 14 |
Based on Paul Blundell's Tutorial: |
|
| 15 |
http://blog.blundell-apps.com/tut-asynctask-loader-using-support-library/ |
|
| 16 |
|
|
| 17 |
which is originally based on: |
|
| 18 |
https://developer.android.com/reference/android/content/AsyncTaskLoader.html |
|
| 19 |
*/ |
|
| 20 |
|
|
| 21 |
package info.guardianproject.checkey; |
|
| 22 |
|
|
| 23 |
import android.app.Activity; |
|
| 24 |
import android.content.Context; |
|
| 25 |
import android.content.Intent; |
|
| 26 |
import android.content.pm.PackageInfo; |
|
| 27 |
import android.content.pm.PackageManager; |
|
| 28 |
import android.content.pm.PackageManager.NameNotFoundException; |
|
| 29 |
import android.content.pm.Signature; |
|
| 30 |
import android.net.Uri; |
|
| 31 |
import android.os.Bundle; |
|
| 32 |
import android.support.v4.app.ListFragment; |
|
| 33 |
import android.support.v4.app.LoaderManager.LoaderCallbacks; |
|
| 34 |
import android.support.v4.content.Loader; |
|
| 35 |
import android.support.v7.app.ActionBarActivity; |
|
| 36 |
import android.support.v7.view.ActionMode; |
|
| 37 |
import android.view.Menu; |
|
| 38 |
import android.view.MenuInflater; |
|
| 39 |
import android.view.MenuItem; |
|
| 40 |
import android.view.View; |
|
| 41 |
import android.webkit.WebView; |
|
| 42 |
import android.widget.ListView; |
|
| 43 |
import android.widget.Toast; |
|
| 44 |
|
|
| 45 |
import java.io.ByteArrayInputStream; |
|
| 46 |
import java.io.FileNotFoundException; |
|
| 47 |
import java.io.FileOutputStream; |
|
| 48 |
import java.io.IOException; |
|
| 49 |
import java.io.InputStream; |
|
| 50 |
import java.io.UnsupportedEncodingException; |
|
| 51 |
import java.security.NoSuchAlgorithmException; |
|
| 52 |
import java.security.cert.CertificateException; |
|
| 53 |
import java.security.cert.CertificateFactory; |
|
| 54 |
import java.security.cert.X509Certificate; |
|
| 55 |
import java.util.List; |
|
| 56 |
|
|
| 57 |
public class AppListFragment extends ListFragment implements LoaderCallbacks<List<AppEntry>> {
|
|
| 58 |
|
|
| 59 |
private PackageManager pm; |
|
| 60 |
private AppListAdapter adapter; |
|
| 61 |
private ActionMode actionMode; |
|
| 62 |
private ListView listView; |
|
| 63 |
private int selectedItem = -1; |
|
| 64 |
private static final String STATE_CHECKED = "info.guardianproject.checkey.STATE_CHECKED"; |
|
| 65 |
WebView androidObservatoryView; |
|
| 66 |
|
|
| 67 |
@Override |
|
| 68 |
public void onActivityCreated(Bundle savedInstanceState) {
|
|
| 69 |
super.onActivityCreated(savedInstanceState); |
|
| 70 |
|
|
| 71 |
setEmptyText(getString(R.string.no_applications_found)); |
|
| 72 |
|
|
| 73 |
adapter = new AppListAdapter(getActivity()); |
|
| 74 |
setListAdapter(adapter); |
|
| 75 |
setListShown(false); |
|
| 76 |
|
|
| 77 |
listView = getListView(); |
|
| 78 |
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); |
|
| 79 |
|
|
| 80 |
if (savedInstanceState != null) {
|
|
| 81 |
int position = savedInstanceState.getInt(STATE_CHECKED, -1); |
|
| 82 |
if (position > -1) {
|
|
| 83 |
listView.setItemChecked(position, true); |
|
| 84 |
} |
|
| 85 |
} |
|
| 86 |
|
|
| 87 |
// Prepare the loader |
|
| 88 |
// either reconnect with an existing one or start a new one |
|
| 89 |
getLoaderManager().initLoader(0, null, this); |
|
| 90 |
} |
|
| 91 |
|
|
| 92 |
@Override |
|
| 93 |
public void onSaveInstanceState(Bundle savedInstanceState) {
|
|
| 94 |
super.onSaveInstanceState(savedInstanceState); |
|
| 95 |
savedInstanceState.putInt(STATE_CHECKED, selectedItem); |
|
| 96 |
} |
|
| 97 |
|
|
| 98 |
@Override |
|
| 99 |
public void onListItemClick(ListView l, View v, int position, long id) {
|
|
| 100 |
if (actionMode != null) {
|
|
| 101 |
return; |
|
| 102 |
} |
|
| 103 |
|
|
| 104 |
// Start the CAB using the ActionMode.Callback defined above |
|
| 105 |
ActionBarActivity activity = (ActionBarActivity) getActivity(); |
|
| 106 |
actionMode = activity.startSupportActionMode(mActionModeCallback); |
|
| 107 |
selectedItem = position; |
|
| 108 |
} |
|
| 109 |
|
|
| 110 |
private void saveCertificate(AppEntry appEntry, Intent intent) {
|
|
| 111 |
if (pm == null) |
|
| 112 |
pm = getActivity().getApplicationContext().getPackageManager(); |
|
| 113 |
Activity activity = getActivity(); |
|
| 114 |
String packageName = appEntry.getPackageName(); |
|
| 115 |
PackageInfo pkgInfo; |
|
| 116 |
try {
|
|
| 117 |
pkgInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); |
|
| 118 |
CertificateFactory certFactory = CertificateFactory.getInstance("X509");
|
|
| 119 |
for (Signature sig : pkgInfo.signatures) {
|
|
| 120 |
byte[] cert = sig.toByteArray(); |
|
| 121 |
InputStream inStream = new ByteArrayInputStream(cert); |
|
| 122 |
X509Certificate x509 = (X509Certificate) certFactory.generateCertificate(inStream); |
|
| 123 |
|
|
| 124 |
String fileName = packageName + ".cer"; |
|
| 125 |
final FileOutputStream os = activity.openFileOutput(fileName, |
|
| 126 |
Context.MODE_WORLD_READABLE); |
|
| 127 |
os.write(cert); |
|
| 128 |
os.close(); |
|
| 129 |
|
|
| 130 |
String subject = packageName + " - " + x509.getIssuerDN().getName() |
|
| 131 |
+ " - " + x509.getNotAfter(); |
|
| 132 |
Uri uri = Uri.fromFile(activity.getFileStreamPath(fileName)); |
|
| 133 |
Intent i = new Intent(Intent.ACTION_SEND); |
|
| 134 |
i.setType("application/pkix-cert");
|
|
| 135 |
i.putExtra(Intent.EXTRA_STREAM, uri); |
|
| 136 |
i.putExtra(Intent.EXTRA_TITLE, subject); |
|
| 137 |
i.putExtra(Intent.EXTRA_SUBJECT, subject); |
|
| 138 |
startActivity(Intent.createChooser(i, getString(R.string.save_cert_using))); |
|
| 139 |
} |
|
| 140 |
} catch (NameNotFoundException e) {
|
|
| 141 |
e.printStackTrace(); |
|
| 142 |
} catch (CertificateException e) {
|
|
| 143 |
e.printStackTrace(); |
|
| 144 |
} catch (FileNotFoundException e) {
|
|
| 145 |
e.printStackTrace(); |
|
| 146 |
} catch (UnsupportedEncodingException e) {
|
|
| 147 |
e.printStackTrace(); |
|
| 148 |
} catch (IOException e) {
|
|
| 149 |
e.printStackTrace(); |
|
| 150 |
} |
|
| 151 |
} |
|
| 152 |
|
|
| 153 |
private void virustotal(AppEntry appEntry, Intent intent) {
|
|
| 154 |
String urlString = "https://www.virustotal.com/en/file/" |
|
| 155 |
+ Utils.getBinaryHash(appEntry.getApkFile(), "sha256") + "/analysis/"; |
|
| 156 |
intent.setData(Uri.parse(urlString)); |
|
| 157 |
intent.putExtra(Intent.EXTRA_TITLE, R.string.virustotal); |
|
| 158 |
startActivity(intent); |
|
| 159 |
} |
|
| 160 |
|
|
| 161 |
private void byApkHash(AppEntry appEntry, Intent intent) {
|
|
| 162 |
String urlString = "https://androidobservatory.org/?searchby=binhash&q=" |
|
| 163 |
+ Utils.getBinaryHash(appEntry.getApkFile(), "sha1"); |
|
| 164 |
intent.setData(Uri.parse(urlString)); |
|
| 165 |
intent.putExtra(Intent.EXTRA_TITLE, R.string.by_apk_hash); |
|
| 166 |
startActivity(intent); |
|
| 167 |
} |
|
| 168 |
|
|
| 169 |
private void byPackageName(AppEntry appEntry, Intent intent) {
|
|
| 170 |
String urlString = "https://androidobservatory.org/?searchby=pkg&q=" |
|
| 171 |
+ appEntry.getPackageName(); |
|
| 172 |
intent.setData(Uri.parse(urlString)); |
|
| 173 |
intent.putExtra(Intent.EXTRA_TITLE, R.string.by_package_name); |
|
| 174 |
startActivity(intent); |
|
| 175 |
} |
|
| 176 |
|
|
| 177 |
private void bySigningCertificate(AppEntry appEntry, Intent intent) {
|
|
| 178 |
String sha1; |
|
| 179 |
try {
|
|
| 180 |
sha1 = Utils.getCertificateFingerprint(appEntry.getApkFile(), "sha1"); |
|
| 181 |
} catch (NoSuchAlgorithmException e) {
|
|
| 182 |
e.printStackTrace(); |
|
| 183 |
Toast.makeText(getActivity(), "Cannot make fingerprint of signing certificate", |
|
| 184 |
Toast.LENGTH_LONG).show(); |
|
| 185 |
return; |
|
| 186 |
} |
|
| 187 |
intent.setData(Uri.parse("https://androidobservatory.org/?searchby=certhash&q=" + sha1));
|
|
| 188 |
intent.putExtra(Intent.EXTRA_TITLE, R.string.by_signing_certificate); |
|
| 189 |
startActivity(intent); |
|
| 190 |
} |
|
| 191 |
|
|
| 192 |
@Override |
|
| 193 |
public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
|
|
| 194 |
// This is called when a new loader needs to be created. |
|
| 195 |
// This sample only has one Loader with no arguments, so it is simple. |
|
| 196 |
return new AppListLoader(getActivity()); |
|
| 197 |
} |
|
| 198 |
|
|
| 199 |
@Override |
|
| 200 |
public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
|
|
| 201 |
adapter.setData(data); |
|
| 202 |
|
|
| 203 |
// The list should now be shown |
|
| 204 |
if (isResumed()) {
|
|
| 205 |
setListShown(true); |
|
| 206 |
} else {
|
|
| 207 |
setListShownNoAnimation(true); |
|
| 208 |
} |
|
| 209 |
} |
|
| 210 |
|
|
| 211 |
@Override |
|
| 212 |
public void onLoaderReset(Loader<List<AppEntry>> loader) {
|
|
| 213 |
// Clear the data in the adapter |
|
| 214 |
adapter.setData(null); |
|
| 215 |
} |
|
| 216 |
|
|
| 217 |
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
|
|
| 218 |
|
|
| 219 |
// Called when the action mode is created; startActionMode() was called |
|
| 220 |
@Override |
|
| 221 |
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
|
| 222 |
// Inflate a menu resource providing context menu items |
|
| 223 |
MenuInflater inflater = mode.getMenuInflater(); |
|
| 224 |
inflater.inflate(R.menu.context, menu); |
|
| 225 |
return true; |
|
| 226 |
} |
|
| 227 |
|
|
| 228 |
@Override |
|
| 229 |
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
|
| 230 |
return false; |
|
| 231 |
} |
|
| 232 |
|
|
| 233 |
// Called when the user selects a contextual menu item |
|
| 234 |
@Override |
|
| 235 |
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
|
| 236 |
AppEntry appEntry = (AppEntry) adapter.getItem(selectedItem); |
|
| 237 |
Intent intent = new Intent(getActivity(), WebViewActivity.class); |
|
| 238 |
intent.putExtra(Intent.EXTRA_TEXT, appEntry.getLabel()); |
|
| 239 |
switch (item.getItemId()) {
|
|
| 240 |
case R.id.save: |
|
| 241 |
saveCertificate(appEntry, intent); |
|
| 242 |
break; |
|
| 243 |
case R.id.virustotal: |
|
| 244 |
virustotal(appEntry, intent); |
|
| 245 |
break; |
|
| 246 |
case R.id.by_apk_hash: |
|
| 247 |
byApkHash(appEntry, intent); |
|
| 248 |
break; |
|
| 249 |
case R.id.by_package_name: |
|
| 250 |
byPackageName(appEntry, intent); |
|
| 251 |
break; |
|
| 252 |
case R.id.by_signing_certificate: |
|
| 253 |
bySigningCertificate(appEntry, intent); |
|
| 254 |
break; |
|
| 255 |
|
|
| 256 |
default: |
|
| 257 |
return false; |
|
| 258 |
} |
|
| 259 |
mode.finish(); |
|
| 260 |
return true; |
|
| 261 |
} |
|
| 262 |
|
|
| 263 |
// Called when the user exits the action mode |
|
| 264 |
@Override |
|
| 265 |
public void onDestroyActionMode(ActionMode mode) {
|
|
| 266 |
listView.setItemChecked(selectedItem, false); |
|
| 267 |
listView.clearChoices(); |
|
| 268 |
selectedItem = -1; |
|
| 269 |
actionMode = null; |
|
| 270 |
} |
|
| 271 |
}; |
|
| 272 |
} |
|
| src/info/guardianproject/checkey/MainActivity.java | ||
|---|---|---|
| 1 | 1 |
|
| 2 | 2 |
package info.guardianproject.checkey; |
| 3 | 3 |
|
| 4 |
import android.annotation.SuppressLint; |
|
| 5 |
import android.app.Activity; |
|
| 6 |
import android.content.Context; |
|
| 7 |
import android.content.Intent; |
|
| 8 |
import android.content.pm.PackageInfo; |
|
| 9 |
import android.content.pm.PackageManager; |
|
| 10 |
import android.content.pm.PackageManager.NameNotFoundException; |
|
| 11 |
import android.net.Uri; |
|
| 4 | 12 |
import android.os.Bundle; |
| 13 |
import android.support.v4.app.ListFragment; |
|
| 14 |
import android.support.v4.app.LoaderManager.LoaderCallbacks; |
|
| 15 |
import android.support.v4.content.Loader; |
|
| 5 | 16 |
import android.support.v7.app.ActionBarActivity; |
| 17 |
import android.util.Log; |
|
| 6 | 18 |
import android.view.Menu; |
| 7 | 19 |
import android.view.MenuItem; |
| 20 |
import android.view.View; |
|
| 21 |
import android.webkit.WebView; |
|
| 22 |
import android.widget.ListAdapter; |
|
| 23 |
import android.widget.ListView; |
|
| 24 |
import android.widget.TextView; |
|
| 25 |
import android.widget.Toast; |
|
| 26 |
|
|
| 27 |
import java.io.ByteArrayInputStream; |
|
| 28 |
import java.io.FileNotFoundException; |
|
| 29 |
import java.io.FileOutputStream; |
|
| 30 |
import java.io.IOException; |
|
| 31 |
import java.io.InputStream; |
|
| 32 |
import java.io.StringBufferInputStream; |
|
| 33 |
import java.io.UnsupportedEncodingException; |
|
| 34 |
import java.security.NoSuchAlgorithmException; |
|
| 35 |
import java.security.cert.CertificateException; |
|
| 36 |
import java.security.cert.CertificateFactory; |
|
| 37 |
import java.security.cert.X509Certificate; |
|
| 38 |
import java.util.Arrays; |
|
| 39 |
import java.util.List; |
|
| 40 |
import java.util.Properties; |
|
| 8 | 41 |
|
| 9 | 42 |
public class MainActivity extends ActionBarActivity {
|
| 10 | 43 |
private final String TAG = "MainActivity"; |
| 44 |
|
|
| 45 |
private static PackageManager pm; |
|
| 46 |
private static CertificateFactory certificateFactory; |
|
| 47 |
private static int selectedItem = -1; |
|
| 11 | 48 |
private AppListFragment appListFragment = null; |
| 12 | 49 |
|
| 13 | 50 |
@Override |
| ... | ... | |
| 32 | 69 |
|
| 33 | 70 |
@Override |
| 34 | 71 |
public boolean onOptionsItemSelected(MenuItem item) {
|
| 72 |
ListAdapter adapter = appListFragment.getListAdapter(); |
|
| 73 |
AppEntry appEntry = (AppEntry) adapter.getItem(selectedItem); |
|
| 74 |
Intent intent = new Intent(this, WebViewActivity.class); |
|
| 75 |
intent.putExtra(Intent.EXTRA_TEXT, appEntry.getLabel()); |
|
| 35 | 76 |
switch (item.getItemId()) {
|
| 36 | 77 |
case android.R.id.home: |
| 37 | 78 |
setResult(RESULT_CANCELED); |
| 38 | 79 |
finish(); |
| 39 | 80 |
return true; |
| 81 |
case R.id.save: |
|
| 82 |
saveCertificate(appEntry, intent); |
|
| 83 |
return true; |
|
| 84 |
case R.id.virustotal: |
|
| 85 |
virustotal(appEntry, intent); |
|
| 86 |
return true; |
|
| 87 |
case R.id.by_apk_hash: |
|
| 88 |
byApkHash(appEntry, intent); |
|
| 89 |
return true; |
|
| 90 |
case R.id.by_package_name: |
|
| 91 |
byPackageName(appEntry, intent); |
|
| 92 |
return true; |
|
| 93 |
case R.id.by_signing_certificate: |
|
| 94 |
bySigningCertificate(appEntry, intent); |
|
| 95 |
return true; |
|
| 40 | 96 |
case R.id.action_settings: |
| 41 | 97 |
return true; |
| 42 | 98 |
} |
| 43 | 99 |
return super.onOptionsItemSelected(item); |
| 44 | 100 |
} |
| 101 |
|
|
| 102 |
private static X509Certificate[] getX509Certificates(Context context, String packageName) {
|
|
| 103 |
X509Certificate[] certs = null; |
|
| 104 |
if (pm == null) |
|
| 105 |
pm = context.getApplicationContext().getPackageManager(); |
|
| 106 |
try {
|
|
| 107 |
PackageInfo pkgInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); |
|
| 108 |
if (certificateFactory == null) |
|
| 109 |
certificateFactory = CertificateFactory.getInstance("X509");
|
|
| 110 |
certs = new X509Certificate[pkgInfo.signatures.length]; |
|
| 111 |
for (int i = 0; i < certs.length; i++) {
|
|
| 112 |
byte[] cert = pkgInfo.signatures[i].toByteArray(); |
|
| 113 |
InputStream inStream = new ByteArrayInputStream(cert); |
|
| 114 |
certs[i] = (X509Certificate) certificateFactory.generateCertificate(inStream); |
|
| 115 |
} |
|
| 116 |
} catch (NameNotFoundException e) {
|
|
| 117 |
e.printStackTrace(); |
|
| 118 |
} catch (CertificateException e) {
|
|
| 119 |
e.printStackTrace(); |
|
| 120 |
} |
|
| 121 |
return certs; |
|
| 122 |
} |
|
| 123 |
|
|
| 124 |
private static void showCertificateInfo(Activity activity, AppEntry appEntry) {
|
|
| 125 |
String packageName = appEntry.getPackageName(); |
|
| 126 |
X509Certificate[] certs = getX509Certificates(activity, packageName); |
|
| 127 |
if (certs == null || certs.length < 1) |
|
| 128 |
return; |
|
| 129 |
// for now, just support the first cert since that is far and away |
|
| 130 |
// the |
|
| 131 |
// most common |
|
| 132 |
X509Certificate cert = certs[0]; |
|
| 133 |
TextView issuerdn = (TextView) activity.findViewById(R.id.issuerdn); |
|
| 134 |
issuerdn.setText(cert.getIssuerDN().getName()); |
|
| 135 |
TextView subjectdn = (TextView) activity.findViewById(R.id.subjectdn); |
|
| 136 |
subjectdn.setText(cert.getSubjectDN().getName()); |
|
| 137 |
} |
|
| 138 |
|
|
| 139 |
@SuppressLint("WorldReadableFiles")
|
|
| 140 |
private void saveCertificate(AppEntry appEntry, Intent intent) {
|
|
| 141 |
String packageName = appEntry.getPackageName(); |
|
| 142 |
try {
|
|
| 143 |
for (X509Certificate x509 : getX509Certificates(this, packageName)) {
|
|
| 144 |
String fileName = packageName + ".cer"; |
|
| 145 |
@SuppressWarnings("deprecation")
|
|
| 146 |
final FileOutputStream os = openFileOutput(fileName, |
|
| 147 |
Context.MODE_WORLD_READABLE); |
|
| 148 |
os.write(x509.getEncoded()); |
|
| 149 |
os.close(); |
|
| 150 |
|
|
| 151 |
String subject = packageName + " - " + x509.getIssuerDN().getName() |
|
| 152 |
+ " - " + x509.getNotAfter(); |
|
| 153 |
Uri uri = Uri.fromFile(getFileStreamPath(fileName)); |
|
| 154 |
Intent i = new Intent(Intent.ACTION_SEND); |
|
| 155 |
i.setType("application/pkix-cert");
|
|
| 156 |
i.putExtra(Intent.EXTRA_STREAM, uri); |
|
| 157 |
i.putExtra(Intent.EXTRA_TITLE, subject); |
|
| 158 |
i.putExtra(Intent.EXTRA_SUBJECT, subject); |
|
| 159 |
startActivity(Intent.createChooser(i, getString(R.string.save_cert_using))); |
|
| 160 |
} |
|
| 161 |
} catch (CertificateException e) {
|
|
| 162 |
e.printStackTrace(); |
|
| 163 |
} catch (FileNotFoundException e) {
|
|
| 164 |
e.printStackTrace(); |
|
| 165 |
} catch (UnsupportedEncodingException e) {
|
|
| 166 |
e.printStackTrace(); |
|
| 167 |
} catch (IOException e) {
|
|
| 168 |
e.printStackTrace(); |
|
| 169 |
} |
|
| 170 |
} |
|
| 171 |
|
|
| 172 |
private void virustotal(AppEntry appEntry, Intent intent) {
|
|
| 173 |
String urlString = "https://www.virustotal.com/en/file/" |
|
| 174 |
+ Utils.getBinaryHash(appEntry.getApkFile(), "sha256") + "/analysis/"; |
|
| 175 |
intent.setData(Uri.parse(urlString)); |
|
| 176 |
intent.putExtra(Intent.EXTRA_TITLE, R.string.virustotal); |
|
| 177 |
startActivity(intent); |
|
| 178 |
} |
|
| 179 |
|
|
| 180 |
private void byApkHash(AppEntry appEntry, Intent intent) {
|
|
| 181 |
String urlString = "https://androidobservatory.org/?searchby=binhash&q=" |
|
| 182 |
+ Utils.getBinaryHash(appEntry.getApkFile(), "sha1"); |
|
| 183 |
intent.setData(Uri.parse(urlString)); |
|
| 184 |
intent.putExtra(Intent.EXTRA_TITLE, R.string.by_apk_hash); |
|
| 185 |
startActivity(intent); |
|
| 186 |
} |
|
| 187 |
|
|
| 188 |
private void byPackageName(AppEntry appEntry, Intent intent) {
|
|
| 189 |
String urlString = "https://androidobservatory.org/?searchby=pkg&q=" |
|
| 190 |
+ appEntry.getPackageName(); |
|
| 191 |
intent.setData(Uri.parse(urlString)); |
|
| 192 |
intent.putExtra(Intent.EXTRA_TITLE, R.string.by_package_name); |
|
| 193 |
startActivity(intent); |
|
| 194 |
} |
|
| 195 |
|
|
| 196 |
private void bySigningCertificate(AppEntry appEntry, Intent intent) {
|
|
| 197 |
String sha1; |
|
| 198 |
try {
|
|
| 199 |
sha1 = Utils.getCertificateFingerprint(appEntry.getApkFile(), "sha1"); |
|
| 200 |
} catch (NoSuchAlgorithmException e) {
|
|
| 201 |
e.printStackTrace(); |
|
| 202 |
Toast.makeText(this, "Cannot make fingerprint of signing certificate", |
|
| 203 |
Toast.LENGTH_LONG).show(); |
|
| 204 |
return; |
|
| 205 |
} |
|
| 206 |
intent.setData(Uri.parse("https://androidobservatory.org/?searchby=certhash&q=" + sha1));
|
|
| 207 |
intent.putExtra(Intent.EXTRA_TITLE, R.string.by_signing_certificate); |
|
| 208 |
startActivity(intent); |
|
| 209 |
} |
|
| 210 |
|
|
| 211 |
public static class AppListFragment extends ListFragment implements |
|
| 212 |
LoaderCallbacks<List<AppEntry>> {
|
|
| 213 |
|
|
| 214 |
private AppListAdapter adapter; |
|
| 215 |
private ListView listView; |
|
| 216 |
private static final String STATE_CHECKED = "info.guardianproject.checkey.STATE_CHECKED"; |
|
| 217 |
WebView androidObservatoryView; |
|
| 218 |
|
|
| 219 |
public AppListFragment() {
|
|
| 220 |
super(); |
|
| 221 |
} |
|
| 222 |
|
|
| 223 |
@Override |
|
| 224 |
public void onActivityCreated(Bundle savedInstanceState) {
|
|
| 225 |
super.onActivityCreated(savedInstanceState); |
|
| 226 |
|
|
| 227 |
setEmptyText(getString(R.string.no_applications_found)); |
|
| 228 |
|
|
| 229 |
adapter = new AppListAdapter(getActivity()); |
|
| 230 |
setListAdapter(adapter); |
|
| 231 |
setListShown(false); |
|
| 232 |
|
|
| 233 |
listView = getListView(); |
|
| 234 |
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); |
|
| 235 |
|
|
| 236 |
if (savedInstanceState != null) {
|
|
| 237 |
int position = savedInstanceState.getInt(STATE_CHECKED, -1); |
|
| 238 |
if (position > -1) {
|
|
| 239 |
listView.setItemChecked(position, true); |
|
| 240 |
} |
|
| 241 |
} |
|
| 242 |
|
|
| 243 |
// Prepare the loader |
|
| 244 |
// either reconnect with an existing one or start a new one |
|
| 245 |
getLoaderManager().initLoader(0, null, this); |
|
| 246 |
} |
|
| 247 |
|
|
| 248 |
@Override |
|
| 249 |
public void onSaveInstanceState(Bundle savedInstanceState) {
|
|
| 250 |
super.onSaveInstanceState(savedInstanceState); |
|
| 251 |
savedInstanceState.putInt(STATE_CHECKED, selectedItem); |
|
| 252 |
} |
|
| 253 |
|
|
| 254 |
@Override |
|
| 255 |
public void onListItemClick(ListView l, View v, int position, long id) {
|
|
| 256 |
// Start the CAB using the ActionMode.Callback defined above |
|
| 257 |
ActionBarActivity activity = (ActionBarActivity) getActivity(); |
|
| 258 |
selectedItem = position; |
|
| 259 |
AppEntry appEntry = (AppEntry) adapter.getItem(selectedItem); |
|
| 260 |
showCertificateInfo(activity, appEntry); |
|
| 261 |
} |
|
| 262 |
|
|
| 263 |
@Override |
|
| 264 |
public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
|
|
| 265 |
// This is called when a new loader needs to be created. |
|
| 266 |
// This sample only has one Loader with no arguments, so it is |
|
| 267 |
// simple. |
|
| 268 |
return new AppListLoader(getActivity()); |
|
| 269 |
} |
|
| 270 |
|
|
| 271 |
@Override |
|
| 272 |
public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
|
|
| 273 |
adapter.setData(data); |
|
| 274 |
|
|
| 275 |
// The list should now be shown |
|
| 276 |
if (isResumed()) {
|
|
| 277 |
setListShown(true); |
|
| 278 |
} else {
|
|
| 279 |
setListShownNoAnimation(true); |
|
| 280 |
} |
|
| 281 |
} |
|
| 282 |
|
|
| 283 |
@Override |
|
| 284 |
public void onLoaderReset(Loader<List<AppEntry>> loader) {
|
|
| 285 |
// Clear the data in the adapter |
|
| 286 |
adapter.setData(null); |
|
| 287 |
} |
|
| 288 |
} |
|
| 45 | 289 |
} |
Also available in: Unified diff