signapk流程分析

一点笔记

转载请注明出处


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();
}
 

你可能感兴趣的:(packagemanager,signapk,android签名,recovery校验,certi)