一点笔记
转载请注明出处
1. 对jar包中的各文件进行sha1hash,生成manifest对象;
除(
META-INF/MANIFEST.MF
META-INF/CERT.SF
META-INF/CERT.RSA
META-INF/com/android/otacert
"^META-INF/(.*)[.](SF|RSA|DSA)$"
)外
2. 将manifest对象中描述的各文件copy到新jar包中;
3. 如果-w整包签,则将 证书.x509.pem 复制到 META-INF/com/android/otacert;
并在manifest对象中增加META-INF/com/android/otacert的SHA1摘要;
4. 将manifest对象写入新jar包中META-INF/MANIFEST.MF文件;
5. 生成签名文件META-INF/CERT.SF;
内容生成方式:
对manifest中(每一项文件名称、sha1摘要)做sha1摘要, 生成新的Manifest对象
for (entry : OriManifest) {
SHA1(
"Name: entryName\r\n" ## e.g.:(Name: res\xml\xx.xml\r\n)
"SHA1-Digest=ORI-SHA1-Digest\r\n" ## "SHA1-Digest=tIoIjlV7AhAroOM3aDWl+6FaX+Q=\r\n"
"\r\n"
)
}
6. 生成META-INF/CERT.RSA;
PKCS#7格式签名/加密信息:(对CERT.SF进行SHA1withRSA,并将证书.pem附在其中);
7. 如果-w整包签,则在jar/zip文件
找到'End of central directory signature'
(一般zip如果无Comment length时,EOCD标记距尾部22Bytes)
[End of central directory record]格式
Offset Bytes Description[18]
0 4 End of central directory signature | 核心目录结束标记(0x06054b50)
4 2 Number of this disk | 当前磁盘编号
6 2 Disk where central directory starts | 核心目录开始位置的磁盘编号
8 2 Number of central directory records on this disk | 该磁盘上所记录的核心目录数量
10 2 Total number of central directory records | 核心目录结构总数
12 4 Size of central directory (bytes) | 核心目录的大小
16 4 Offset of start of central directory,
relative to start of archive | 核心目录开始位置相对于archive开始的位移
20 2 Comment length (n) 注释长度
22 n Comment 注释内容
在其后写入Archive Comment:
--------------------------------------------------------------------------------------------------------
| 2B | Comment_Length | 2B | 2B | 2B |
--------------------------------------------------------------------------------------------------------
| Comment_Length | ‘signed by SignApk\0’ + (PKCS#7_SIG) | signature_start | \xff\xff | Comment_Length |
--------------------------------------------------------------------------------------------------------
signature_start = Comment_Length - len('signed by SignApk') - 1
(PKCS#7_SIG)是对对整个zip包(从ZIP头到<EOCD.CommentLength>之前)数据生成sha1,
再对sha1用私钥加密生成签名放在公钥证书尾部
整个Comment为PKCS#7格式(类似于CERT.RSA,只不过是对整个zip包数据做签名)
OTA包校验时也是先对ZIP包数据生成sha1,然后从ZIP尾部EOCD中取出Comment中的
最后256(2048bits)签名数据(SHA1WithRSA),用公钥解开再和sha1对比,一致则验证通过
// The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
// the signing tool appends after the signature itself.
RSA_verify(pKeys+i, eocd + eocd_size - 6 - 256, 256, sha1)
========================================================================================
系统APK验证流程:
PackageManagerService :: collectCertificatesLI
PackageParser :: collectCertificates
-- if ((flags&PARSE_IS_SYSTEM) != 0)
// If this package comes from the system image, then we
// can trust it... we'll just use the AndroidManifest.xml
// to retrieve its signatures, not validating all of the files.
for system apk only, just verify AndroidManifest.xml, otherwise, verify all entry except "META-INF/"
JarFile jarFile = new JarFile(pkg);
JarEntry je = jarFile.getJarEntry("AndroidManifest.xml");
jarFile.getInputStream(je)
-- JarFile.getInputStream(ZipEntry) line: 378
-- JarVerifier.readCertificates() line: 258
-- JarVerifier.verifyCertificate(String) line: 330
-- JarVerifier.verify(Attributes, String, byte[], int, int, boolean, boolean) line: 405
1. Use .SF to verify the mainAttributes of the manifest
2. Use .SF to verify the whole manifest.
3. associate signatures with .SF and entries of manifest.
-- JarFile.getInputStream(ZipEntry) line: 395
-- JarVerifier.initEntry(String) line: 213
1. init entry digest method and it's hashbytes.
read inputStream
-- BufferedInputStream.read(byte[], int, int) line: 304
-- JarFile$JarFileInputStream.read(byte[], int, int) line: 119
-- JarVerifier$VerifierEntry.verify() line: 121
<<<
byte[] d = digest.digest();
if (!MessageDigest.isEqual(d, Base64.decode(hash))) {
throw invalidDigest(JarFile.MANIFEST_NAME, name, jarName);
}
verifiedEntries.put(name, certificates);
>>>>
1. hash content of jarEntry, cmp the digest with hashbytes already saved.
if they're identical, associate entry name with certificates.
Certificate[] certs = je.getCertificates();
-- JarEntry.getCertificates() line: 108
-- JarVerifier.getCertificates(String) line: 422
<<<
Certificate[] verifiedCerts = verifiedEntries.get(name);
>>>
------------------------------------------------------------------------------------------------------------------
Source snippet:
String pkg = "/mnt/asec/com.speedsoftware.rootexplorer-1/pkg.apk";
JarFile jarFile = new JarFile(pkg);
JarEntry je = jarFile.getJarEntry("AndroidManifest.xml");
byte[] readBuffer = new byte[8192];
try {
// We must read the stream for the JarEntry to retrieve
// its certificates.
InputStream is = new BufferedInputStream(jarFile.getInputStream(je));
while (is.read(readBuffer, 0, readBuffer.length) != -1) {
// not using
}
is.close();
Certificate[] certs = je.getCertificates();
Log.e(TAG, certs.toString());
} catch (IOException e) {
Slog.w(TAG, "Exception reading " + je.getName() + " in "
+ jarFile.getName(), e);
} catch (RuntimeException e) {
Slog.w(TAG, "Exception reading " + je.getName() + " in "
+ jarFile.getName(), e);
}
} catch (IOException e2) {
e2.printStackTrace();
}