RecoverySystem升级包校验逻辑

一、分析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 descriptorcentral directoryend of central directory record

1.1 压缩源文件数据区

  1. local file header :文件头用于标识该文件的开始,记录了该压缩文件的信息,文件头标识由固定值 50 4B 03 04 开头,也是 ZIP 的文件头的重要标志

  2. file data :文件数据记录了相应压缩文件的数据

  3. data descriptor :数据描述符用于标识该文件压缩结束,该结构只有在相应的 local file header 中通用标记字段的第 3 bit 设为 1 时才会出现,紧接在压缩文件源数据后

1.2 核心目录

​ 记录了压缩文件的目录信息,在这个数据区中每一条纪录对应在压缩源文件数据区中的一条数据

OffsetBytesDescription
04Central directory file header signature = 0x02014b50核心目录文件 header 标识 =(0x02014b50)
42Version made by压缩所用的 pkware 版本
62Version needed to extract (minimum)解压所需 pkware 的最低版本
82General purpose bit flag通用位标记伪加密
102Compression method压缩方法
122File last modification time文件最后修改时间
142File last modification date文件最后修改日期
164CRC-32CRC-32 校验码
204Compressed size压缩后的大小
244Uncompressed size未压缩的大小
282File name length (n)文件名长度
302Extra field length (m)扩展域长度
322File comment length (k)文件注释长度
342Disk number where file starts文件开始位置的磁盘编号
362Internal file attributes内部文件属性
384External file attributes外部文件属性
424relative offset of local header本地文件头的相对位移
46nFile name目录文件名
46+nmExtra field扩展域
46+n+mkFile 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");
    }
}
                                     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值