Android P v3签名新特性

  • @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.

  • @throws IOException if an I/O error occurs while reading the APK file.

*/

private static SignatureInfo findSignature(RandomAccessFile apk)

throws IOException, SignatureNotFoundException {

return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);

}

/**

  • Verifies the contents of the provided APK file against the provided APK Signature Scheme v3

  • Block.

  • @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it

  •    against the APK file.
    

*/

private static VerifiedSigner verify(

RandomAccessFile apk,

SignatureInfo signatureInfo,

boolean doVerifyIntegrity) throws SecurityException, IOException {

}

  • 通过函数重载,最终的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

return signaturesMatchExactly(oldDetails);

} else {

// we may have signing certificate rotation history, check to see if the oldDetails

// was one of our old signing certificates, and if we grant it the capability it’s

// requesting

return hasCertificate(oldDetails.signatures[0], flags);

}

}

根据注释可以看出,如果是多签名的则不被允许使用新特性。最终进入hasCertificate()函数

PackageParser.SigningDetails.hasCertificate()

frameworks/base/core/java/android/content/pm/PackageParser.java

public boolean hasCertificate(Signature signature, @CertCapabilities int flags) {

return hasCertificateInternal(signature, flags);

}

/** Convenient wrapper for calling {@code hasCertificate} with certificate’s raw bytes. */

public boolean hasCertificate(byte[] certificate) {

Signature signature = new Signature(certificate);

return hasCertificate(signature);

}

private boolean hasCertificateInternal(Signature signature, int flags) {

if (this == UNKNOWN) {

return false;

}

// only single-signed apps can have pastSigningCertificates

if (hasPastSigningCertificates()) {

// check all past certs, except for the current one, which automatically gets all

// capabilities, since it is the same as the current signature

for (int i = 0; i < pastSigningCertificates.length - 1; i++) {

if (pastSigningCertificates[i].equals(signature)) {

if (flags == PAST_CERT_EXISTS

|| (flags & pastSigningCertificatesFlags[i]) == flags) {

return true;

总结

首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。

另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。

之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。

下一步还是要查漏补缺,进行针对性复习。

最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!希望大家不要犯和我一样的错误呀!!!一定要看完!

参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF
oldDetails

// was one of our old signing certificates, and if we grant it the capability it’s

// requesting

return hasCertificate(oldDetails.signatures[0], flags);

}

}

根据注释可以看出,如果是多签名的则不被允许使用新特性。最终进入hasCertificate()函数

PackageParser.SigningDetails.hasCertificate()

frameworks/base/core/java/android/content/pm/PackageParser.java

public boolean hasCertificate(Signature signature, @CertCapabilities int flags) {

return hasCertificateInternal(signature, flags);

}

/** Convenient wrapper for calling {@code hasCertificate} with certificate’s raw bytes. */

public boolean hasCertificate(byte[] certificate) {

Signature signature = new Signature(certificate);

return hasCertificate(signature);

}

private boolean hasCertificateInternal(Signature signature, int flags) {

if (this == UNKNOWN) {

return false;

}

// only single-signed apps can have pastSigningCertificates

if (hasPastSigningCertificates()) {

// check all past certs, except for the current one, which automatically gets all

// capabilities, since it is the same as the current signature

for (int i = 0; i < pastSigningCertificates.length - 1; i++) {

if (pastSigningCertificates[i].equals(signature)) {

if (flags == PAST_CERT_EXISTS

|| (flags & pastSigningCertificatesFlags[i]) == flags) {

return true;

总结

首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。

另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。

之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。

下一步还是要查漏补缺,进行针对性复习。

最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!希望大家不要犯和我一样的错误呀!!!一定要看完!
[外链图片转存中…(img-xf0bR8TF-1724256455592)]

[外链图片转存中…(img-rrtiWzcE-1724256455593)]

[外链图片转存中…(img-9yn5zWXk-1724256455593)]

参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值