一、分析OTA升级包校验过程需要先了解下zip文件结构
参考文档:
https://ctf-wiki.org/en/misc/archive/zip/
https://www.cnblogs.com/li-sx/p/17531186.html
https://www.cnblogs.com/Zer0o/p/17174911.html
zip文件结构如下:
| 压缩源文件数据区 | 核心目录 | 目录结束 |
|---|---|---|
| local file header + file data + data descriptor | central directory | end of central directory record |
1.1 压缩源文件数据区
-
local file header:文件头用于标识该文件的开始,记录了该压缩文件的信息,文件头标识由固定值50 4B 03 04开头,也是ZIP的文件头的重要标志 -
file data:文件数据记录了相应压缩文件的数据 -
data descriptor:数据描述符用于标识该文件压缩结束,该结构只有在相应的local file header中通用标记字段的第3 bit设为1时才会出现,紧接在压缩文件源数据后
1.2 核心目录
记录了压缩文件的目录信息,在这个数据区中每一条纪录对应在压缩源文件数据区中的一条数据
| Offset | Bytes | Description | 译 |
|---|---|---|---|
| 0 | 4 | Central directory file header signature = 0x02014b50 | 核心目录文件 header 标识 =(0x02014b50) |
| 4 | 2 | Version made by | 压缩所用的 pkware 版本 |
| 6 | 2 | Version needed to extract (minimum) | 解压所需 pkware 的最低版本 |
| 8 | 2 | General purpose bit flag | 通用位标记伪加密 |
| 10 | 2 | Compression method | 压缩方法 |
| 12 | 2 | File last modification time | 文件最后修改时间 |
| 14 | 2 | File last modification date | 文件最后修改日期 |
| 16 | 4 | CRC-32 | CRC-32 校验码 |
| 20 | 4 | Compressed size | 压缩后的大小 |
| 24 | 4 | Uncompressed size | 未压缩的大小 |
| 28 | 2 | File name length (n) | 文件名长度 |
| 30 | 2 | Extra field length (m) | 扩展域长度 |
| 32 | 2 | File comment length (k) | 文件注释长度 |
| 34 | 2 | Disk number where file starts | 文件开始位置的磁盘编号 |
| 36 | 2 | Internal file attributes | 内部文件属性 |
| 38 | 4 | External file attributes | 外部文件属性 |
| 42 | 4 | relative offset of local header | 本地文件头的相对位移 |
| 46 | n | File name | 目录文件名 |
| 46+n | m | Extra field | 扩展域 |
| 46+n+m | k | File comment | 文件注释内容 |
1.3 目录结束
目录结束标识存在于整个归档包的结尾,用于标记压缩的目录数据的结束。每个压缩文件必须有且只有一个 EOCD 记录
ZIP 文件头 50 4B 03 04 0A 00 00 00
ZIP 文件尾 50 4B 05 06 00 00 00 00 + 其他字符
二、校验接口如下:
public static void verifyPackage(File packageFile,ProgressListener listener,File deviceCertsZipFile) throws IOException, GeneralSecurityException {
final long fileLen = packageFile.length();
final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
try {
final long startTimeMillis = System.currentTimeMillis();
if (listener != null) {
listener.onProgress(0);
}
raf.seek(fileLen - 6);//定位到倒数第六个Byte
byte[] footer = new byte[6];
raf.readFully(footer);//读取最后6Byte到footer数组
if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {//默认的这两位是空,别问为什么,我也不清楚为什么这么定义,如果这两位不为0xff则认为文件脚校验失败
throw new SignatureException("no signature in file (no footer)");
}
final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);//签名开始位置
byte[] eocd = new byte[commentSize + 22];//zip文件结束标记,50 4B 05 06
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");
}
}
PKCS7 block = new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
// Take the first certificate from the signature (packages
// should contain only one).
X509Certificate[] certificates = block.getCertificates();
if (certificates == null || certificates.length == 0) {
throw new SignatureException("signature contains no certificates");
}
X509Certificate cert = certificates[0];
PublicKey signatureKey = cert.getPublicKey();//获取X.509 公共密钥
SignerInfo[] signerInfos = block.getSignerInfos();
if (signerInfos == null || signerInfos.length == 0) {
throw new SignatureException("signature contains no signedData");
}
SignerInfo signerInfo = signerInfos[0];
// Check that the public key of the certificate contained
// in the package equals one of our trusted public keys.
boolean verified = false;
HashSet<X509Certificate> trusted = getTrustedCerts(deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
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.
raf.seek(0);
final ProgressListener listenerForInner = listener;
SignerInfo verifyResult = block.verify(signerInfo, new InputStream() {
// The signature covers all of the OTA package except the
// archive comment and its 2-byte length.
long toRead = fileLen - commentSize - 2;
long soFar = 0;
int lastPercent = 0;
long lastPublishTime = startTimeMillis;
@Override
public int read() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (soFar >= toRead) {
return -1;
}
if (Thread.currentThread().isInterrupted()) {
return -1;
}
int size = len;
if (soFar + size > toRead) {
size = (int)(toRead - soFar);
}
int read = raf.read(b, off, size);
soFar += read;
if (listenerForInner != null) {
long now = System.currentTimeMillis();
int p = (int)(soFar * 100 / toRead);
if (p > lastPercent && now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
lastPercent = p;
lastPublishTime = now;
listenerForInner.onProgress(lastPercent);
}
}
return read;
}
}
final boolean interrupted = Thread.interrupted();
if (listener != null) {
listener.onProgress(100);
}
if (interrupted) {
throw new SignatureException("verification was interrupted");
}
if (verifyResult == null) {
throw new SignatureException("signature digest verification failed");
}
} finally {
raf.close();
}
// Additionally verify the package compatibility.
if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
throw new SignatureException("package compatibility verification failed");
}
}

2万+

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



