-
通过函数重载,最终的verify()接受三个参数:apk的二进制文件数据对象,一个SignatureInfo对象,是否检查完整性的bool。并返回一个VerifiedSigner的对象
-
其中传递的SignatureInfo对象,是由ApkSigningBlockUtils类中的findSignature()获得
-
可见plsCertsNoVerifyOnlyCerts()与verify()的区别是完整性校验的bool值不同,最终调用的函数殊途同归,这个值最终会判断是否跳过位于ApkSigningBlockUtils类中的verifyIntegrity()方法的校验
所以为了继续分析这个verify()我们先要知道SignatureInfo这个对象是什么
ApkSigningBlockUtils.findSignature()
frameworks/base/core/java/android/util/apk/ApkSigningBlockUtils.java
static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
throws IOException, SignatureNotFoundException {
// Find the ZIP End of Central Directory (EoCD) record.
Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
ByteBuffer eocd = eocdAndOffsetInFile.first;
long eocdOffset = eocdAndOffsetInFile.second;
if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
throw new SignatureNotFoundException(“ZIP64 APK not supported”);
}
// Find the APK Signing Block. The block immediately precedes the Central Directory.
long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
findApkSigningBlock(apk, centralDirOffset);
ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
// Find the APK Signature Scheme Block inside the APK Signing Block.
ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
blockId);
return new SignatureInfo(
apkSignatureSchemeBlock,
apkSigningBlockOffset,
centralDirOffset,
eocdOffset,
eocd);
}
到这里就已经真的开始对整个apk文件进行检查了,通过获得apk尾部的EOCD块中获得中央目录的偏移,由中央目录开始处往上找24个字节,获取8个字节的小端长整型,这个值即为签名块的长度减8。不过这个长度的值是从签名块开始到中央目录开始,所以这里要从中央目录开始处往前跳转找到签名块的偏移。这里主要看到findApkSigningBlock(),findApkSignatureSchemeBlock()这两个函数
ApkSigningBlockUtils.findApkSigningBlock()
frameworks/base/core/java/android/util/apk/ApkSigningBlockUtils.java
private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
static Pair<ByteBuffer, Long> findApkSigningBlock(
RandomAccessFile apk, long centralDirOffset)
throws IOException, SignatureNotFoundException {
// FORMAT:
// OFFSET DATA TYPE DESCRIPTION
// * @+0 bytes uint64: size in bytes (excluding this field)
// * @+8 bytes payload
// * @-24 bytes uint64: size in bytes (same as the one above)
// * @-16 bytes uint128: magic
if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
throw new SignatureNotFoundException(
"APK too small for APK Signing Block. ZIP Central Directory offset: "
- centralDirOffset);
}
// Read the magic and offset in file from the footer section of the block:
// * uint64: size of block
// * 16 bytes: magic
ByteBuffer footer = ByteBuffer.allocate(24);
footer.order(ByteOrder.LITTLE_ENDIAN);
apk.seek(centralDirOffset - footer.capacity());
apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
|| (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
throw new SignatureNotFoundException(getLengthPrefixedSlice
“No APK Signing Block before ZIP Central Directory”);
}
// Read and compare size fields
long apkSigBlockSizeInFooter = footer.getLong(0);
if ((apkSigBlockSizeInFooter < footer.capacity())
|| (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
throw new SignatureNotFoundException(
"APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
}
int totalSize = (int) (apkSigBlockSizeInFooter + 8);
long apkSigBlockOffset = centralDirOffset - totalSize;
if (apkSigBlockOffset < 0) {
throw new SignatureNotFoundException(
"APK Signing Block offset out of range: " + apkSigBlockOffset);
}
ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
apk.seek(apkSigBlockOffset);
apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
throw new SignatureNotFoundException(
"APK Signing Block sizes in header and footer do not match: "
- apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
}
return Pair.create(apkSigBlock, apkSigBlockOffset);
}
-
通过中心目录偏移往上16字节找到apk签名的魔术字APK Sig Block 42
-
在往上8字节找到签名块的长度,拿到的长度加8字节为整个签名块的长度
-
从中心目录往上找整个签名块的长度,即为签名块的开始位置
-
函数返回整个签名块的数据,以及签名块开始的偏移
整个签名块就是从apkSigBlockSizeInHeader到APK_SIG_BLOCK_MAGIC_HI的所有数据
ApkSigningBlockUtils.findApkSignatureSchemeBlock()
frameworks/base/core/java/android/util/apk/ApkSigningBlockUtils.java
static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
throws SignatureNotFoundException {
checkByteOrderLittleEndian(apkSigningBlock);
// FORMAT:
// OFFSET DATA TYPE DESCRIPTION
// * @+0 bytes uint64: size in bytes (excluding this field)
// * @+8 bytes pairs
// * @-24 bytes uint64: size in bytes (same as the one above)
// * @-16 bytes uint128: magic
ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
int entryCount = 0;
while (pairs.hasRemaining()) {
entryCount++;
if (pairs.remaining() < 8) {
throw new SignatureNotFoundException(
“Insufficient data to read size of APK Signing Block entry #” + entryCount);
}
long lenLong = pairs.getLong();
if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
throw new SignatureNotFoundException(
“APK Signing Block entry #” + entryCount
- " size out of range: " + lenLong);
}
int len = (int) lenLong;
int nextEntryPos = pairs.position() + len;
if (len > pairs.remaining()) {
throw new SignatureNotFoundException(
“APK Signing Block entry #” + entryCount + " size out of range: " + len
- ", available: " + pairs.remaining());
}
int id = pairs.getInt();
if (id == blockId) {
return getByteBuffer(pairs, len - 4);
}
pairs.position(nextEntryPos);
}
throw new SignatureNotFoundException(
“No block with ID " + blockId + " in APK Signing Block.”);
}
-
这个函数接受之前解析出的签名部分的数据,通过sliceFromTo剪掉前8个字节和后24个字节
-
利用剩下的部分再次获取八个字节的长度数据,代表着第一个签名部分的长度,一般也只有一个
-
继续获取一个四个字节的id,与blockID进行比较,这个参数来源于ApkSignatureSchemeV3Verifier.verify()函数中传入的常量:APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0 (这个签名块的标记v3v2不同)
-
最终返回的是剪掉签名长度和签名标记数据块
SignatureInfo.SignatureInfo()
frameworks/base/core/java/android/util/apk/SignatureInfo.java
class SignatureInfo {
/** Contents of APK Signature Scheme v2 block. */
public final ByteBuffer signatureBlock;
/** Position of the APK Signing Block in the file. */
public final long apkSigningBlockOffset;
/** Position of the ZIP Central Directory in the file. */
public final long centralDirOffset;
/** Position of the ZIP End of Central Directory (EoCD) in the file. */
public final long eocdOffset;
/** Contents of ZIP End of Central Directory (EoCD) of the file. */
public final ByteBuffer eocd;
SignatureInfo(ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset,
long eocdOffset, ByteBuffer eocd) {
this.signatureBlock = signatureBlock;
this.apkSigningBlockOffset = apkSigningBlockOffset;
this.centralDirOffset = centralDirOffset;
this.eocdOffset = eocdOffset;
this.eocd = eocd;
}
}
最终获取到SignatureInfo包含如下成员:
-
APK包含签名信息的数据(signatureBlock)
-
APK签名数据块的偏移(apkSigningBlockOffset)
-
APK中央目录偏移(centralDirOffset)
-
APK中央目录结束块偏移(eocdOffset)
-
APK中央目录结束块数据(eocd)
其中重要的就是signatureBlock,就是刚才返回的apkSignatureSchemeBlock,现在可以回到ApkSignatureSchemeV3Verifier.verify()函数
ApkSignatureSchemeV3Verifier.verify()
frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
private static VerifiedSigner verify(
RandomAccessFile apk,
SignatureInfo signatureInfo,
boolean doVerifyIntegrity) throws SecurityException, IOException {
int signerCount = 0;
Map<Integer, byte[]> contentDigests = new ArrayMap<>();
VerifiedSigner result = null;
CertificateFactory certFactory;
try {
certFactory = CertificateFactory.getInstance(“X.509”);
} catch (CertificateException e) {
throw new RuntimeException(“Failed to obtain X.509 CertificateFactory”, e);
}
ByteBuffer signers;
try {
signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
} catch (IOException e) {
throw new SecurityException(“Failed to read list of signers”, e);
}
while (signers.hasRemaining()) {
try {
ByteBuffer signer = getLengthPrefixedSlice(signers);
result = verifySigner(signer, contentDigests, certFactory);
signerCount++;
} catch (PlatformNotSupportedException e) {
// this signer is for a different platform, ignore it.
continue;
} catch (IOException | BufferUnderflowException | SecurityException e) {
throw new SecurityException(
“Failed to parse/verify signer #” + signerCount + " block",
e);
}
}
if (signerCount < 1 || result == null) {
throw new SecurityException(“No signers found”);
}
if (signerCount != 1) {
throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
- “multiple signers found.”);
}
if (contentDigests.isEmpty()) {
throw new SecurityException(“No content digests found”);
}
if (doVerifyIntegrity) {
ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
}
if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
result.verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
verityDigest, apk.length(), signatureInfo);
}
return result;
}
-
这里首先生成了一个certFactory对象,证书标准为X.509
-
构造一个空的contentDigests,准备存放签名块中的完整性校验信息
-
通过getLengthPrefixedSlice()函数剪掉signatureBlock(刚才获得的签名数据块)两个四字节的长度数据,获得signer数据块
这里我们发现当我们尝试获取一个大数据块中的小数据块时候总是有如下的代码写法:
try {
signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
} catch (IOException e) {}
while (signers.hasRemaining()) {
try {
ByteBuffer signer = getLengthPrefixedSlice(signers);
} catch (PlatformNotSupportedException e) {}
}
这其实是一种带长度前缀的数据块的构造方法,而且是为了一个大块下可以包含多个字块。第一个try是通过块前长度获取整个块,第二个在循环里的try是通过每一个字块长度获得每一个字块,但是这里一般签名块不存在并列,所以一般早签名块前就会有两个长度标记,第一个比第二个数值大4。
最终将signer部分数据传入verifySigner()函数
ApkSignatureSchemeV3Verifier.verifySigner()
这个函数虽然比较长,但是这里正是校验签名真正的实现部分,所以我们分段来分析:
frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
private static VerifiedSigner verifySigner(
ByteBuffer signerBlock,
Map<Integer, byte[]> contentDigests,
CertificateFactory certFactory)
throws SecurityException, IOException, PlatformNotSupportedException {
ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
int minSdkVersion = signerBlock.getInt();
int maxSdkVersion = signerBlock.getInt();
if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
// this signature isn’t meant to be used with this platform, skip it.
throw new PlatformNotSupportedException(
"Signer not supported by this platform "
-
"version. This platform: " + Build.VERSION.SDK_INT
-
", signer minSdkVersion: " + minSdkVersion
-
", maxSdkVersion: " + maxSdkVersion);
}
ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
这里将传入的signer部分数据继续拆分,主要是三个部分:
-
signedData
-
signatures
-
publicKeyBytes
具体如下:
回到函数继续分析:
int signatureCount = 0;
int bestSigAlgorithm = -1;
byte[] bestSigAlgorithmSignatureBytes = null;
List signaturesSigAlgorithms = new ArrayList<>();
while (signatures.hasRemaining()) {
signatureCount++;
try {
ByteBuffer signature = getLengthPrefixedSlice(signatures);
if (signature.remaining() < 8) {
throw new SecurityException(“Signature record too short”);
}
int sigAlgorithm = signature.getInt();
signaturesSigAlgorithms.add(sigAlgorithm);
if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
continue;
}
if ((bestSigAlgorithm == -1)
|| (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
bestSigAlgorithm = sigAlgorithm;
bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
}
} catch (IOException | BufferUnderflowException e) {
throw new SecurityException(
“Failed to parse signature record #” + signatureCount,
e);
}
}
if (bestSigAlgorithm == -1) {
if (signatureCount == 0) {
throw new SecurityException(“No signatures found”);
} else {
throw new SecurityException(“No supported signatures found”);
}
}
这段就是继续拆分signatures块,分出四个字节的sigAlgorithm与加密过的hash值bestSigAlgorithmSignatureBytes
String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
boolean sigVerified;
try {
PublicKey publicKey =
KeyFactory.getInstance(keyAlgorithm)
.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
sig.initVerify(publicKey);
if (jcaSignatureAlgorithmParams != null) {
sig.setParameter(jcaSignatureAlgorithmParams);
}
sig.update(signedData);
sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
| InvalidAlgorithmParameterException | SignatureException e) {
throw new SecurityException(
“Failed to verify " + jcaSignatureAlgorithm + " signature”, e);
}
if (!sigVerified) {
throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
}
// Signature over signedData has verified.
用最后的公钥,中间的hash,验证前面的SignatureData:即公钥解密中间的hash,并计算SignatureData的hash进行比对
byte[] contentDigest = null;
signedData.clear();
ByteBuffer digests = getLengthPrefixedSlice(signedData);
List digestsSigAlgorithms = new ArrayList<>();
int digestCount = 0;
while (digests.hasRemaining()) {
digestCount++;
try {
ByteBuffer digest = getLengthPrefixedSlice(digests);
if (digest.remaining() < 8) {
throw new IOException(“Record too short”);
}
int sigAlgorithm = digest.getInt();
digestsSigAlgorithms.add(sigAlgorithm);
if (sigAlgorithm == bestSigAlgorithm) {
contentDigest = readLengthPrefixedByteArray(digest);
}
} catch (IOException | BufferUnderflowException e) {
throw new IOException(“Failed to parse digest record #” + digestCount, e);
}
}
if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
throw new SecurityException(
“Signature algorithms don’t match between digests and signatures records”);
}
int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
if ((previousSignerDigest != null)
&& (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
throw new SecurityException(
getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
- " contents digest does not match the digest specified by a preceding signer");
}
ByteBuffer certificates = getLengthPrefixedSlice(signedData);
List certs = new ArrayList<>();
int certificateCount = 0;
while (certificates.hasRemaining()) {
certificateCount++;
byte[] encodedCert = readLengthPrefixedByteArray(certificates);
X509Certificate certificate;
try {
certificate = (X509Certificate)
certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
} catch (CertificateException e) {
throw new SecurityException(“Failed to decode certificate #” + certificateCount, e);
}
certificate = new VerbatimX509Certificate(
certificate, encodedCert);
certs.add(certificate);
}
if (certs.isEmpty()) {
throw new SecurityException(“No certificates listed”);
}
X509Certificate mainCertificate = certs.get(0);
byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
throw new SecurityException(
“Public key mismatch between certificate and signature record”);
}
int signedMinSDK = signedData.getInt();
if (signedMinSDK != minSdkVersion) {
throw new SecurityException(
“minSdkVersion mismatch between signed and unsigned in v3 signer block.”);
}
int signedMaxSDK = signedData.getInt();
if (signedMaxSDK != maxSdkVersion) {
throw new SecurityException(
“maxSdkVersion mismatch between signed and unsigned in v3 signer block.”);
}
ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
}
继续拆分块,并在拆分过程中校验数据是否匹配:
| singer | |
| — | — |
| signedDataLength (8Byte) | |
| digestsLength (4Byte) | |
| digestLength (4Byte) | |
| sigAlgorithm (4Byte) | |
| contentDigestLength (4Byte) | |
| contentDigest (Byte[]) | |
| certificatesLength (4Byte) | |
| encodedCertLength (4Byte) | |
| encodedCert (byte[]) | |
| signedMinSDK (4Byte) | |
| signedMaxSDK (4Byte) | |
| additionalAttrsLength (4Byte) | |
| additionalAttrs | <-v3版本签名新特性 |
| minSdkVersion (4Byte) | |
| maxSdkVersion (4Byte) | |
| signaturesLength (4Byte) | |
| signatureLength (4Byte) | |
| sigAlgorithm (4Byte) | |
| bestSigAlgorithmSignatureBytesLength (4Byte) | |
| bestSigAlgorithmSignatureBytes (Byte[]) | |
| publicKeyBytesLength(8Byte) | |
| publicKeyBytes(byte[]) | |
这里解析出证书,添加到certs列表里,并做了一系列的校验,校验signedData中解析的数据,与其他块中的数据是否相匹配:
-
signedData块中的sigAlgorithm,与signatures块中的sigAlgorithm
-
signedMinSDK与minSdkVersion
-
signedMaxSDK与maxSdkVersion
-
encodedCert解析出的公钥与整个签名块尾部个公钥
其中拆分出的additionalAttrs就是v3版本中新添加的一个数据块,继续下一个函数
ApkSignatureSchemeV3Verifier.verifyAdditionalAttributes()
frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
List certs, CertificateFactory certFactory) throws IOException {
X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
VerifiedProofOfRotation por = null;
while (attrs.hasRemaining()) {
ByteBuffer attr = getLengthPrefixedSlice(attrs);
if (attr.remaining() < 4) {
throw new IOException("Remaining buffer too short to contain additional attribute "
- "ID. Remaining: " + attr.remaining());
}
int id = attr.getInt();
switch(id) {
case PROOF_OF_ROTATION_ATTR_ID:
if (por != null) {
throw new SecurityException(“Encountered multiple Proof-of-rotation records”
- " when verifying APK Signature Scheme v3 signature");
}
por = verifyProofOfRotationStruct(attr, certFactory);
// make sure that the last certificate in the Proof-of-rotation record matches
// the one used to sign this APK.
try {
if (por.certs.size() > 0
&& !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
certChain[0].getEncoded())) {
throw new SecurityException(“Terminal certificate in Proof-of-rotation”
- " record does not match APK signing certificate");
}
} catch (CertificateEncodingException e) {
throw new SecurityException(“Failed to encode certificate when comparing”
- " Proof-of-rotation record and signing certificate", e);
}
break;
default:
// not the droid we’re looking for, move along, move along.
break;
}
}
return new VerifiedSigner(certChain, por);
}
继续拆分attrs,就是上一个函数传入的additionalAttrs,把减去一个长度和魔术字的数据块attr继续丢进下一个函数verifyProofOfRotationStruct获得por这个变量,最后返回一个VerifiedSigner
ApkSignatureSchemeV3Verifier.verifyProofOfRotationStruct()
frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
private static VerifiedProofOfRotation verifyProofOfRotationStruct(
ByteBuffer porBuf,
CertificateFactory certFactory)
throws SecurityException, IOException {
int levelCount = 0;
int lastSigAlgorithm = -1;
X509Certificate lastCert = null;
List certs = new ArrayList<>();
List flagsList = new ArrayList<>();
// Proof-of-rotation struct:
// A uint32 version code followed by basically a singly linked list of nodes, called levels
// here, each of which have the following structure:
// * length-prefix for the entire level
// - length-prefixed signed data (if previous level exists)
// * length-prefixed X509 Certificate
// * uint32 signature algorithm ID describing how this signed data was signed
// - uint32 flags describing how to treat the cert contained in this level
// - uint32 signature algorithm ID to use to verify the signature of the next level. The
// algorithm here must match the one in the signed data section of the next level.
// - length-prefixed signature over the signed data in this level. The signature here
// is verified using the certificate from the previous level.
// The linking is provided by the certificate of each level signing the one of the next.
try {
// get the version code, but don’t do anything with it: creator knew about all our flags
porBuf.getInt();
while (porBuf.hasRemaining()) {
levelCount++;
ByteBuffer level = getLengthPrefixedSlice(porBuf);
ByteBuffer signedData = getLengthPrefixedSlice(level);
int flags = level.getInt();
int sigAlgorithm = level.getInt();
byte[] signature = readLengthPrefixedByteArray(level);
if (lastCert != null) {
// Use previous level cert to verify current level
Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
PublicKey publicKey = lastCert.getPublicKey();
Signature sig = Signature.getInstance(sigAlgParams.first);
sig.initVerify(publicKey);
if (sigAlgParams.second != null) {
sig.setParameter(sigAlgParams.second);
}
sig.update(signedData);
if (!sig.verify(signature)) {
throw new SecurityException(“Unable to verify signature of certificate #”
-
levelCount + " using " + sigAlgParams.first + " when verifying"
-
" Proof-of-rotation record");
}
}
signedData.rewind();
byte[] encodedCert = readLengthPrefixedByteArray(signedData);
int signedSigAlgorithm = signedData.getInt();
if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
throw new SecurityException(“Signing algorithm ID mismatch for certificate #”
- levelCount + " when verifying Proof-of-rotation record");
}
lastCert = (X509Certificate)
certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
lastSigAlgorithm = sigAlgorithm;
certs.add(lastCert);
flagsList.add(flags);
}
} catch (IOException | BufferUnderflowException e) {
throw new IOException(“Failed to parse Proof-of-rotation record”, e);
} catch (NoSuchAlgorithmException | InvalidKeyException
| InvalidAlgorithmParameterException | SignatureException e) {
throw new SecurityException(
“Failed to verify signature over signed data for certificate #”
- levelCount + " when verifying Proof-of-rotation record", e);
} catch (CertificateException e) {
throw new SecurityException(“Failed to decode certificate #” + levelCount
- " when verifying Proof-of-rotation record", e);
}
return new VerifiedProofOfRotation(certs, flagsList);
}
这里的注释已经详细的给出了v3新特性的中Proof-of-rotation的结构说明,图示如下:
| ProofOfRotationStruct | |
| — | — |
| ProofOfRotationStruct | |
| version code (4Byte) | |
| levelLength (4Byte) <- level 0 | |
| signedDataLength (4Byte) | |
| encodedCertLength (4Byte) | |
| encodedCert (Byte[]) | |
| signedSigAlgorithm (4Byte) | |
| flags (4Byte) | |
| sigAlgorithm (4Byte) | |
| signatureLength (4Byte) | |
| signature (Byte[]) | |
| levelLength (4Byte) | <- level 1 |
| signedDataLength (4Byte) | |
| encodedCertLength (4Byte) | |
| encodedCert (Byte[]) | |
| signedSigAlgorithm (4Byte) | |
| flags (4Byte) | |
| sigAlgorithm (4Byte) | |
| signatureLength (4Byte) | |
| signature (Byte[]) | |
| levelLength (4Byte) <- level 2 | |
| …… | |
| …… | |
| …… | |
这里其实就是一个证书链的验证:
-
利用level 0证书中的公钥去验证level 1的数据
-
同理level 1证书中的公钥去验证level 2的数据
-
若最后的证书是level 2,则保证整个签名的证书需与level 2匹配
即如果我们想换新证书的时候,需要在por中添加最老的证书为level 0,利用最老的证书去保证新的证书,然后在利用新的证书签名即可。这个函数最终会返回VerifiedProofOfRotation对象,即之前将赋值给por的对象,参数为证书列表和标记列表。
ApkSignatureSchemeV3Verifier.VerifiedProofOfRotation()
frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
public static class VerifiedProofOfRotation {
public final List certs;
public final List flagsList;
public VerifiedProofOfRotation(List certs, List flagsList) {
this.certs = certs;
this.flagsList = flagsList;
}
}
就是一个成员是两个列表的类,返回到verifyAdditionalAttributes()函数,这个函数就是验证了,por中最后的证书是否匹配我们签名整个apk包的证书,最后返回VerifiedSigner(certChain, por),第一个参数里就一个证书,第二个参数里有一个证书链
ApkSignatureSchemeV3Verifier.VerifiedSigner()
frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
public static class VerifiedSigner {
public final X509Certificate[] certs;
public final VerifiedProofOfRotation por;
public byte[] verityRootHash;
public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
this.certs = certs;
this.por = por;
}
}
就是把传进来的两个参数放入成员变量中,回到ApkSignatureSchemeV3Verifier.verify()函数中,最终的返回值赋给result这个变量里,不过在返回result之前,因为没有跳过完整性检查doVerifyIntegrity,这个值为真所以继续执行ApkSigningBlockUtils.verifyIntegrity()这个函数,这个完整性检查与v2版本完全相同,就是将apk文件分段分成1M大小的数据然后hash,比较的数据已经通过verifySigner()函数解析出来了,就是contentDigests。继续这个函数的深入分析先留白。
ApkSigningBlockUtils.verifyIntegrity()
frameworks/base/core/java/android/util/apk/ApkSigningBlockUtils.java
检查完毕从ApkSignatureSchemeV3Verifier.verify()返回result到ApkSignatureVerifier.verify()
ApkSignatureVerifier.verify()
frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java
try {
ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
ApkSignatureSchemeV3Verifier.verify(apkPath);
Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
Signature[] signerSigs = convertToSignatures(signerCerts);
Signature[] pastSignerSigs = null;
int[] pastSignerSigsFlags = null;
if (vSigner.por != null) {
// populate proof-of-rotation information
pastSignerSigs = new Signature[vSigner.por.certs.size()];
pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
for (int i = 0; i < pastSignerSigs.length; i++) {
pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
}
}
return new PackageParser.SigningDetails(
signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
pastSignerSigs, pastSignerSigsFlags);
这里可以看出我们最后的por证书链信息放到了返回的SigningDetails中的pastSignerSigs, pastSignerSigsFlags与成员中。最后会把这个变量赋值给pkg.mSigningDetails,返回到PMS
PackageManagerService.InstallPackageLI()
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
if (bp != null) {
// If the defining package is signed with our cert, it’s okay. This
// also includes the “updating the same package” case, of course.
// “updating same package” could also involve key-rotation.
final boolean sigsOk;
final String sourcePackageName = bp.getSourcePackageName();
final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
final KeySetManagerService ksms = mSettings.mKeySetManagerService;
if (sourcePackageName.equals(pkg.packageName)
&& (ksms.shouldCheckUpgradeKeySetLocked(
sourcePackageSetting, scanFlags))) {
sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg);
} else {
// in the event of signing certificate rotation, we need to see if the
// package’s certificate has rotated from the current one, or if it is an
// older certificate with which the current is ok with sharing permissions
if (sourcePackageSetting.signatures.mSigningDetails.checkCapability(
pkg.mSigningDetails,
PackageParser.SigningDetails.CertCapabilities.PERMISSION)) {
sigsOk = true;
} else if (pkg.mSigningDetails.checkCapability(
sourcePackageSetting.signatures.mSigningDetails,
PackageParser.SigningDetails.CertCapabilities.PERMISSION)) {
// the scanned package checks out, has signing certificate rotation
// history, and is newer; bring it over
sourcePackageSetting.signatures.mSigningDetails = pkg.mSigningDetails;
sigsOk = true;
} else {
sigsOk = false;
}
}
这里首先通过包名获取到包的设置信息,如果不为空则证明已经安装过这个应用,过了一堆判断最终进入checkCapability()函数信,新的签名和老的签名信息互相检查,如果是第一次安装则不会对证书进行进一步的检查
PackageParser.SigningDetails.checkCapability()
frameworks/base/core/java/android/content/pm/PackageParser.java
public boolean checkCapability(SigningDetails oldDetails, @CertCapabilities int flags) {
if (this == UNKNOWN || oldDetails == UNKNOWN) {
return false;
}
if (oldDetails.signatures.length > 1) {
// multiple-signer packages cannot rotate signing certs, so we must have an exact
// match, which also means all capabilities are granted
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结
最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
rser.SigningDetails.checkCapability()
frameworks/base/core/java/android/content/pm/PackageParser.java
public boolean checkCapability(SigningDetails oldDetails, @CertCapabilities int flags) {
if (this == UNKNOWN || oldDetails == UNKNOWN) {
return false;
}
if (oldDetails.signatures.length > 1) {
// multiple-signer packages cannot rotate signing certs, so we must have an exact
// match, which also means all capabilities are granted
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-fXEqoRA3-1713006850893)]
[外链图片转存中…(img-TZqQz0IA-1713006850894)]
[外链图片转存中…(img-pweRbHgi-1713006850895)]
[外链图片转存中…(img-qGZHJPQw-1713006850895)]
[外链图片转存中…(img-ddSLceoV-1713006850895)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结
最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
[外链图片转存中…(img-BU72TdHf-1713006850895)]
[外链图片转存中…(img-MGLbqhyC-1713006850896)]
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!