国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

pms包管理服務分析-證書校驗流程

2019-11-09 17:27:53
字體:
來源:轉載
供稿:網友

Apk簽名后,apk文件目錄下生成了一個名為META-INF目錄,里面有三個文件,分別是MANIFEST.MF,CERT.SFCERT.RSA;

其中MANIFEST.MF中記錄的是apk中所有文件的摘要值;CERT.SF中記錄的是對MANIFEST.MF的摘要值,包括整個文件的摘要,還有文件中每一項的摘要;而CERT.RSA中記錄的是對CERT.SF文件的簽名,以及簽名的公鑰。這個目錄下的簽名文件是如何解析和校驗的呢,我們回到pms的scanPackageLI方法。

[/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java]

    /*     *  Scan a package and return the newly parsed package.     *  Returns null in case of errors and the error code is stored in mLastScanError     */    PRivate PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,            long currentTime, UserHandle user) throws PackageManagerException {        if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);        …         // Verify certificates against what was last scanned        collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);        …}

這里調用了collectCertificatesLI方法,傳入pkg以及pkg目錄,繼續分析該方法:

[/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java]

    private void collectCertificatesLI(PackageParser pp, PackageSetting ps, PackageParser.Package pkg, File srcFile, int parseFlags) throws PackageManagerException {        if (ps != null && ps.codePath.equals(srcFile) && ps.timeStamp == srcFile.lastModified()                && !isCompatSignatureUpdateNeeded(pkg) && !isRecoverSignatureUpdateNeeded(pkg)) {            long mSigningKeySetId = ps.keySetData.getProperSigningKeySet();            if (ps.signatures.mSignatures != null && ps.signatures.mSignatures.length != 0                    && mSigningKeySetId != PackageKeySetData.KEYSET_UNASSIGNED) {                pkg.mSignatures = ps.signatures.mSignatures;                KeySetManagerService ksms = mSettings.mKeySetManagerService;                synchronized (mPackages) {                    pkg.mSigningKeys = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);                }                return;            }        }         try {            pp.collectCertificates(pkg, parseFlags);            pp.collectManifestDigest(pkg);        } catch (PackageParserException e) {            throw PackageManagerException.from(e);        }    }

collectCertificatesLI方法中首先判斷當前packages.xml是否存在,如果存在則把之前保存的apk簽名信息取出并賦值給pkg。否則調用PackageParser的collertCertficates和collectManifestDigest方法分別解析手機apk的簽名信息。

繼續分析PackageParser里面的這兩個方法:

[/frameworks/base/services/core/java/com/android/server/pm/PackageParser.java]

    public void collectCertificates(Package pkg, int flags) throws PackageParserException {        pkg.mCertificates = null;        pkg.mSignatures = null;        pkg.mSigningKeys = null;        collectCertificates(pkg, new File(pkg.baseCodePath), flags);        …  }

接著調用collectCertificates的一個重載版本:

    private static void collectCertificates(Package pkg, File apkFile, int flags)            throws PackageParserException {        final String apkPath = apkFile.getAbsolutePath();        StrictJarFile jarFile = null;        try {            jarFile = new StrictJarFile(apkPath);            …

方法的開頭,首先創建了一個StrictJarFile(代碼位于libcore/luni/src/main/java/java/util/jar/StrictJarFile.java,編譯后存在于core.jar文件中)對象,先來看看其構造函數中的內容:

[libcore/luni/src/main/java/java/util/jar/StrictJarFile.java]

    public StrictJarFile(String fileName) throws IOException {        …        try {            HashMap<String, byte[]> metaEntries = getMetaEntries();            this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true);            this.verifier = new JarVerifier(fileName, manifest, metaEntries);        isSigned = verifier.readCertificates() && verifier.isSignedJar();    …}

這里構造了幾個重要的對象。首先,獲得了META-INF目錄下所有文件名及其字節流。然后是構造了一個manifest對象,主要是用來處理對META-INF目錄下MANIFEST.MF文件的操作。接著,構造了一個JarVeirifer(代碼位于libcore/luni/src/main/java /java/util/jar/JarVerifier.java文件中,編譯后存在于core.jar文件中)對象,這個對象主要實現了對Jar文件的驗證工作,非常關鍵,后面的分析中會逐步提到。在構造函數的最后,調用了JarVeirifer.readCertificates函數:

[libcore/luni/src/main/java /java/util/jar/JarVerifier.java]

synchronized boolean readCertificates() {      if (metaEntries.isEmpty()) {          return false;      }        Iterator<String> it = metaEntries.keySet().iterator();      while (it.hasNext()) {          String key = it.next();          if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {              verifyCertificate(key);              it.remove();          }      }      return true;  }

代碼遍歷所有META-INF目錄下的文件,找到以.DSA.RSA或者.EC結尾的文件,以這些名字結尾的文件都是所謂的簽名證書文件。在本例中對應的是META-INF目錄下的CERT.RSA簽名文件。然后調用JarVeirifer.verifyCertificate函數:

[libcore/luni/src/main/java /java/util/jar/JarVerifier.java]

private void verifyCertificate(String certFile) {      String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";      byte[] sfBytes = metaEntries.get(signatureFile);      if (sfBytes == null) {          return;      }      ……      byte[] sBlockBytes = metaEntries.get(certFile);      try {         Certificate[] signerCertChain = JarUtils.verifySignature( new ByteArrayInputStream(sfBytes),                      new ByteArrayInputStream(sBlockBytes));          if (signerCertChain != null) {              certificates.put(signatureFile, signerCertChain);          }      }     ……

函數開頭,首先找到與證書文件同名,但是以.SF結尾的簽名文件,本例中即為META-INF目錄下的CERT.SF文件。然后分別獲得簽名文件CERT.SF和證書文件CERT.RSA的字節流,調用JarUtils(代碼位于libcore/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java文件中,編譯后存在于core.jar文件中)的verifySignature函數,驗證CERT.RSA文件中包含的對CERT.SF文件的簽名是否正確。如果驗證失敗,則會拋出GeneralSecurityException異常;而如果驗證成功,則會返回簽名的證書鏈。回到JarVeirifer.verifyCertificate函數,如果JarUtils.verifySignature驗證失敗拋出異常,被捕獲后會接著向上拋出SecurityException異常;

如果簽名驗證成功的話,會將證書鏈保存在certifcates屬性變量中。而JarVerifier自己的isSignedJar函數,就是判斷一下這個certificates屬性變量是否為空。

boolean isSignedJar() {      return certificates.size() > 0;  }

如果不為空就代表這個Jar是簽過名的,如果為空則代表其沒有簽過名。

繼續分析JarVeirifer.verifyCertificate函數:

 ……  Attributes attributes = new Attributes();  HashMap<String, Attributes> entries = new HashMap<String, Attributes>();  try {      ManifestReader im = new ManifestReader(sfBytes, attributes);      im.readEntries(entries, null);  } catch (IOException e) {      return;  }    if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {      return;  }    boolean createdBySigntool = false;  String createdBy = attributes.getValue("Created-By");  if (createdBy != null) {      createdBySigntool = createdBy.indexOf("signtool") != -1;  }  ……  

函數接下來讀取了簽名文件,也就是META-INF目錄下CERT.SF文件中的內容。CERT.SF文件內容大致如下:

首先判斷了是否有“Signature-Version”屬性,如果沒有的話,直接返回。再下來判斷apk是否是由簽名工具簽的名,判斷條件就是在“Created-By”屬性值內有沒有“signtool”字符串。本例中,簽名版本是“1.0”,并且不是用其它簽名工具簽的名。如果不是用其它工具簽名的話,接下來還會驗證主屬性中是否有“SHA1-Digest-Manifest-Main-Attributes”屬性的值,這個屬性值記錄的是對META-INF目錄下MANIFEST.MF文件內,頭屬性塊的hash值。

……  byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);  if (manifestBytes == null) {      return;  }  ……  if (mainAttributesEnd > 0 && !createdBySigntool) {      String digestAttribute = "-Digest-Manifest-Main-Attributes";      if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {          throw failedVerification(jarName, signatureFile);      }  }  ……

接著調用了JarVerifier.verify對該摘要值進行驗證:

private boolean verify(Attributes attributes, String entry, byte[] data,              int start, int end, boolean ignoreSecondEndline, boolean ignorable) {      for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {          String algorithm = DIGEST_ALGORITHMS[i];          String hash = attributes.getValue(algorithm + entry);          if (hash == null) {              continue;          }            MessageDigest md;          try {              md = MessageDigest.getInstance(algorithm);          } catch (NoSuchAlgorithmException e) {              continue;          }          if (ignoreSecondEndline && data[end - 1] == '/n' && data[end - 2] == '/n') {              md.update(data, start, end - 1 - start);          } else {             md.update(data, start, end - start);          }          byte[] b = md.digest();          byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);          return MessageDigest.isEqual(b, Base64.decode(hashBytes));      }      return ignorable;  }  

JarVerifier.verify函數很簡單,由于不知道到底是用什么算法算出的散列值,所以其會遍歷所有的可能算法。這些算法都預先定義在DIGEST_ALGORITHMS這個JarVerifier內的靜態字符串數組變量中:

private static final String[] DIGEST_ALGORITHMS = new String[] {      "SHA-512",      "SHA-384",      "SHA-256",      "SHA1",  }; 

可以看出,一共支持四種算法,本例中用到的是SHA1摘要算法。變量attributes表示的是一個屬性塊,而變量entry是要在attributes屬性塊中查找的屬性名的一部分,它會與摘要算法的名稱拼接成正真的屬性名。接著會將在屬性塊中,對應屬性名的屬性值取出來,與data數據塊中startend之間的數據,用同樣算法算出的摘要值進行比較,如果一致就返回“true”,不一致則返回“false”。

    ……      String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";      if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {          Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();          while (it.hasNext()) {              Map.Entry<String, Attributes> entry = it.next();              Manifest.Chunk chunk = manifest.getChunk(entry.getKey());              if (chunk == null) {                  return;              }              if (!verify(entry.getValue(), "-Digest", manifestBytes,                      chunk.start, chunk.end, createdBySigntool, false)) {                  throw invalidDigest(signatureFile, entry.getKey(), jarName);              }          }      }      metaEntries.put(signatureFile, null);      signatures.put(signatureFile, entries);  }  

JarVeirifer.verifyCertificate剩下的代碼就很簡單了,會比較MANIFEST.MF文件的整體摘要值和每一個屬性塊的摘要值,與CERT.SF文件中記錄的是否一致。如果都驗證通過的話,會將該簽名文件的信息加到metaEntriessignatures屬性變量中去。所以,在StrictJarFile構造的過程中就已經完成了兩步驗證:一是通過在CERT.RSA文件中記錄的簽名信息,驗證了CERT.SF沒有被篡改過;二是通過CERT.SF文件中記錄的摘要值,驗證了MANIFEST.MF沒有被修改過。

回到PackageParser的collectCertificates方法中:

[/frameworks/base/services/core/java/com/android/server/pm/PackageParser.java]

……  final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);  if (manifestEntry == null) {     throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,"Package " + apkPath + " has no manifest");  }    final List<ZipEntry> toVerify = new ArrayList<>();  toVerify.add(manifestEntry);    if ((flags & PARSE_IS_SYSTEM) == 0) {      final Iterator<ZipEntry> i = jarFile.iterator();      while (i.hasNext()) {          final ZipEntry entry = i.next();            if (entry.isDirectory()) continue;          if (entry.getName().startsWith("META-INF/")) continue;          if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;            toVerify.add(entry);      }  }  ……

接下來的代碼主要是用來確定,到底哪些文件需要進行驗證。AndroidManifest.xml無論如何都要驗證。如果不是系統,也就是普通的應用程序安裝,必須要驗證除去位于META-INF目錄下所有文件之外的所有剩下的文件。

……  for (ZipEntry entry : toVerify) {      final Certificate[][] entryCerts = loadCertificates(jarFile, entry);      if (ArrayUtils.isEmpty(entryCerts)) {          throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,                      "Package " + apkPath + " has no certificates at entry " + entry.getName());      }      final Signature[] entrySignatures = convertToSignatures(entryCerts);      ……

接著是逐項驗證前面羅列出的apk中的各個文件。對每個文件,都接著調用了PackageParser.loadCertificates函數:

private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry) throws ackageParserException{    InputStream is = null;      try {          is = jarFile.getInputStream(entry);          readFullyIgnoringContents(is);          return jarFile.getCertificateChains(entry);       …

方法內對apk內的文件創建了一個輸入流,并且通過函數PackageParser.readFullyIgnoringContents全讀了一遍,而且通過函數名可以看出,具體讀出什么內容并不重要。我們先來看看StrictJarFile.getInputStream函數:

public InputStream getInputStream(ZipEntry ze) {      final InputStream is = getZipInputStream(ze);        if (isSigned) {          JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());          if (entry == null) {              return is;          }            return new JarFile.JarFileInputStream(is, ze.getSize(), entry);      }        return is;  }  

重點要關注兩個函數調用,一是JarVerifier.initEntry,二是JarFile.JarFileInputStream

initEntry函數主要的用途就是構造一個JarVerifer.VerifierEntry對象:要構造這個對象,必須事先準備好參數。第一個參數很簡單,就是要驗證的文件名,直接將name傳進來就好了。第二個參數是計算摘要的對象,可以通過MessageDigest.getInstance獲得,不過要先告知到底要用哪個摘要算法,同樣也是通過查看MANIFEST.MF文件中對應名字的屬性值來決定的。本例中的MANIFEST.MF文件格式大致如下:

所以可以知道所用的摘要算法是SHA1。第三個參數是對應文件的摘要值,這是通過讀取MANIFEST.MF文件獲得的。第四個參數是證書鏈,即對該apk文件簽名的所有證書鏈信息。為什么是二維數組呢?這是因為Android允許用多個證書對apk進行簽名,但是它們的證書文件名必須不同。最后一個參數是已經驗證過的文件列表,VerifierEntry在完成了對指定文件的摘要驗證之后會將該文件的信息加到其中。

通過digest就可以算出apk內指定文件的真實摘要值。而記錄在MANIFEST.MF文件中對應該文件的摘要值,也在構造JarVerifier.VerifierEntry時傳遞給了hash變量。不過這個hash值是經過Base64編碼的。所以在比較之前,必須通過Base64解碼。如果不一致的話,會拋出SecurityException異常:

至此,最后一步驗證,即apk內所有文件的摘要值要和在MANIFEST.MF文件中記錄的一致,也已經完成了。這還沒完,PackageParser.collectCertificates還要接著驗證apk文件中的每個文件對應的簽名要和第一個文件一致:

……  if (pkg.mCertificates == null) {      pkg.mCertificates = entryCerts;      pkg.mSignatures = entrySignatures;      pkg.mSigningKeys = new ArraySet<PublicKey>();      for (int i=0; i < entryCerts.length; i++) {          pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());      }  } ……

最后將證書信息以Certificates, mSignatures, SigningKeys的形式保存到pkg中,并最終寫入packages.xml供下次開機使用。

到這里,apk安裝時的簽名驗證過程都已經分析完了,來總結一下:

1. 所有有關apk文件的簽名驗證工作都是在JarVerifier里面做的,一共分成三步;

2. JarVeirifer.verifyCertificate主要做了兩步。首先,使用證書文件(在META-INF目錄下,以.DSA.RSA或者.EC結尾的文件)檢驗簽名文件(在META-INF目錄下,和證書文件同名,但擴展名為.SF的文件)是沒有被修改過的。然后,使用簽名文件,檢驗MANIFEST.MF文件中的內容也沒有被篡改過;

3. JarVerifier.VerifierEntry.verify做了最后一步驗證,即保證apk文件中包含的所有文件,對應的摘要值與MANIFEST.MF文件中記錄的一致。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 民和| 新民市| 北海市| 南丹县| 昭觉县| 当雄县| 尼勒克县| 新建县| 建水县| 大同县| 专栏| 香港| 正定县| 江都市| 麻栗坡县| 双峰县| 于都县| 涞水县| 庆城县| 青海省| 吴桥县| 大同市| 周口市| 讷河市| 宜阳县| 宣武区| 敖汉旗| 保定市| 大邑县| 江油市| 阳山县| 扎兰屯市| 沈丘县| 康乐县| 邻水| 增城市| 林州市| 烟台市| 辉县市| 定远县| 赫章县|