/** * OpenAtlasForAndroid Project * The MIT License (MIT) Copyright (OpenAtlasForAndroid) 2015 Bunny Blue,achellies * <p> * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following conditions: * <p> * The above copyright notice and this permission notice shall be included in all copies * or substantial portions of the Software. * <p> * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @author BunnyBlue **/ package com.openatlas.util; import android.content.pm.ApplicationInfo; import android.content.pm.Signature; import android.util.Log; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.util.Enumeration; import java.util.HashSet; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; public class PackageValidate { public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104; public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105; private static final boolean DEBUG_JAR = false; /** * File name in an APK for the Android manifest. */ private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; private String mArchiveSourcePath; private int mParseError = 0; private static final Object mSync = new Object(); private static WeakReference<byte[]> mReadBuffer; private static final String TAG = "PackageValidate"; public PackageValidate(String archiveSourcePath) { mArchiveSourcePath = archiveSourcePath; } public Package parsePackage() { Package pkg = new Package(""); pkg.mPath = mArchiveSourcePath; return pkg; } public static Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer) { InputStream is = null; try { // We must read the stream for the JarEntry to retrieve // its certificates. is = new BufferedInputStream(jarFile.getInputStream(je)); while (is.read(readBuffer, 0, readBuffer.length) != -1) { // not using } return je != null ? je.getCertificates() : null; } catch (IOException e) { Log.w(TAG, "Exception reading " + je.getName() + " in " + jarFile.getName(), e); } catch (RuntimeException e) { Log.w(TAG, "Exception reading " + je.getName() + " in " + jarFile.getName(), e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } public boolean isSignaturesSame(Signature[] s1, Signature[] s2) { if (s1 == null) { return false; } if (s2 == null) { return false; } HashSet<Signature> set1 = new HashSet<Signature>(); for (Signature sig : s1) { set1.add(sig); } HashSet<Signature> set2 = new HashSet<Signature>(); for (Signature sig : s2) { set2.add(sig); } return set1.equals(set2); } public boolean collectCertificates() { Package pkg = parsePackage(); pkg.mSignatures = null; WeakReference<byte[]> readBufferRef; byte[] readBuffer = null; synchronized (mSync) { readBufferRef = mReadBuffer; if (readBufferRef != null) { mReadBuffer = null; readBuffer = readBufferRef.get(); } if (readBuffer == null) { readBuffer = new byte[8192]; readBufferRef = new WeakReference<byte[]>(readBuffer); } } try { JarFile jarFile = new JarFile(mArchiveSourcePath); Certificate[] certs = null; { Enumeration<JarEntry> entries = jarFile.entries(); final Manifest manifest = jarFile.getManifest(); while (entries.hasMoreElements()) { final JarEntry je = entries.nextElement(); if (je.isDirectory()) continue; final String name = je.getName(); if (name.startsWith("META-INF/")) continue; if (ANDROID_MANIFEST_FILENAME.equals(name)) { final Attributes attributes = manifest.getAttributes(name); pkg.manifestDigest = ManifestDigest.fromAttributes(attributes); } final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer); if (DEBUG_JAR) { Log.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName() + ": certs=" + certs + " (" + (certs != null ? certs.length : 0) + ")"); } if (localCerts == null) { Log.e(TAG, "Package " + pkg.packageName + " has no certificates at entry " + je.getName() + "; ignoring!"); jarFile.close(); mParseError = INSTALL_PARSE_FAILED_NO_CERTIFICATES; return false; } else if (certs == null) { certs = localCerts; } else { // Ensure all certificates match. for (int i = 0; i < certs.length; i++) { boolean found = false; for (int j = 0; j < localCerts.length; j++) { if (certs[i] != null && certs[i].equals(localCerts[j])) { found = true; break; } } if (!found || certs.length != localCerts.length) { Log.e(TAG, "Package " + pkg.packageName + " has mismatched certificates at entry " + je.getName() + "; ignoring!"); jarFile.close(); mParseError = INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; return false; } } } } } jarFile.close(); synchronized (mSync) { mReadBuffer = readBufferRef; } if (certs != null && certs.length > 0) { final int N = certs.length; pkg.mSignatures = new Signature[certs.length]; for (int i = 0; i < N; i++) { pkg.mSignatures[i] = new Signature( certs[i].getEncoded()); } } else { Log.e(TAG, "Package " + pkg.packageName + " has no certificates; ignoring!"); mParseError = INSTALL_PARSE_FAILED_NO_CERTIFICATES; return false; } } catch (CertificateEncodingException e) { Log.w(TAG, "Exception reading " + mArchiveSourcePath, e); mParseError = INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; return false; } catch (IOException e) { Log.w(TAG, "Exception reading " + mArchiveSourcePath, e); mParseError = INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; return false; } catch (RuntimeException e) { Log.w(TAG, "Exception reading " + mArchiveSourcePath, e); mParseError = INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; return false; } return true; } public final static class Package { public String packageName; //For now we only support one application per package. public final ApplicationInfo applicationInfo = new ApplicationInfo(); // If this s a 3rd party app, this is the path of the zip file. public String mPath; // Signatures that were read from the package. public Signature mSignatures[]; /** * Digest suitable for comparing whether this package's manifest is the * same as another. */ public ManifestDigest manifestDigest; public Package(String _name) { packageName = _name; applicationInfo.packageName = _name; applicationInfo.uid = -1; } } }