最近公司有个需求需要搭建服务器,通过对终端系统进行网络升级。对相关知识进行整理。
android中OTA进行升级,首先需要使用源码中OTA升级包打包工具: build/tools/releasetools/ota_from_target_files 生成OTA包。包有两类:整体完全升级包和增量升级包。
在android系统编译环境下,在终端下使用make otapackage生成。
android平台使用了提供了android.os.RecoverySystem工具类支持升级,需要添加android.Manifest.permission#REBOOT permission, 而REBOOT权限需要添加android:shareUserId="android.uid.system",进行系统签名,赋予升级平台权限后,才能支持升级,或是升级app在系统源码编译环境中编译后才能使用该功能。
使用RecoverySystem工具类,需要两个步骤。
1、 进行签名验证,public static void verifyPackage(File packageFile,
ProgressListener listener,
File deviceCertsZipFile) throws IOException, GeneralSecurityException
* @param packageFile 待验证的升级包
* @param listener 升级进度监听器,验证的进度
* @param deviceCertsZipFile 验证证书文件,如果为null, 系统默认的是 "/system/etc/security/otacerts.zip"。
private static final File DEFAULT_KEYSTORE = new File("/system/etc/security/otacerts.zip")
/** 通过/cache/recovery/command,与系统通信,接收升级命令指令,详细可以查看源码: bootable/recovery/recovery.c. */
private static File RECOVERY_DIR = new File("/cache/recovery");
private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
//验证OTA升级包方法
public static void verifyPackage(File packageFile,
ProgressListener listener,
File deviceCertsZipFile)
throws IOException, GeneralSecurityException {
long fileLen = packageFile.length();
RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
try {
int lastPercent = 0;
long lastPublishTime = System.currentTimeMillis();
if (listener != null) {
listener.onProgress(lastPercent);
}
raf.seek(fileLen - 6);
byte[] footer = new byte[6];
raf.readFully(footer);
//升级包是否含有系统签名信息
if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
throw new SignatureException("no signature in file (no footer)");
}
int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
//签名的起始位置
int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
byte[] eocd = new byte[commentSize + 22];
raf.seek(fileLen - (commentSize + 22));
raf.readFully(eocd);
// Check that we have found the start of the
// end-of-central-directory record.
if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
throw new SignatureException("no signature in file (bad footer)");
}
for (int i = 4; i < eocd.length-3; ++i) {
if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
throw new SignatureException("EOCD marker found after start of EOCD");
}
}
// The following code is largely copied from
// JarUtils.verifySignature(). We could just *call* that
// method here if that function didn't read the entire
// input (ie, the whole OTA package) into memory just to
// compute its message digest.
BerInputStream bis = new BerInputStream(
new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
SignedData signedData = info.getSignedData();
if (signedData == null) {
throw new IOException("signedData is null");
}
List<Certificate> encCerts = signedData.getCertificates();
if (encCerts.isEmpty()) {
throw new IOException("encCerts is empty");
}
// Take the first certificate from the signature (packages
// should contain only one).
Iterator<Certificate> it = encCerts.iterator();
X509Certificate cert = null;
if (it.hasNext()) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream is = new ByteArrayInputStream(it.next().getEncoded());
cert = (X509Certificate) cf.generateCertificate(is);
} else {
throw new SignatureException("signature contains no certificates");
}
//获取到包中所有的签名证书信息
List<SignerInfo> sigInfos = signedData.getSignerInfos();
SignerInfo sigInfo;
if (!sigInfos.isEmpty()) {
sigInfo = (SignerInfo)sigInfos.get(0);
} else {
throw new IOException("no signer infos!");
}
// Check that the public key of the certificate contained
// in the package equals one of our trusted public keys.
HashSet<X509Certificate> trusted = getTrustedCerts(
deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
PublicKey signatureKey = cert.getPublicKey();
boolean verified = false;
//签名验证key比较
for (X509Certificate c : trusted) {
if (c.getPublicKey().equals(signatureKey)) {
verified = true;
break;
}
}
if (!verified) {
throw new SignatureException("signature doesn't match any trusted key");
}
// The signature cert matches a trusted key. Now verify that
// the digest in the cert matches the actual file data.
// The verifier in recovery only handles SHA1withRSA and
// SHA256withRSA signatures. SignApk chooses which to use
// based on the signature algorithm of the cert:
//
// "SHA256withRSA" cert -> "SHA256withRSA" signature
// "SHA1withRSA" cert -> "SHA1withRSA" signature
// "MD5withRSA" cert -> "SHA1withRSA" signature (for backwards compatibility)
// any other cert -> SignApk fails
//
// Here we ignore whatever the cert says, and instead use
// whatever algorithm is used by the signature.
String da = sigInfo.getDigestAlgorithm();
String dea = sigInfo.getDigestEncryptionAlgorithm();
String alg = null;
if (da == null || dea == null) {
// fall back to the cert algorithm if the sig one
// doesn't look right.
alg = cert.getSigAlgName();
} else {
alg = da + "with" + dea;
}
Signature sig = Signature.getInstance(alg);
sig.initVerify(cert);
// 对整个升级包开始验证
long toRead = fileLen - commentSize - 2;
long soFar = 0;
raf.seek(0);
byte[] buffer = new byte[4096];
boolean interrupted = false;
while (soFar < toRead) {
interrupted = Thread.interrupted();
if (interrupted) break;
int size = buffer.length;
if (soFar + size > toRead) {
size = (int)(toRead - soFar);
}
int read = raf.read(buffer, 0, size);
sig.update(buffer, 0, read);
soFar += read;
if (listener != null) {
long now = System.currentTimeMillis();
int p = (int)(soFar * 100 / toRead);
if (p > lastPercent &&
now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
lastPercent = p;
lastPublishTime = now;
//返回验证进度
listener.onProgress(lastPercent);
}
}
}
// 验证进度完毕
if (listener != null) {
listener.onProgress(100);
}
if (interrupted) {
throw new SignatureException("verification was interrupted");
}
if (!sig.verify(sigInfo.getEncryptedDigest())) {
throw new SignatureException("signature digest verification failed");
}
} finally {
raf.close();
}
}
2、通过系统签名验证通过后,进行安装
/**
* Reboots the device in order to install the given update
* package.
* Requires the {@link android.Manifest.permission#REBOOT} permission.
*
* @param context the Context to use
* @param packageFile OTA升级包路径 ,必须在可恢复的挂载区,一般在/cache and /data 都是安全的
*/
public static void installPackage(Context context, File packageFile)
throws IOException {
String filename = packageFile.getCanonicalPath();
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
final String filenameArg = "--update_package=" + filename;
final String localeArg = "--locale=" + Locale.getDefault().toString();
//调用bootCommand进行升级包安装
bootCommand(context, filenameArg, localeArg);
}
本文介绍了Android系统的OTA升级过程,包括使用源码工具生成OTA包、系统验证升级包的流程,涉及android.os.RecoverySystem工具类,需要REBOOT权限及系统签名。升级过程中,首先进行签名验证,然后通过RecoverySystem安装升级包。
2万+

被折叠的 条评论
为什么被折叠?



