/*
* Tencent is pleased to support the open source community by making Tinker available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.tinker.loader.shareutil;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import com.tencent.tinker.loader.TinkerRuntimeException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Created by zhangshaowen on 16/3/10.
*/
public class ShareSecurityCheck {
private static final String TAG = "ShareSecurityCheck";
/**
* static to faster
* public key
*/
private static PublicKey mPublicKey = null;
private final Context mContext;
private final HashMap<String, String> metaContentMap;
private HashMap<String, String> packageProperties;
public ShareSecurityCheck(Context context) {
mContext = context;
metaContentMap = new HashMap<>();
if (mPublicKey == null) {
init(mContext);
}
}
public HashMap<String, String> getMetaContentMap() {
return metaContentMap;
}
/**
* Nullable
*
* @return HashMap<String, String>
*/
public HashMap<String, String> getPackagePropertiesIfPresent() {
if (packageProperties != null) {
return packageProperties;
}
String property = metaContentMap.get(ShareConstants.PACKAGE_META_FILE);
if (property == null) {
return null;
}
String[] lines = property.split("\n");
for (final String line : lines) {
if (line == null || line.length() <= 0) {
continue;
}
//it is comment
if (line.startsWith("#")) {
continue;
}
final String[] kv = line.split("=", 2);
if (kv == null || kv.length < 2) {
continue;
}
if (packageProperties == null) {
packageProperties = new HashMap<>();
}
packageProperties.put(kv[0].trim(), kv[1].trim());
}
return packageProperties;
}
public boolean verifyPatchMetaSignature(File path) {
if (!SharePatchFileUtil.isLegalFile(path)) {
return false;
}
JarFile jarFile = null;
try {
jarFile = new JarFile(path);
final Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
// no code
if (jarEntry == null) {
continue;
}
final String name = jarEntry.getName();
if (name.startsWith("META-INF/")) {
continue;
}
//for faster, only check the meta.txt files
//we will check other files's mad5 written in meta files
if (!name.endsWith(ShareConstants.META_SUFFIX)) {
continue;
}
metaContentMap.put(name, SharePatchFileUtil.loadDigestes(jarFile, jarEntry));
Certificate[] certs = jarEntry.getCertificates();
if (certs == null) {
return false;
}
if (!check(path, certs)) {
return false;
}
}
} catch (Exception e) {
throw new TinkerRuntimeException(
String.format("ShareSecurityCheck file %s, size %d verifyPatchMetaSignature fail", path.getAbsolutePath(), path.length()), e);
} finally {
try {
if (jarFile != null) {
jarFile.close();
}
} catch (IOException e) {
Log.e(TAG, path.getAbsolutePath(), e);
}
}
return true;
}
// verify the signature of the Apk
private boolean check(File path, Certificate[] certs) {
if (certs.length > 0) {
for (int i = certs.length - 1; i >= 0; i--) {
try {
certs[i].verify(mPublicKey);
return true;
} catch (Exception e) {
Log.e(TAG, path.getAbsolutePath(), e);
}
}
}
return false;
}
@SuppressLint("PackageManagerGetSignatures")
private void init(Context context) {
ByteArrayInputStream stream = null;
try {
PackageManager pm = context.getPackageManager();
String packageName = context.getPackageName();
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
stream = new ByteArrayInputStream(packageInfo.signatures[0].toByteArray());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(stream);
mPublicKey = cert.getPublicKey();
} catch (Exception e) {
throw new TinkerRuntimeException("ShareSecurityCheck init public key fail", e);
} finally {
SharePatchFileUtil.closeQuietly(stream);
}
}
}